场景需求:
有时候某个接口涉及的逻辑很多,比如:查数据库、查redis、远程调用接口,发mq消息,执行业务代码等等。
该接口一次请求的链路很长,如果逐一排查,需要花费大量的时间,这时候,我们已经没法用传统的办法定位问题了。
有没有办法解决这问题呢?
用分布式链路跟踪系统:skywalking。
架构图如下:通过skywalking定位性能问题:在skywalking中可以通过traceId(全局唯一的id),串联一个接口请求的完整链路。可以看到整个接口的耗时,调用的远程服务的耗时,访问数据库或者redis的耗时等等,功能非常强大。
之前没有这个功能的时候,为了定位线上接口性能问题,我们还需要在代码中加日志,手动打印出链路中各个环节的耗时情况,然后再逐一排查。
如果你用过skywalking排查接口性能问题,不自觉的会爱上它的。如果你想了解更多功能,可以访问skywalking的官网:https://skywalking.apache.org/
除了这个,项目中也简单做了日志链路的追踪:
1.定义日志拦截器
package com.icon.config;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* 定义拦截器拦截日志链路唯一标识traceid的值
*/
@Component
public class LogInterceptor implements HandlerInterceptor {
@Value(value = "${trace.id:traceid}")
private String TRACE_ID;
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) {
MDC.remove(TRACE_ID);
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 定义日志链路追踪唯一标识的key=traceid 前端UUID没有传递过来,则后台UUID生成
String traceId = request.getHeader(TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
MDC.put(TRACE_ID, traceId);
return true;
}
}
2.配置拦截器
package com.hermes.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//指定拦截器,指定拦截路径
registry.addInterceptor(logInterceptor).addPathPatterns("/**")
.excludePathPatterns("/index.html","/css/**","/fonts/**","/img/**","/js/**");
}
}
3.配置logback日志,输出traceid,完成日志链路追踪
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="2 seconds" debug="false">
<!--定义参数常量-->
<property name="log.level" value="debug"/>
<property name="log.maxHistory" value="15"/>
<!-- test -->
<property name="log.filePath" value="/opt/backend.log"/>
<!-- pro -->
<!-- <property name="log.filePath" value="/opt/backend.log/"/>-->
<!-- <property name="log.pattern"-->
<!-- value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} >> %msg%n"/>-->
<!--配置logback日志,输出traceid,完成日志链路追踪-->
<property name="log.pattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%X{traceid}] [%thread] %logger{50}:%L >> %msg%n"/>
<!--控制台设置-->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 设置DEBUG级别的日志-->
<appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--<file>${log.filePath}/debug.log</file>-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.filePath}/debug/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 设置INFO级别的日志-->
<appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--<file>${log.filePath}/info.log</file>-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.filePath}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 设置ERROR级别的日志-->
<appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--<file>${log.filePath}/error.log</file>-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.filePath}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 绑定到logger-->
<!-- <logger name="com.tory.shop" level="${log.level}" additivity="true">-->
<!-- <appender-ref ref="debugAppender"/>-->
<!-- <appender-ref ref="infoAppender"/>-->
<!-- <appender-ref ref="errorAppender"/>-->
<!-- </logger>-->
<!-- 1. 输出SQL 到控制台和文件-->
<logger name="org.hibernate.SQL" additivity="false" >
<level value="info" />
<appender-ref ref="debugAppender"/>
<appender-ref ref="infoAppender"/>
<appender-ref ref="errorAppender"/>
</logger>
<!-- 2. 输出SQL 的参数到控制台和文件-->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" additivity="false" level="info" >
<level value="info" />
<appender-ref ref="debugAppender"/>
<appender-ref ref="infoAppender"/>
<appender-ref ref="errorAppender"/>
</logger>
<root level="info">
<appender-ref ref="consoleAppender"/>
<appender-ref ref="debugAppender"/>
<appender-ref ref="infoAppender"/>
<appender-ref ref="errorAppender"/>
</root>
</configuration>
测试:
End