本 demo 使用 SpringBoot 提供的 Admin 对其他项目进行监控,实时监测项目的健康状况,并配置实现了通过微信公众号对项目的上下线进行通知告警。
首先创建 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
,即可进入监控页面,此时还没有服务注册上来
首先需要进行微信测试公众号的申请,具体流程自行搜索…
编写微信公众号配置类,按照微信公众号测试号管理页面的相应信息进行配置
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"
至此 Admin Server 的配置全部完成。
创建 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 监控主页,此时客户端服务已经注册到服务器并被其监控
关闭 adminClient 服务,微信公众号推送服务下线告警信息
重新启动 adminClient 服务,微信公众号推送服务上线通知
配置客户端日志的相关设置,使其日志可以在服务器端实时同步
在 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 监控页面,点击服务进入详情页面,点击左侧日志-日志文件即可查看服务的日志,该日志动态滚动更新