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

posted on 2025-03-20 01:03  少年攻城狮  阅读(657)  评论(0)    收藏  举报

导航