当前位置:   article > 正文

Java实现日志全链路追踪.精确到一次请求的全部流程_java 链路追踪

java 链路追踪

广大程序员在排除线上问题时,会经常遇见各种BUG.处理这些BUG的时候日志就格外的重要.只有完善的日志才能快速有效的定位问题.为了提高BUG处理效率.我决定在日志上面优化.实现每次请求有统一的id.通过id能获取当前接口的全链路流程走向. 

实现效果如下: 一次查询即可找到所有关键信息.不再被多线程日志进行困扰了.

1:日志打印框架log4j ->  logback

logback是springboot默认自带的日志框架。不仅速度更快,而且内存占用也更小. (如果之前没用过log4j的建议先去学习下怎么使用).

打印日志的配置文件如下:  logback.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration scan="true" scanPeriod="60 seconds">
  3. <include resource="org/springframework/boot/logging/logback/base.xml"/>
  4. <jmxConfigurator/>
  5. <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  6. <Target>System.out</Target>
  7. <encoder>
  8. <charset>UTF-8</charset>
  9. <pattern>%d - [%t] %-5p %c:%L %X - %m%n</pattern>
  10. </encoder>
  11. </appender>
  12. <appender name="DAILY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  13. <Append>true</Append>
  14. <!-- 日志输出路径 -->
  15. <File>/opt/logs/logOut.log</File>
  16. <encoder>
  17. <charset>UTF-8</charset>
  18. <pattern>%d - [%t] %-5p %c:%L %X - %m%n</pattern>
  19. </encoder>
  20. <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  21. <fileNamePattern>/opt/logs/logOut.log.%d{yyyy-MM-dd}</fileNamePattern>
  22. <maxHistory>7</maxHistory>
  23. </rollingPolicy>
  24. <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
  25. <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
  26. <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread][%X{traceId}] %-5level %logger{50} - %msg%n</pattern>
  27. </encoder>
  28. </appender>
  29. <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  30. <File>/opt/logs/logOut-error.log</File>
  31. <Append>true</Append>
  32. <encoder>
  33. <charset>UTF-8</charset>
  34. <pattern>%d - [%t] %-5p %c:%L %X - %m%n</pattern>
  35. </encoder>
  36. <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
  37. <level>ERROR</level>
  38. </filter>
  39. <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  40. <fileNamePattern>/opt/logs/logOut-error.log.%d{yyyy-MM-dd}</fileNamePattern>
  41. <maxHistory>7</maxHistory>
  42. </rollingPolicy>
  43. </appender>
  44. <logger name="org.springframework" level="INFO"/>
  45. <root level="INFO">
  46. <appender-ref ref="DAILY_FILE"/>
  47. <appender-ref ref="ROLLING_FILE"/>
  48. </root>
  49. </configuration>

重点是这行代码

<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread][%X{traceId}] %-5level %logger{50} - %msg%n</pattern>

此时的配置文件打印只能出现 年月日-线程名这些关键信息.无法获得每次请求的唯一id.所以我们需要创建一个拦截器.将每次请求生成一个id.通过id把本次请求覆盖到每个流程中.

2.1: 编写 http请求 拦截器

  1. public class TraceWebInterceptor extends HandlerInterceptorAdapter {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(TraceWebInterceptor.class);
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. request.setAttribute("startTime", System.currentTimeMillis());
  6. //traceOrigin、traceCaller、traceId
  7. String traceOrigin = request.getHeader(TraceConstants.LOG_TRACE_ORIGIN);
  8. String traceCaller = request.getHeader(TraceConstants.LOG_TRACE_CALLER);
  9. String traceId = request.getHeader(TraceConstants.LOG_TRACE_ID);
  10. //如果不存在traceId需要生成
  11. if (StringUtils.isBlank(traceId)) {
  12. boolean generate = TraceUtil.loadTraceInfo();
  13. if(generate) {
  14. LOGGER.debug("[生成追踪信息]" + TraceUtil.getTraceInfoString());
  15. }
  16. }else {
  17. //设置MDC
  18. MDC.put(TraceConstants.LOG_TRACE_ORIGIN, traceOrigin);
  19. MDC.put(TraceConstants.LOG_TRACE_CALLER, traceCaller);
  20. MDC.put(TraceConstants.LOG_TRACE_ID, traceId);
  21. }
  22. //IP
  23. String traceIp = IpUtil.getIp(request);
  24. MDC.put(TraceConstants.LOG_TRACE_IP, traceIp);
  25. //响应返回
  26. response.setHeader(TraceConstants.LOG_TRACE_ID, TraceUtil.getTraceId());
  27. return super.preHandle(request, response, handler);
  28. }
  29. @Override
  30. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException {
  31. if (LOGGER.isInfoEnabled()) {
  32. long upmsStartTime = (long) request.getAttribute("startTime");
  33. long upmsEndTime = System.currentTimeMillis();
  34. long upmsIntervalTime = upmsEndTime - upmsStartTime;
  35. LOGGER.info("{} {}接口耗时{}毫秒", request.getRequestURL(), request.getMethod(), upmsIntervalTime);
  36. }
  37. MDC.clear();
  38. }

2.2 编写Config类, 将拦截器TraceWebInterceptor添加到容器

  1. @Configuration
  2. @ConditionalOnClass({HandlerInterceptorAdapter.class, MDC.class, HttpServletRequest.class})
  3. public class TraceWebAutoConfiguration implements WebMvcConfigurer {
  4. private static List<String> EXCLUDE_PATHS = new ArrayList<>();
  5. @Value("${" + TraceConstants.CONFIG_TRACE_EXCLUDE_PATHS + ":}")
  6. private String excludePaths;
  7. @Bean
  8. public TraceWebInterceptor traceWebInterceptor() {
  9. return new TraceWebInterceptor();
  10. }
  11. @Override
  12. public void addInterceptors(InterceptorRegistry registry) {
  13. EXCLUDE_PATHS.add("/error");
  14. EXCLUDE_PATHS.add("/actuator/**");
  15. if (StringUtils.isNotBlank(excludePaths)) {
  16. if (excludePaths.contains(",")) {
  17. String[] split = excludePaths.split(",");
  18. EXCLUDE_PATHS.addAll(Arrays.asList(split));
  19. } else {
  20. EXCLUDE_PATHS.add(excludePaths);
  21. }
  22. }
  23. //该方式不能过全部过滤掉
  24. registry.addInterceptor(traceWebInterceptor()).order(-100).excludePathPatterns(EXCLUDE_PATHS);
  25. }
  26. }

2.3 编写工具类

  1. import javax.servlet.http.HttpServletRequest;
  2. public class IpUtil {
  3. private static final String UNKNOWN = "unknown";
  4. public static String getIp(HttpServletRequest request) {
  5. if (request == null) {
  6. return UNKNOWN;
  7. }
  8. String ip = request.getHeader("x-forwarded-for");
  9. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  10. ip = request.getHeader("Proxy-Client-IP");
  11. }
  12. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  13. ip = request.getHeader("X-Forwarded-For");
  14. }
  15. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  16. ip = request.getHeader("WL-Proxy-Client-IP");
  17. }
  18. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  19. ip = request.getHeader("X-Real-IP");
  20. }
  21. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  22. ip = request.getRemoteAddr();
  23. }
  24. return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
  25. }
  26. }
  1. public class TraceConstants {
  2. public static final String LOG_TRACE_ORIGIN = "traceOrigin";
  3. public static final String LOG_TRACE_CALLER = "traceCaller";
  4. public static final String LOG_TRACE_IP = "traceIp";
  5. public static final String LOG_TRACE_ID = "traceId";
  6. public static final String CONFIG_TRACE_EXCLUDE_PATHS = "trace.exclude.paths";
  7. public TraceConstants() {
  8. }
  9. }
  1. import org.apache.commons.lang3.StringUtils;
  2. import org.slf4j.MDC;
  3. import java.util.UUID;
  4. public class TraceUtil {
  5. private static boolean simbaHttpClientInterceptorFlag = true;
  6. private static boolean sdkInterceptorFlag = false;
  7. private static String applicationName;
  8. public TraceUtil() {
  9. }
  10. public static void setApplicationName(String applicationName) {
  11. TraceUtil.applicationName = applicationName;
  12. }
  13. public static String getApplicationName() {
  14. return applicationName;
  15. }
  16. public static boolean getSimbaHttpClientInterceptorFlag() {
  17. return simbaHttpClientInterceptorFlag;
  18. }
  19. public static void setSimbaHttpClientInterceptorFlag(boolean simbaHttpClientInterceptorFlag) {
  20. TraceUtil.simbaHttpClientInterceptorFlag = simbaHttpClientInterceptorFlag;
  21. }
  22. public static boolean getSdkInterceptorFlag() {
  23. return sdkInterceptorFlag;
  24. }
  25. public static void setSdkInterceptorFlag(boolean sdkInterceptorFlag) {
  26. TraceUtil.sdkInterceptorFlag = sdkInterceptorFlag;
  27. }
  28. public static void setTraceCaller(String traceCaller) {
  29. MDC.put("traceCaller", traceCaller);
  30. }
  31. public static String getTraceCaller() {
  32. return MDC.get("traceCaller");
  33. }
  34. public static void setTraceOrigin(String traceOrigin) {
  35. MDC.put("traceOrigin", traceOrigin);
  36. }
  37. public static String getTraceOrigin() {
  38. return MDC.get("traceOrigin");
  39. }
  40. public static void setTraceId(String traceId) {
  41. MDC.put("traceId", traceId);
  42. }
  43. public static void removeTraceId() {
  44. MDC.remove("traceId");
  45. }
  46. public static void clearMdc() {
  47. MDC.clear();
  48. }
  49. public static String getTraceId() {
  50. return MDC.get("traceId");
  51. }
  52. public static String genTraceId() {
  53. return UUID.randomUUID().toString().replace("-", "");
  54. }
  55. public static String getTraceIp() {
  56. return MDC.get("traceIp");
  57. }
  58. public static void setTraceIp(String traceIp) {
  59. MDC.put("traceIp", traceIp);
  60. }
  61. public static boolean loadTraceInfo() {
  62. boolean generate = false;
  63. String traceId = getTraceId();
  64. if (StringUtils.isBlank(traceId)) {
  65. traceId = genTraceId();
  66. generate = true;
  67. }
  68. setTraceId(traceId);
  69. return generate;
  70. }
  71. public static String getTraceInfoString() {
  72. return "TraceId:" + getTraceId() + ". traceCaller:" + getTraceCaller() + ". traceOrigin:" + getTraceOrigin();
  73. }
  74. }
  1. import org.apache.commons.lang3.StringUtils;
  2. import org.slf4j.MDC;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  8. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  9. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  10. import javax.servlet.http.HttpServletRequest;
  11. import java.util.ArrayList;
  12. import java.util.Arrays;
  13. import java.util.List;
  14. @Configuration
  15. @ConditionalOnClass({HandlerInterceptorAdapter.class, MDC.class, HttpServletRequest.class})
  16. public class TraceWebAutoConfiguration implements WebMvcConfigurer {
  17. private static List<String> EXCLUDE_PATHS = new ArrayList<>();
  18. @Value("${" + TraceConstants.CONFIG_TRACE_EXCLUDE_PATHS + ":}")
  19. private String excludePaths;
  20. @Bean
  21. public TraceWebInterceptor traceWebInterceptor() {
  22. return new TraceWebInterceptor();
  23. }
  24. @Override
  25. public void addInterceptors(InterceptorRegistry registry) {
  26. EXCLUDE_PATHS.add("/error");
  27. EXCLUDE_PATHS.add("/actuator/**");
  28. if (StringUtils.isNotBlank(excludePaths)) {
  29. if (excludePaths.contains(",")) {
  30. String[] split = excludePaths.split(",");
  31. EXCLUDE_PATHS.addAll(Arrays.asList(split));
  32. } else {
  33. EXCLUDE_PATHS.add(excludePaths);
  34. }
  35. }
  36. //该方式不能过全部过滤掉
  37. registry.addInterceptor(traceWebInterceptor()).order(-100).excludePathPatterns(EXCLUDE_PATHS);
  38. }
  39. }
  1. import org.apache.commons.lang3.StringUtils;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.slf4j.MDC;
  5. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.io.IOException;
  9. public class TraceWebInterceptor extends HandlerInterceptorAdapter {
  10. private static final Logger LOGGER = LoggerFactory.getLogger(TraceWebInterceptor.class);
  11. @Override
  12. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  13. request.setAttribute("startTime", System.currentTimeMillis());
  14. //traceOrigin、traceCaller、traceId
  15. String traceOrigin = request.getHeader(TraceConstants.LOG_TRACE_ORIGIN);
  16. String traceCaller = request.getHeader(TraceConstants.LOG_TRACE_CALLER);
  17. String traceId = request.getHeader(TraceConstants.LOG_TRACE_ID);
  18. //如果不存在traceId需要生成
  19. if (StringUtils.isBlank(traceId)) {
  20. boolean generate = TraceUtil.loadTraceInfo();
  21. if (generate) {
  22. LOGGER.debug("[生成追踪信息]" + TraceUtil.getTraceInfoString());
  23. }
  24. } else {
  25. //设置MDC
  26. MDC.put(TraceConstants.LOG_TRACE_ORIGIN, traceOrigin);
  27. MDC.put(TraceConstants.LOG_TRACE_CALLER, traceCaller);
  28. MDC.put(TraceConstants.LOG_TRACE_ID, traceId);
  29. }
  30. //IP
  31. String traceIp = IpUtil.getIp(request);
  32. MDC.put(TraceConstants.LOG_TRACE_IP, traceIp);
  33. //响应返回
  34. response.setHeader(TraceConstants.LOG_TRACE_ID, TraceUtil.getTraceId());
  35. return super.preHandle(request, response, handler);
  36. }
  37. @Override
  38. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException {
  39. if (LOGGER.isInfoEnabled()) {
  40. long upmsStartTime = (long) request.getAttribute("startTime");
  41. long upmsEndTime = System.currentTimeMillis();
  42. long upmsIntervalTime = upmsEndTime - upmsStartTime;
  43. LOGGER.info("{} {}接口耗时{}毫秒", request.getRequestURL(), request.getMethod(), upmsIntervalTime);
  44. }
  45. MDC.clear();
  46. }
  47. }
  1. import org.slf4j.MDC;
  2. import java.util.Map;
  3. import java.util.concurrent.*;
  4. public class WrapUtil {
  5. public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
  6. return () -> {
  7. if (context == null) {
  8. MDC.clear();
  9. } else {
  10. MDC.setContextMap(context);
  11. }
  12. TraceUtil.loadTraceInfo();
  13. try {
  14. return callable.call();
  15. } finally {
  16. MDC.clear();
  17. }
  18. };
  19. }
  20. public static <T> Callable<T> wrap(final Callable<T> callable) {
  21. return wrap(callable, MDC.getCopyOfContextMap());
  22. }
  23. public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
  24. return () -> {
  25. if (context == null) {
  26. MDC.clear();
  27. } else {
  28. MDC.setContextMap(context);
  29. }
  30. TraceUtil.loadTraceInfo();
  31. try {
  32. runnable.run();
  33. } finally {
  34. MDC.clear();
  35. }
  36. };
  37. }
  38. public static Runnable wrap(final Runnable runnable) {
  39. return wrap(runnable, MDC.getCopyOfContextMap());
  40. }
  41. public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  42. BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
  43. RejectedExecutionHandler handler) {
  44. return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
  45. }
  46. public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  47. BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
  48. return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
  49. }
  50. public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  51. BlockingQueue<Runnable> workQueue) {
  52. return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
  53. }
  54. public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  55. BlockingQueue<Runnable> workQueue,
  56. RejectedExecutionHandler handler) {
  57. return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
  58. }
  59. public static ForkJoinPool newForkJoinPool() {
  60. return new ForkJoinPoolMdcWrapper();
  61. }
  62. public static class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
  63. public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  64. BlockingQueue<Runnable> workQueue) {
  65. super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
  66. }
  67. public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  68. BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
  69. super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
  70. }
  71. public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  72. BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
  73. super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
  74. }
  75. public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  76. BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
  77. RejectedExecutionHandler handler) {
  78. super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
  79. }
  80. @Override
  81. public void execute(Runnable task) {
  82. super.execute(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
  83. }
  84. @Override
  85. public <T> Future<T> submit(Runnable task, T result) {
  86. return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()), result);
  87. }
  88. @Override
  89. public <T> Future<T> submit(Callable<T> task) {
  90. return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
  91. }
  92. @Override
  93. public Future<?> submit(Runnable task) {
  94. return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
  95. }
  96. }
  97. public static class ForkJoinPoolMdcWrapper extends ForkJoinPool {
  98. public ForkJoinPoolMdcWrapper() {
  99. super();
  100. }
  101. public ForkJoinPoolMdcWrapper(int parallelism) {
  102. super(parallelism);
  103. }
  104. public ForkJoinPoolMdcWrapper(int parallelism, ForkJoinWorkerThreadFactory factory,
  105. Thread.UncaughtExceptionHandler handler, boolean asyncMode) {
  106. super(parallelism, factory, handler, asyncMode);
  107. }
  108. @Override
  109. public void execute(Runnable task) {
  110. super.execute(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
  111. }
  112. @Override
  113. public <T> ForkJoinTask<T> submit(Runnable task, T result) {
  114. return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()), result);
  115. }
  116. @Override
  117. public <T> ForkJoinTask<T> submit(Callable<T> task) {
  118. return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
  119. }
  120. }
  121. }

把工具类加上后.此时运行项目. 如果报错就处理下依赖导包. 不报错就说明可以正常使用了. 然后发布代码.运行方法.去查看日志吧. 此时每次请求都已经生成唯一ID了.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/961937
推荐阅读
相关标签
  

闽ICP备14008679号