天天看點

spring boot admin 監控實踐

Spring Boot Admin是一個開源社群項目,用于管理和監控SpringBoot應用程式, 線上檢視日志 修改日志級别等

目錄

    • 使用場景
    • 快速開始
      • Spring Boot Admin 服務端
      • Spring Boot Admin 用戶端
      • Fastjson 相關問題

使用場景

當下網際網路技術發展很快,各類微服務程式的監控也很多,系統運作日志分布式管理的解決方案也很多 ELK 、點評的 CAT等等。

但還有一些小型項目比如各内網小型系統,開發周期也很短的項目,為了這些項目搞個ELK 叢集也是大費周章。

調研發現很早之前的Spring Boot Admin 這個開源項目不錯,既能監控spring boot 程式也能線上檢視日志,一舉兩得。

快速開始

分兩部分服務端 + 用戶端,用戶端即為要被監控的Spring Boot 系統

Spring Boot Admin 服務端

目前 Spring Boot 2.3.8

目前 Spring Boot Admin 2.3.1

Spring Boot Admin 本身就是一個springboot 項目

pom.xml檔案如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- ============================== 引入統一版本控制父類xml ============================== -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>boot-admin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot-admin</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-dependencies</artifactId>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

           
Maven子產品 說 明
spring-boot-admin-starter-server spring-boot-admin 服務端需要引入的jar
spring-boot-starter-security spring-boot-admin 需要登入才能檢視日志監控資訊,如果沒有該子產品則 任何人都能随意通路 admin
spring-boot-starter-mail 監控報警郵件通知

yaml 檔案配置如下:

server:
  port: 8888
spring:
  application:
    name: SpringBootAdmin
  boot:
    admin:
      ui:
        title: SpringBootAdmin-Server
      notify:
        mail:
          to:                    # 預警郵件通知 的 接受方,可以為多個
            - [email protected]
            - [email protected]
          from: [email protected]     # 同  mail.username
  security:
    user:
      name: "admin"              # 設定 spring boot admin 登入的使用者名
      password: "middol123"      # 設定 spring boot admin 登入的密碼
  mail:                          # 設定 spring boot admin 預警郵件通知 的 發送方資訊,這裡以騰訊企業郵箱為例 
    host: smtp.exmail.qq.com
    username: [email protected]
    password: admin123456
    port: 465
    protocol: smtps
           

由于引入了security 子產品,需要建立一個簡單的配置類,來設定Spring boot admin的通路控制,放在可以被掃碼到的包路徑下,配置内容如下:

import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {

    private final String adminContextPath;

    public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter( "redirectTo" );

        http.authorizeRequests()
                .antMatchers( adminContextPath + "/assets/**" ).permitAll()
                .antMatchers( adminContextPath + "/login" ).permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage( adminContextPath + "/login" ).successHandler( successHandler ).and()
                .logout().logoutUrl( adminContextPath + "/logout" ).and()
                .httpBasic().and()
                .csrf().disable();
        // @formatter:on
    }
}
           

啟動類增加 @EnableAdminServer 注解

例如下面的示範:

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAdminServer
public class BootAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootAdminApplication.class, args);
    }

}


           

啟動後, 通路 localhost:8888/ 得到如下界面,輸入使用者名密碼登入即可:

spring boot admin 監控實踐

現在還沒有用戶端注冊上來,我們接下來建立需要監控的用戶端

spring boot admin 監控實踐

Spring Boot Admin 用戶端

目前 Spring Boot 2.3.8

目前 Spring Boot Admin 2.3.1

用戶端(或你本身已經有待監控的SpringBoot項目)引入如下maven依賴即可:

<!--  actuator 需要暴露出的系統相關性能監控資訊給 Spring boot admin -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
        </dependency>

           

這裡的測試用戶端完整 pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>boot-admin-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot-admin-client</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-dependencies</artifactId>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

           

下一步主要設定 application.yml 檔案, 這裡測試用戶端的完整 配置如下:

server:
  port: 8083
  tomcat:
    uri-encoding: UTF-8 # tomcat的URI編碼
    threads:
      max: 1000         # tomcat最大線程數,預設為200
      min-spare: 30     # Tomcat啟動初始化的線程數,預設值25
  servlet:
    context-path: /${spring.application.name}
    encoding:
      charset: UTF-8
      enabled: true
      force: true
  shutdown: graceful    # 開啟優雅停機模式

spring:
  application:
    name: boot-admin-client2
  lifecycle:
    timeout-per-shutdown-phase: 30s      # 優雅停機模式,設定緩沖時間,最大關機等待時間
  servlet:
    multipart:
      enabled: true
      max-file-size: 20MB
      max-request-size: 200MB
  jackson:
    time-zone: GMT+8
  boot:
    admin:
      client:
        url: http://localhost:8888      # spring boot admin  位址
        instance:
          service-base-url: http://localhost:${server.port}        # 在 spring boot admin  控制台中展示的client位址
        username: admin          # spring boot admin 登入認證的使用者名密碼
        password: middol123      # spring boot admin 登入認證的使用者名密碼

management:
  endpoint:
    logfile:       #  需要設定該值為 true 才能在 spring boot admin 中檢視日志
      enabled: true
    shutdown:
      enabled: false
    health:
      show-details: always
  endpoints:     #  需要設定 該值,spring boot admin 才能監控檢查本系統
    web:
      exposure:
        include: "*"

#  設定 logging 資訊才能 讓 spring boot admin 讀取到 log 日志檔案内容
logging:
  pattern:   #  設定彩色日志資訊
    file: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"
  file:   
    name: logs/${spring.application.name}.log    #  這裡參考 官方文檔 ,設定日志的路徑和名稱
    max-history: 7
    max-size: 10MB

           

啟動應用,檢視Spring boot admin,當啟動成功可以看到控制台有一句日志:

2021-01-17 20:39:54.725  INFO 24456 --- [gistrationTask1] d.c.b.a.c.r.ApplicationRegistrator       : Application registered itself as d30fe4656560
           

這樣表示 注冊到 Spring boot admin 成功

檢視spring boot admin 系統界面如下:

spring boot admin 監控實踐

點選 這個綠色的六邊形圖案,進入監控界面

spring boot admin 監控實踐

各類運作系統參數都有,點選日志檢視線上日志:

spring boot admin 監控實踐

到此, 快速實踐完畢,關于其他更多内容(例如內建 Spring cloud)請檢視官方文檔:

https://codecentric.github.io/spring-boot-admin/2.3.1/

Fastjson 相關問題

如果你的 Spring boot 采用 fastjson 作為首選 HttpMessageConverter 的話,需要注意一下 有個 MediaType 需要忽略掉。

/**
	 * Public constant media type for {@code text/plain}.
	 */
	public static final MediaType TEXT_PLAIN;
           

該 MediaType (text/plain) 如果也被Fastjson 支援解析成JSON的話,Spring boot admin 取 log日志檔案内容的時候希望是文本資訊,不是JSON格式的資訊,是以 會報 HTTP 416 錯誤

是以需要将 TEXT_PLAIN 這種 MediaType 忽略掉。

import cn.hutool.core.date.DatePattern;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ToStringSerializer;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 *  一般的  FastJsonHttpMessageConverter  配置
 * @author <a href="mailto:[email protected]" target="_blank" rel="external nofollow" >guzhongtao</a>
 */
@Configuration
public class MyFastJsonConfig {

    @Bean
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList<>();
       supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
     
        // --------------------------------------------------------------------
        //  需要将  TEXT_PLAIN 忽略掉 ,注釋掉
        //  supportedMediaTypes.add(MediaType.TEXT_PLAIN);
         // --------------------------------------------------------------------
         
        supportedMediaTypes.add(MediaType.TEXT_XML);
        converter.setSupportedMediaTypes(supportedMediaTypes);

        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat(DatePattern.NORM_DATETIME_PATTERN);
        config.setCharset(StandardCharsets.UTF_8);

        config.setSerializerFeatures(
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.DisableCircularReferenceDetect
        );

        //解決Long轉json精度丢失的問題
        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
        serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
        serializeConfig.put(Long.class, ToStringSerializer.instance);
        serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
        config.setSerializeConfig(serializeConfig);

        converter.setFastJsonConfig(config);
        return converter;
    }
}