【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警

SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警

  • 基本概述
  • SpringBoot - Admin Server
    • Admin-Server 基本配置
    • 配置微信公众号告警
  • SpringBoot - Admin client
    • Admin-Client 基本配置
    • 配置日志实时同步

基本概述

本 demo 使用 SpringBoot 提供的 Admin 对其他项目进行监控,实时监测项目的健康状况,并配置实现了通过微信公众号对项目的上下线进行通知告警。

SpringBoot - Admin Server

Admin-Server 基本配置

首先创建 maven 项目 adminServer,引入所需的依赖

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>de.codecentricgroupId>
            <artifactId>spring-boot-admin-starter-serverartifactId>
            <version>2.2.2version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.55version>
        dependency>
        
        <dependency>
            <groupId>com.github.binarywanggroupId>
            <artifactId>weixin-java-mpartifactId>
            <version>3.3.0version>
        dependency>
    dependencies>

编写配置文件 application.yml

server:
  port: 8000

spring:
  application:
    ## 注册服务名
    name: admin-server

management:
  endpoint:
    health:
      show-details: always

构造主启动类,使用@EnableAdminServer注解启动 admin-server 服务

@EnableAdminServer
@SpringBootApplication
public class ServerStartApplication {

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

}

启动服务,访问localhost:8000,即可进入监控页面,此时还没有服务注册上来

【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警_第1张图片

配置微信公众号告警

首先需要进行微信测试公众号的申请,具体流程自行搜索…

编写微信公众号配置类,按照微信公众号测试号管理页面的相应信息进行配置

public class WeChatProperties {

    /**
     * 测试号 appID
     */
    public static final String APP_ID = "XXX";

    /**
     * 测试号 appsecret
     */
    public static final String APP_SECRET = "XXX";

    /**
     * 推送用户 id
     */
    public static final String OPEN_ID_ME = "XXX";

    /**
     * 推送模板 id
     */
    public static final String TEMPLATE_ID = "XXX";

}

编写服务上下限监测的告警推送方法

@Slf4j
@Component
public class WeChatNotifier extends AbstractStatusChangeNotifier {

    /**
     * 标题:系统告警
     */
    private final String TITLE_ALARM = "系统告警";

    /**
     * 标题:系统通知
     */
    private final String TITLE_NOTICE = "系统通知";

    /**
     * 状态转换过滤
     */
    private final String[] IGNORE_CHANGES = new String[]{"UNKNOWN:UP", "DOWN:UP"};

    /**
     * 构造器
     */
    public WeChatNotifier(InstanceRepository repository) {
        super(repository);
    }

    /**
     * 微信公众号服务
     */
    WxMpService wxMpService = new WxMpServiceImpl();

    /**
     * 获取微信公众号消息模板
     * @return WxMpTemplateMessage
     */
    private WxMpTemplateMessage getTemplate() {
        WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage();
        wxStorage.setAppId(WeChatProperties.APP_ID);
        wxStorage.setSecret(WeChatProperties.APP_SECRET);

        wxMpService.setWxMpConfigStorage(wxStorage);

        return WxMpTemplateMessage.builder()
                .toUser(WeChatProperties.OPEN_ID_ME)
                .templateId(WeChatProperties.TEMPLATE_ID)
                .build();
    }

    /**
     * 判断是否需要告警
     * @return 判断结果
     */
    @Override
    protected boolean shouldNotify(InstanceEvent event, Instance instance) {
        if (!(event instanceof InstanceStatusChangedEvent)) {
            return false;
        } else {
            InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent)event;
            String from = this.getLastStatus(event.getInstance());
            String to = statusChange.getStatusInfo().getStatus();
            return Arrays.binarySearch(this.IGNORE_CHANGES, from + ":" + to) < 0
                    && Arrays.binarySearch(this.IGNORE_CHANGES, "*:" + to) < 0
                    && Arrays.binarySearch(this.IGNORE_CHANGES, from + ":*") < 0;
        }
    }

    /**
     * 执行告警通知
     * @return Mono
     */
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
        return Mono.fromRunnable(() -> {
            if (event instanceof InstanceStatusChangedEvent) {
                log.info("Instance {} ({}) is {}", instance.getRegistration().getName(),
                        event.getInstance(),
                        ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());

                String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
                WxMpTemplateMessage templateMessage = getTemplate();
                switch (status) {
                    // 健康检查没通过
                    case "DOWN":
                        log.info("发送 健康检查没通过 的通知!");
                        templateMessage.addData(new WxMpTemplateData("tittle",
                                TITLE_ALARM));
                        templateMessage.addData(new WxMpTemplateData("app_name",
                                instance.getRegistration().getName()));
                        templateMessage.addData(new WxMpTemplateData("app_status",
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus()));
                        templateMessage.addData(new WxMpTemplateData("app_ip",
                                instance.getRegistration().getServiceUrl()));
                        templateMessage.addData(new WxMpTemplateData("details",
                                JSON.toJSONString(instance.getStatusInfo().getDetails())));
                        break;
                    // 服务离线
                    case "OFFLINE":
                        log.info("发送 服务离线 的通知!");
                        templateMessage.addData(new WxMpTemplateData("tittle",
                                TITLE_ALARM));
                        templateMessage.addData(new WxMpTemplateData("app_name",
                                instance.getRegistration().getName()));
                        templateMessage.addData(new WxMpTemplateData("app_status",
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus()));
                        templateMessage.addData(new WxMpTemplateData("app_ip",
                                instance.getRegistration().getServiceUrl()));
                        templateMessage.addData(new WxMpTemplateData("details",
                                JSON.toJSONString(instance.getStatusInfo().getDetails())));
                        break;
                    //服务上线
                    case "UP":
                        log.info("发送 服务上线 的通知!");
                        templateMessage.addData(new WxMpTemplateData("tittle",
                                TITLE_NOTICE));
                        templateMessage.addData(new WxMpTemplateData("app_name",
                                instance.getRegistration().getName()));
                        templateMessage.addData(new WxMpTemplateData("app_status",
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus()));
                        templateMessage.addData(new WxMpTemplateData("app_ip",
                                instance.getRegistration().getServiceUrl()));
                        templateMessage.addData(new WxMpTemplateData("details",
                                JSON.toJSONString(instance.getStatusInfo().getDetails())));
                        break;
                    // 服务未知异常
                    case "UNKNOWN":
                        log.info("发送 服务未知异常 的通知!");
                        templateMessage.addData(new WxMpTemplateData("tittle",
                                TITLE_ALARM));
                        templateMessage.addData(new WxMpTemplateData("app_name",
                                instance.getRegistration().getName()));
                        templateMessage.addData(new WxMpTemplateData("app_status",
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus()));
                        templateMessage.addData(new WxMpTemplateData("app_ip",
                                instance.getRegistration().getServiceUrl()));
                        templateMessage.addData(new WxMpTemplateData("details",
                                JSON.toJSONString(instance.getStatusInfo().getDetails())));
                        break;
                    default:
                        break;
                }
                // 执行公众号消息推送
                try {
                    System.out.println(templateMessage.toJson());
                    System.out.println(wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage));
                } catch (Exception e) {
                    System.out.println("推送失败:" + e.getMessage());
                    e.printStackTrace();
                }
            } else {
                log.info("Instance {} ({}) {}",
                        instance.getRegistration().getName(),
                        event.getInstance(),
                        event.getType());
            }
        });
    }

}

在测试号管理中配置相应的消息模板,其中的数据格式均为{{xxx.DATA},其中的 xxx 为我们配置消息时的属性名,如templateMessage.addData(new xMpTemplateData("tittle", TITLE_ALARM));中的"title"

【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警_第2张图片

至此 Admin Server 的配置全部完成。

SpringBoot - Admin client

Admin-Client 基本配置

创建 maven 项目 adminClient 并将其注册到 adminServer 上,实现服务器对客户端服务的监控以及上下线的告警。

引入依赖,Admin 对服务的监控依赖于 actuator 服务

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>de.codecentricgroupId>
            <artifactId>spring-boot-admin-starter-clientartifactId>
            <version>2.2.2version>
        dependency>

在 application.yml 中进行配置,将服务注册到 adminServer 中

spring:
  application:
    ## 注册服务名
    name: admin-client
  ## spring boot admin
  boot:
    admin:
      client:
        url: http://localhost:8000
        instance:
          prefer-ip: true

server:
  port: 8001

#  endpoints config
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    enabled-by-default: true
    web:
      base-path: /actuator
      exposure:
        include: '*'

创建主启动类

@SpringBootApplication
public class ClientStartApplication {

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

}

启动项目,访问localhost:8000 Admin 监控主页,此时客户端服务已经注册到服务器并被其监控

【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警_第3张图片

关闭 adminClient 服务,微信公众号推送服务下线告警信息

【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警_第4张图片

重新启动 adminClient 服务,微信公众号推送服务上线通知

【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警_第5张图片

配置日志实时同步

配置客户端日志的相关设置,使其日志可以在服务器端实时同步

在 resources 文件夹下新建 logback-spring.xml 文件,进行如下配置



<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <property name="CONTEXT_NAME" value="admin-client"/>
    <property name="LOG_PATH" value="logs"/>
    <property name="MAX_FILE_SIZE" value="100MB"/>
    <property name="MAX_HISTORY" value="30"/>

    <contextName>${CONTEXT_NAME}contextName>

    
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>

    
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} [%L] %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    
    <property name="FILE_LOG_PATTERN"
              value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } [%t] %-40.40logger{39} %L : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    
    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>

    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}pattern>
        encoder>
    appender>

    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}pattern>
        encoder>
        <file>${LOG_PATH}/admin-client.logfile>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/admin-client-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            <maxFileSize>${MAX_FILE_SIZE}maxFileSize>
            <maxHistory>${MAX_HISTORY}maxHistory>
        rollingPolicy>
    appender>

    
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}pattern>
        encoder>
        <file>${LOG_PATH}/admin-client-error.logfile>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/admin-client-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            <maxFileSize>${MAX_FILE_SIZE}maxFileSize>
            <maxHistory>${MAX_HISTORY}maxHistory>
        rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERRORlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0discardingThreshold>
        <queueSize>1024queueSize>
        <appender-ref ref="FILE"/>
    appender>

    <appender name="ASYNC_ERROR_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0discardingThreshold>
        <queueSize>1024queueSize>
        <appender-ref ref="ERROR_FILE"/>
    appender>

    
    <springProfile name="local">
        <logger name="admin-client" level="DEBUG"/>
    springProfile>

    
    <logger name="org.apache.catalina.connector.CoyoteAdapter" level="OFF"/>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC_FILE"/>
        <appender-ref ref="ASYNC_ERROR_FILE"/>
    root>

configuration>

在 application.xml 文件中,新增以下日志相关配置

logging:
  config: classpath:logback-spring.xml
  level:
    root: info
  #  方便Spring Boot Admin页面上实时查看日志
  file:
    name: logs/admin-client.log

重新启动项目,进入 Admin 监控页面,点击服务进入详情页面,点击左侧日志-日志文件即可查看服务的日志,该日志动态滚动更新

【应用】SpringBoot -- 基于 SpringBoot-Admin 实现程序监控以及上下线报警_第6张图片

你可能感兴趣的:(SpringBoot,spring,boot,java)