本文源码存放在【GitHub】,如需更多整合学习源码,请点击此处,有帮助请start三连
本文将讲解:SpringBootAdmin服务搭建、集成、日志实时预览、服务告警推送至钉钉群消息
概述
本篇讲解SpringBoot2.X
整合SpringBoot-Admin
监控。Spring Boot Admin
就是将 Spring Boot Actuator
中提供的endpoint
信息可视化表示,并且可以通过钉钉群、邮件、Telegram、Hipchat等发送告警消息。
预览效果
服务说明
SpringBootAdmin-Server
应用Application
)Application
)spring boot admin-server
源码在github
上,可以通过以下2种方式启动,考虑到后续需要扩展钉钉推送这里我选择第二种
java -jar jar包
的方式启动SpringBoot
项目,引入官方提供的pom依赖,通过自己的项目的方式来启动编码实现
加入pom
依赖
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>05-boot-adminartifactId>
<groupId>com.it235.cloud.examplegroupId>
<version>1.0.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>05-boot-admin-serverartifactId>
<description>
SpringBootAdmin的服务端,一般是一个服务端管理多个服务,
可以采用官方的jar,也可以自己集成,这里我们是自己集成编写服务
description>
<properties>
<spring-boot-admin.version>2.2.0spring-boot-admin.version>
<spring-cloud.version>Hoxton.SR3spring-cloud.version>
<spring-cloud-alibaba.version>2.2.1.RELEASEspring-cloud-alibaba.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-serverartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-dependenciesartifactId>
<version>${spring-boot-admin.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
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
服务应用A、B是指当前你已存在的应用服务,用于编写业务的服务,如:订单服务、课程服务等。由于各服务配置基本相同,这里我就以A服务为例进行讲解。
spring-boot-admin
提供了spring-boot-admin-starter-client.jar
进行admin-server
的注册。
添加pom依赖
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>05-boot-adminartifactId>
<groupId>com.it235.cloud.examplegroupId>
<version>1.0.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>05-boot-admin-AartifactId>
<description>SpringBoot2.X整合spring-boot-admindescription>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR3version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-clientartifactId>
<version>2.2.0version>
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
文件,输入以下信息
<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}}"/>
<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.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/it235-boot-admin-%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}/it235-bootadmin-a-error.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/it235-boot-admin-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="com.it235" 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>
修改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.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.13version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpcoreartifactId>
<version>4.4.13version>
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.com/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");
到这里,本篇基本讲解了spring-boot-admin
在企业中的应用场景,如需更多整合代码,请点击访问源码,如对你有帮助,三连走起