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

基本概述

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

SpringBoot - Admin Server

Admin-Server 基本配置

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

    <dependencies>
        <!--springboot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--springboot admin server-->
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.55</version>
        </dependency>
        <!--微信模版消息推送三方sdk-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>3.3.0</version>
        </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,即可进入监控页面,此时还没有服务注册上来

在这里插入图片描述

配置微信公众号告警

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

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

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<Void>
     */
    @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"

在这里插入图片描述

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

SpringBoot - Admin client

Admin-Client 基本配置

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
            <version>2.2.2</version>
        </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 监控主页,此时客户端服务已经注册到服务器并被其监控

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

配置日志实时同步

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

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

<?xml version="1.0" encoding="UTF-8"?>

<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}}"/>

    <!-- 禁用logback自身日志输出 -->
    <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.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/admin-client-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <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.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/admin-client-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            <maxHistory>${MAX_HISTORY}</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 异步写日志 -->
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>1024</queueSize>
        <appender-ref ref="FILE"/>
    </appender>

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

    <!-- 不同环境的日志级别配置 -->
    <springProfile name="local">
        <logger name="admin-client" level="DEBUG"/>
    </springProfile>

    <!-- 解决 SpringBootAdmin 错误日志问题 -->
    <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 监控页面,点击服务进入详情页面,点击左侧日志-日志文件即可查看服务的日志,该日志动态滚动更新

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!对于未授权的springboot-actuator,您可以尝试以下解决方案: 1. 检查配置文件:确保您的应用程序的配置文件中已正确配置了安全认证。您可以在application.properties或application.yml文件中查找以下配置项: management.security.enabled=true 如果该配置项已启用,并且您仍然遇到未授权的问题,请继续尝试下一个解决方案。 2. 添加访问权限:您可以为actuator端点添加访问权限。在您的配置文件中添加以下配置来设置允许访问的角色: management.endpoints.web.exposure.include=* management.endpoint.health.roles=ACTUATOR_ADMIN 这将允许所有角色访问所有的actuator端点。您可以根据需要更改角色名称或将其限制为特定角色。 3. 自定义安全配置:如果默认的安全配置不适用于您的需求,您可以自定义Spring Security配置。创建一个类,继承自WebSecurityConfigurerAdapter,并重写configure方法。在该方法中,您可以定义自己的安全规则和访问规则。 例如,您可以创建一个类似于以下示例的配置: ```java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/actuator/**").hasRole("ACTUATOR_ADMIN") .anyRequest().authenticated() .and() .httpBasic(); } } ``` 这将要求用户在访问actuator端点时进行身份验证,并且只有具有ACTUATOR_ADMIN角色的用户才能访问。 请注意,根据您的具体需求,您可能需要调整上述解决方案的细节。希望这些提示对您有帮助!如有更多问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值