SpringBoot系列---【SpringBoot集成Skywalking】
SkyWalking
1.官网
地址:http://skywalking.apache.org/
单机环境搭建图
2.下载安装包
本文以支持Jdk8的最后一个版本为例:skywalking-apm-8.9.1和java-agent-8.9.0
- skywalking8.7.0之后的版本,agent的相关代码被抽离出skywalking当中,需要自行下载agent,不能直接在这个解压包里复制出来了,解压后没有agent目录了
- SkyWalking 8.x版本要求Java版本至少为8(即JDK 1.8)
- SkyWalking 9.x版本则要求Java版本至少为11(即JDK 11)
- Skywalking10.x版本 jdk22不兼容,用17版本即可。
方式一:官网下载(推荐)网速比方式2快

方式二:下面这个网站下载(这里我用的这个)
APM:Index of /dist/skywalking/8.9.1
AGENT:Index of /dist/skywalking/java-agent/8.9.0
3.oap服务端部署
-
1.修改webui端口:webapp目录下的webapp.yml,server.port默认8080。
-
2.修改数据库存储方式:config目录下的application.yml,storage.selector: {SW_STORAGE:H2}。
-
3.agent下的skywalking的jar包是启动springboot应用时用的。
-
同时启动oap和skywalking-ui:/opt/app/middles/skywalking/apache-skywalking-apm-bin/bin/startup.sh,ui页面访问:http://localhost:8080。
-
skywalking-oap-server服务启动后会暴露11800和12800两个端口,分别为收集监控数据的端口11800和接受前端请求的端口12800,修改端口可以修改
config/applicaiton.yml 。
4.Agent部署
-
1.
把agent目录复制到应用根目录(不能只复制jar包)
;重点注意 -
2.修改JVM启动参数(注意:参数要放到java和
-jar
之间);
java -javaagent:agent/skywalking-agent.jar -DSW_AGENT_NAME=demo-1 -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.137.11:11800 -jar demo-springboot.jar
5.验证
访问:localhost:8080即可查看skywalking-ui,请求接口,然后在追踪页面上找到对应的接口,即可查看耗时。注意:时间范围要选对。
6.集成logback日志
springboot默认集成logback,这里使用默认的即可,不用再单独引入日志坐标。
6.1引入日志pom坐标
<skywalking.version>8.9.0</skywalking.version>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>${skywalking.version}</version>
</dependency>
6.2新增logback-spring.xml日志配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="D:/logs/" ></property>
<!-- 彩色日志 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<!--控制台日志, 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} [%X{tid}] %clr([%-10.10thread]){faint} %clr(%-5level) %clr(%-50.50logger{50}:%-3L){cyan} %clr(-){faint} %msg%n</pattern>
</layout>
</encoder>
</appender>
<!--文件日志, 按照每天生成日志文件 (只能是 由 Logger 或者 LoggerFactory 记录的日志消息哦)-->
<!--以下关于 日志文件的pattern 需要去掉颜色,防止出现 ANSI转义序列-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/pro.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<!-- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%-10.10thread] %-5level %-50.50logger{50}:%-3L - %msg%n</pattern>
</layout>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--skywalking grpc 日志收集-->
<appender name="grpc" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" ></appender-ref>
<appender-ref ref="FILE" ></appender-ref>
<appender-ref ref="grpc"/>
</root>
</configuration>
注意:到这里,系统启动的时候,tid为N/A,当有请求进来的时候,就会打印tid。
7.自定义链路追踪(自动获取入参和响应)
7.1.pom.xml引入坐标
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.9.0</version>
</dependency>
7.2.编写全局过滤器
知识小贴士:为什么不用HttpServletRequest?
如果直接把HttpServletRequest中的InputStream读取后输出日志,会导致后续业务逻辑读取不到InputStream中的内容,因为流只能读取一次。
package com.fast.auth.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Component
public class ApmHttpInfo extends HttpFilter {
//被忽略的头部信息
private static final Set<String> IGNORED_HEADERS;
static {
Set<String> ignoredHeaders = new HashSet<>();
ignoredHeaders.addAll(
java.util.Arrays.asList(
"Content-Type",
"User-Agent",
"Accept",
"Cache-Control",
"Postman-Token",
"Host",
"Accept-Encoding",
"Connection",
"Content-Length"
).stream()
.map(String::toUpperCase)
.collect(Collectors.toList())
);
IGNORED_HEADERS = ignoredHeaders;
}
@Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
try {
filterChain.doFilter(requestWrapper, responseWrapper);
} finally {
try {
//构造请求信息: 比如 curl -X GET http://localhost:18080/getPerson?id=1 -H 'token: me-token' -d '{ "name": "hello" }'
//构造请求的方法&URL&参数
StringBuilder sb = new StringBuilder("curl")
.append(" -X ").append(request.getMethod())
.append(" ").append(request.getRequestURL().toString());
if (StringUtils.hasLength(request.getQueryString())) {
sb.append("?").append(request.getQueryString());
}
//构造header
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (!IGNORED_HEADERS.contains(headerName.toUpperCase())) {
sb.append(" -H '").append(headerName).append(": ").append(request.getHeader(headerName)).append("'");
}
}
//获取body
String body = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
if (StringUtils.hasLength(body)) {
sb.append(" -d '").append(body).append("'");
}
//输出到input
ActiveSpan.tag("input", sb.toString());
//获取返回值body
String responseBody = new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
//输出到output
ActiveSpan.tag("output", responseBody);
} catch (Exception e) {
log.warn("fail to build http log", e);
} finally {
//这一行必须添加,否则就一直不返回
responseWrapper.copyBodyToResponse();
}
}
}
}
7.3.编写测试接口,获取traceId
@GetMapping("getTraceId")
public String getTraceId(String prefix){
ActiveSpan.error(new RuntimeException("业务执行失败了!"));
ActiveSpan.info("业务执行成功了!");
ActiveSpan.debug("业务执行过程");
return TraceContext.traceId();
}
注意:到这里,就可以实现全局打印请求和响应参数了。
7.4.skywalking就会按照执行顺序显示自定义的trace信息了,包括traceId和tags。

skywalking ui页面上最终查看到的效果如下图,此时不会有默认的那一堆没用的了。

8.过滤指定的端点
-
1.复制可选插件apm-trace-ignore-plugin-6.4.0.jar
将agent中的 /agent/optional-plugins/apm-trace-ignore-plugin-6.4.0.jar插件拷贝到
plugins目录中。 -
2.添加jvm忽略参数重启springboot应用
这里以忽略/exclude
接口为例:
java -javaagent:agent/skywalking-agent.jar
-Dskywalking.agent.service_name=demo-1
-Dskywalking.trace.ignore_path=/exclude -jar demo-springboot.jar &
这里添加-Dskywalking.trace.ignore_path=/exclude参数来标识需要过滤哪些请求,支持AntPath表达式:
/path/*, /path/**, /path/?
?匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录
启动之后,再调用exclude接口,就不会被skywalking监控到了。
9.告警功能
这里以service_resp_time服务响应时间为例:
- 1.定义一个耗时1.5s的接口
@GetMapping("timeout")
public String timeout(){
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "timeout";
}
- 2.定义一个webhook回调接口
当满足rule的告警的条件时,会调用此接口,默认为notify接口。
@Data
public class AlarmMessage {
private int scopeId;
private String name;
private int id0;
private int id1;
/**
* 告警消息
*/
private String alarmMessage;
/**
* 告警时间
*/
private long startTime;
}
notify接口
private List<AlarmMessage> latestMessages = new ArrayList<>();
@PostMapping("notify")
public void notify(@RequestBody List<AlarmMessage> alarmMessages){
latestMessages = alarmMessages;
}
@GetMapping("show")
public List<AlarmMessage> notice(){
return latestMessages;
}
- 3.修改oap的alarm-settings.xml文件
服务【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒

- 4.重启oap服务

sh oapService.sh
-
5.重启springboot服务
-
6.验证规则是否生效
连续4次访问timeout接口,告警那里就会生成告警信息。
调用show接口,可以看到skywalking通过webhook传递的信息。nofify可以实现调用短信和邮箱通知逻辑。
常用规则名:

period的单位是分钟,threshold的单位是毫秒,silence-period的单位也是分钟。

10.Open Tracing概念
-
trace
-
span
-
log
-
tag
