本文源码存放在【GitHub】,如需更多整合学习源码,请点击此处,有帮助请start三连
本文将讲解:SpringBootAdmin服务搭建、集成、日志实时预览、服务告警推送至钉钉群消息
前言
-
概述
本篇讲解
SpringBoot2.X
整合SpringBoot-Admin
监控。Spring Boot Admin
就是将Spring Boot Actuator
中提供的endpoint
信息可视化表示,并且可以通过钉钉群、邮件、Telegram、Hipchat等发送告警消息。 -
预览效果
-
服务说明
SpringBootAdmin-Server
应用- 服务应用A(
Application
) - 服务应用B(
Application
)
服务整合
搭建Admin-Server
spring boot admin-server
源码在github
上,可以通过以下2种方式启动,考虑到后续需要扩展钉钉推送这里我选择第二种
- 直接使用官方提供的代码构建成jar之后,通过
java -jar jar包
的方式启动 - 自己建一个
SpringBoot
项目,引入官方提供的pom依赖,通过自己的项目的方式来启动
编码实现
-
加入
pom
依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd"> <parent> <artifactId>05-boot-admin</artifactId> <groupId>com.it235.cloud.example</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>05-boot-admin-server</artifactId> <description> SpringBootAdmin的服务端,一般是一个服务端管理多个服务, 可以采用官方的jar,也可以自己集成,这里我们是自己集成编写服务 </description> <properties> <spring-boot-admin.version>2.2.0</spring-boot-admin.version> <spring-cloud.version>Hoxton.SR3</spring-cloud.version> <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version> </properties> <dependencies> <!--健康检查--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</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-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring-boot-admin依赖--> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-dependencies</artifactId> <version>${spring-boot-admin.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--SpringCloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--Spring Alibaba Cloud--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
-
配置
yml
server: port: 8769 spring: application: name: it235-boot-admin-server management: endpoints: web: exposure: include: '*' endpoint: health: show-details: always logging: level: root: info
-
打开浏览器访问
http://localhost:8769
搭建服务应用A、B
服务应用A、B是指当前你已存在的应用服务,用于编写业务的服务,如:订单服务、课程服务等。由于各服务配置基本相同,这里我就以A服务为例进行讲解。
spring-boot-admin
提供了spring-boot-admin-starter-client.jar
进行admin-server
的注册。
-
添加pom依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd"> <parent> <artifactId>05-boot-admin</artifactId> <groupId>com.it235.cloud.example</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>05-boot-admin-A</artifactId> <description>SpringBoot2.X整合spring-boot-admin</description> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--SpringCloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--加入spring-boot-admin连接端--> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.2.0</version> </dependency> </dependencies> </project>
-
编写
yml
配置server: port: 7056 spring: application: name: it235-boot-admin # 配置spring-boot-admin服务端的地址 boot: admin: client: enabled: true url: http://localhost:8769 # 健康检查访问: http://ip:port/sys/actuator/health management: # 端点信息接口使用的端口,为了和主系统接口使用的端口进行分离 server: port: 7057 servlet: context-path: /sys # 端点健康情况,默认值"never",设置为"always"可以显示硬盘使用情况和线程情况 endpoint: health: show-details: always # 设置端点暴露的哪些内容,默认["health","info"],设置"*"代表暴露所有可访问的端点 endpoints: web: exposure: include: '*' logging: level: root: info
-
编写服务启动类
@SpringBootApplication public class BootAdminAApplication { public static void main(String[] args) { SpringApplication.run(BootAdminAApplication.class , args); } }
-
启动服务,查看admin-server的面板有无变化
注意:此处的你有可能在服务面板上发现没有任何服务注册上来,空空如也
此时你可以将
logging.level.root
调整为debug级别,会发现异常信息javax.management.InstanceNotFoundException: org.springframework.boot:type=Admin,name=SpringApplication
,此时你需要编辑服务参数面板,关闭如下2个勾选项 -
重新启动服务,查看
admin-server
面板 -
我们以同样的方式构建B应用服务,同时查看
admin-server
面板注意A、B的服务名不要一样,否则会当成多个实例进行注册上来
-
查看应用详细信息
信息都非常全,这里我就不带大家一一观看了
日志实时预览
SpringBootAdmin预览实时日志也是一个非常强大的功能,接下来我们看如何去实现。
-
预览效果
-
原理
原理非常简单,通过logback记录日志,配置文件位置,
springboot-admin
定时去抓取某一个位置的日志文件,解析后输出到admin-server
所以此处最重要的是集成logback
-
logback集成
我们在
src/main/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="it235-boot-admin"/> <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}/it235-boot-admin.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/it235-boot-admin-%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}/it235-bootadmin-a-error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/it235-boot-admin-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="com.it235" 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>
-
修改yml文件,将日志配置改为如下
logging: config: classpath:logback-spring.xml level: root: info # 方便Spring Boot Admin页面上实时查看日志 file: logs/it235-boot-admin.log
-
启动应用服务器查看
admin-server
中对应用的日志管理点击-应用-日志,此时你会发现多了日志文件这个子菜单
-
查看日志是否动态输出
在应用中加一个
Controller
,在某个接口中打印几个info或者error,再查看admin-server
面板看是否动态输出@Slf4j @RestController @RequestMapping("/demo") public class DemoController { @GetMapping("get") public String get(){ log.info("这里是新日志,日志XXX:{}" , "abc"); int i = 0; int x = 3 / i; return "ok"; } }
-
浏览器输入
http://localhost:7056/demo/get
查看日志是否输出
服务上下线告警
以往我们一般采用zabbix+邮件
的方式进行告警,但时效性太低,接下来我们通过spring boot admin
来实现服务上下线告警,并将消息推送到钉钉群。
- 报警通知类继承
AbstractStatusChangeNotifier
类,重写shouldNotify
和doNotify
方法 - 根据获取的状态进行实例的信息获取和封装
- 接入钉钉群机器人将封装的消息推送到钉钉群中
编码实现
在spring-boot-admin-server
的服务中编写DingtalkNotifier
类用来实现该功能
package com.it235.cloud.example.notifier;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.client.config.NacosConfigService;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ParserContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Map;
/**
* @description:
* @package: com.it235.cloud.example.notifier
* @author: jianjun.ren
* @date: Created in 2020/10/16 12:43
* @copyright: Copyright (c) 2019
* @modified: jianjun.ren
*/
@Slf4j
@Component
public class DingtalkNotifier extends AbstractStatusChangeNotifier {
/**
* 消息模板
*/
private static final String template = "<<<%s>>> \n 【服务名】: %s(%s) \n 【状态】: %s(%s) \n 【服务ip】: %s \n 【详情】: %s";
private String titleAlarm = "系统告警";
private String titleNotice = "系统通知";
private String[] ignoreChanges = new String[]{"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"};
public DingtalkNotifier(InstanceRepository repository) {
super(repository);
}
@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.ignoreChanges, from + ":" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, "*:" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, from + ":*") < 0;
}
}
@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();
String messageText = null;
switch (status) {
// 健康检查没通过
case "DOWN":
log.info("发送 健康检查没通过 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "健康检查没通过通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
//先输出信息在控制台
System.out.println(messageText);
break;
// 服务离线
case "OFFLINE":
log.info("发送 服务离线 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务离线通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
先输出信息在控制台
System.out.println(messageText);
break;
//服务上线
case "UP":
log.info("发送 服务上线 的通知!");
messageText = String
.format(template,titleNotice, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务上线通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
//先输出信息在控制台
System.out.println(messageText);
break;
// 服务未知异常
case "UNKNOWN":
log.info("发送 服务未知异常 的通知!");
messageText = String
.format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务未知异常通知",
instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
先输出信息在控制台
System.out.println(messageText);
break;
default:
break;
}
} else {
log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
event.getType());
}
});
}
}
启动服务可以看到控制台输出的监课检查通知
-
服务上线通知
-
服务下线通知
-
加入钉钉群消息推送功能
-
推送代码编写
钉钉推送需要发送http请求触发webhook机器人,我们先加入pom.xml依赖
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.13</version> </dependency>
编写
DingtalkUtils代码
package com.it235.cloud.example.notifier; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.http.HttpStatus; import java.util.HashMap; /** * @description: * @package: com.it235.cloud.example.notifier * @author: jianjun.ren * @date: Created in 2020/10/17 0:11 * @copyright: Copyright (c) 2019 * @modified: jianjun.ren */ @Slf4j public class DingtalkUtils { public static void main(String[] args) { pushInfoToDingding("测试消息通知", "b240b227f5add0fsdfsf54721bf08d7ee17114"); } public static Boolean pushInfoToDingding(String textMsg, String dingURL) { HashMap<String, Object> resultMap = new HashMap<>(8); resultMap.put("msgtype", "text"); HashMap<String, String> textItems = new HashMap<>(8); textItems.put("content", textMsg); resultMap.put("text", textItems); HashMap<String, Object> atItems = new HashMap<>(8); atItems.put("atMobiles", null); atItems.put("isAtAll", false); resultMap.put("at", atItems); dingURL = "https://oapi.dingtalk/robot/send?access_token=" + dingURL; try { HttpClient httpClient = HttpClients.createDefault(); StringEntity stringEntity = new StringEntity(JSON.toJSONString(resultMap), "utf-8"); HttpPost httpPost = createConnectivity(dingURL); httpPost.setEntity(stringEntity); HttpResponse response = httpClient.execute(httpPost); if (response.getStatusLine().getStatusCode() == HttpStatus.OK.value()) { String result = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(result); log.info("执行结果:{}" , result); } return Boolean.TRUE; } catch (Exception e) { e.printStackTrace(); return Boolean.FALSE; } } static HttpPost createConnectivity(String restUrl) { HttpPost post = new HttpPost(restUrl); post.setHeader("Content-Type", "application/json"); post.setHeader("Accept", "application/json"); post.setHeader("X-Stream", "true"); return post; } }
更换token后直接运行测试,看是否发送成功,我这了显示发送成功
改造DingtalkNotifier中sout输出的消息为钉钉工具类推送
DingtalkUtils.pushInfoToDingding(messageText , "b240b227f5add0ffba1d04d017b53019f5sdfsfsds12317ee17114");
- 改造完成后,再次启动服务,查看钉钉消息群的信息
- 格式美化
此时的格式仅仅是字符串,钉钉提供markdown及更多的小卡片元素,请大家自行扩展,我的github源码整合库中也有讲解。
结语
到这里,本篇基本讲解了spring-boot-admin
在企业中的应用场景,如需更多整合代码,请点击访问源码,如对你有帮助,三连走起
更多推荐
SpringBoot2整合SpringBootAdmin监控管理服务上下线(跟我学SpringCloud系列)
发布评论