2019独角兽企业重金招聘Python工程师标准>>>

why?

一个web项目通常提供很多URL访问地址,

项目一般都是分层处理,例如Controller——>Service——>DAO。

如果想根据日志查看用户一次请求都走了哪些方法(多数是查错误)。

如果系统是多人访问,日志打印是无序的,想要串行拿到日志是大海捞针。

能通过一个字符串就能将一次请求调用了哪些方法按照顺序搜索出来就好了。

how?

Slf4j的MDC ( Mapped Diagnostic Contexts )专门为此需求而生。

MDC的基本原理是:

通过一个ThreadLocal保存设置的key值,在打印的时候从ThreadLocal中获取到打印出来。由此可知,如果一个请求中的某些方法调用是在另外的线程中执行,那MDC是获取不到该值的。

note 当前线程的子线程会继承其父线程中的MDC的内容,所以不用担心会话过程中,新创建的子线程无法追踪。

为什么最开始子线程会得到父线程MDC设置的内容?创建子线程的时候会调用init(ThreadGroup g, Runnable target, String name,long stackSize)方法,判断如果parent.inheritableThreadLocals不为null就调用createInheritedMap方法把父线程的ThreadLocal里保存的变量都加载到子线程的ThreadLocal中去。所以MDC类的静态变量mdcAdapter(LogbackMDCAdapter实现)中的copyOnInheritThreadLocal会得到父类MDC写入的内容,因为它用的是InheritableThreadLocal这个继承类。

底层代码

/*** This class hides and serves as a substitute for the underlying logging* system's MDC implementation.* * <p>* If the underlying logging system offers MDC functionality, then SLF4J's MDC,* i.e. this class, will delegate to the underlying system's MDC. Note that at* this time, only two logging systems, namely log4j and logback, offer MDC* functionality. For java.util.logging which does not support MDC,* {@link BasicMDCAdapter} will be used. For other systems, i.e. slf4j-simple* and slf4j-nop, {@link NOPMDCAdapter} will be used.** <p>* Thus, as a SLF4J user, you can take advantage of MDC in the presence of log4j,* logback, or java.util.logging, but without forcing these systems as* dependencies upon your users.* * <p>* For more information on MDC please see the <a* href="http://logback.qos.ch/manual/mdc.html">chapter on MDC</a> in the* logback manual.* * <p>* Please note that all methods in this class are static.* * @author Ceki G&uuml;lc&uuml;* @since 1.4.1*/
public class MDC {

/*** Basic MDC implementation, which can be used with logging systems that lack* out-of-the-box MDC support.** This code was initially inspired by  logback's LogbackMDCAdapter. However,* LogbackMDCAdapter has evolved and is now considerably more sophisticated.** @author Ceki Gulcu* @author Maarten Bosteels* @author Lukasz Cwik* * @since 1.5.0*/
public class BasicMDCAdapter implements MDCAdapter {private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<Map<String, String>>() {@Overrideprotected Map<String, String> childValue(Map<String, String> parentValue) {if (parentValue == null) {return null;}return new HashMap<String, String>(parentValue);}};/*** Put a context value (the <code>val</code> parameter) as identified with* the <code>key</code> parameter into the current thread's context map.* Note that contrary to log4j, the <code>val</code> parameter can be null.** <p>* If the current thread does not have a context map it is created as a side* effect of this call.** @throws IllegalArgumentException*                 in case the "key" parameter is null*/public void put(String key, String val) {if (key == null) {throw new IllegalArgumentException("key cannot be null");}Map<String, String> map = inheritableThreadLocal.get();if (map == null) {map = new HashMap<String, String>();inheritableThreadLocal.set(map);}map.put(key, val);}
/*** This class extends <tt>ThreadLocal</tt> to provide inheritance of values* from parent thread to child thread: when a child thread is created, the* child receives initial values for all inheritable thread-local variables* for which the parent has values.  Normally the child's values will be* identical to the parent's; however, the child's value can be made an* arbitrary function of the parent's by overriding the <tt>childValue</tt>* method in this class.** <p>Inheritable thread-local variables are used in preference to* ordinary thread-local variables when the per-thread-attribute being* maintained in the variable (e.g., User ID, Transaction ID) must be* automatically transmitted to any child threads that are created.** @author  Josh Bloch and Doug Lea* @see     ThreadLocal* @since   1.2*/public class InheritableThreadLocal<T> extends ThreadLocal<T> {/*** Computes the child's initial value for this inheritable thread-local* variable as a function of the parent's value at the time the child* thread is created.  This method is called from within the parent* thread before the child is started.* <p>* This method merely returns its input argument, and should be overridden* if a different behavior is desired.** @param parentValue the parent thread's value* @return the child thread's initial value*/protected T childValue(T parentValue) {return parentValue;}

1 修改日志文件

只需要在日志配置文件的pattern中中添加一个{key},在请求方法入口设置一个key=某字符串,logger日志就能输出此字符串。logger的所有日志方法不需要做任何改动。如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><layout class="ch.qos.logback.classic.PatternLayout"><pattern>%d{ISO8601} [%thread] [%-5level] [%-10X{tracing_id}] %logger - %msg%n</pattern></layout></appender><!-- 文件输出日志 (文件大小策略进行文件输出,超过指定大小对文件备份)--><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>/data/rec_loader/logs/rec_loader.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>/data/rec_loader/logs/rec_loader.log.%d{yyyy-MM-dd}.%d{hh-mm-ss}</fileNamePattern><maxHistory>7</maxHistory><totalSizeCap>100GB</totalSizeCap></rollingPolicy><layout class="ch.qos.logback.classic.PatternLayout"><!--** 改动在此处 tracing_id**--><Pattern>%d{ISO8601} [%thread] [%-5level] [%-10X{tracing_id}] %logger - %msg%n</Pattern></layout></appender><root level="info"><appender-ref ref="STDOUT"/><appender-ref ref="FILE"/></root></configuration>

2 增加拦截器(跨系统之间可用,通过增加header字段)

import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;/* ** *    Starry Starry Night* *          __   __* *          \/---\/* *           ). .(* *          ( (") )* *           )   (* *          /     \* *         (       )``* *        ( \ /-\ / )* *         w'W   W'w* ** author   杨春炼* email    qdcccc@gmail.com* date     2018-08-18**/
@Slf4j
public class ResponseMetricsInterceptor extends HandlerInterceptorAdapter {private static final String TRACING_ID = "tracing_id";private static final String RESPONSE_TIME = "response_time";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {String tracingId;//this judgement used for webs contextif (StringUtils.isBlank(request.getHeader(TRACING_ID))) {tracingId = RandomStringUtils.randomAlphanumeric(10);} else {tracingId = request.getHeader(TRACING_ID);}response.setHeader(TRACING_ID, tracingId);//add a start time in request attributerequest.setAttribute(RESPONSE_TIME, System.currentTimeMillis());MDC.put(TRACING_ID, tracingId);log.info("tracing.start.url={}", request.getServletPath());return super.preHandle(request, response, handler);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {try {Object attribute = request.getAttribute(RESPONSE_TIME);Long timeStart = Long.valueOf(Objects.toString(attribute, "0"));long responseTime = System.currentTimeMillis() - timeStart;log.info("tracing.end.url={}, 消耗时间:{}ms", request.getServletPath(), responseTime);} catch (Exception ignored) {} finally {//prevent memory leakMDC.remove(TRACING_ID);MDC.clear();}super.afterCompletion(request, response, handler, ex);}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;/* ** *    Starry Starry Night* *          __   __* *          \/---\/* *           ). .(* *          ( (") )* *           )   (* *          /     \* *         (       )``* *        ( \ /-\ / )* *         w'W   W'w* ** author   杨春炼* email    qdcccc@gmail.com* date     2018-08-18**/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new ResponseMetricsInterceptor()).addPathPatterns("/**");}
}

Effect

向自己的项目发起一次http请求

结果如下图(顺便打印了该次http请求的总耗时)

更专业的分布式链路追踪中间件?

可以自行搜索 SOFATracer,比较重,觉得一般企业没必要应用。

转载于:https://my.oschina.net/yangchunlian/blog/1929982

利用Slf4j的MDC跟踪方法调用链相关推荐

  1. 执行链java_java 方法调用链

    缘起 对所有的调用做入参拦截,为了更便于查阅,希望可以得到方法的签名( MethodSignature ). 一.AOP 此时,想获取拦截的方法名称较为简单. @Around("pointc ...

  2. 利用OpenCV实现——目标跟踪方法(一)

    目标跟踪一直是热门话题,对于实时帧跟踪首先要判断有无运动目标. 最简单的目标跟踪是首先处理视频(摄像头)帧,然后灰度化,二值化,包括滤波处理(Gaussian),模糊处理(blur),二值化处理等等得 ...

  3. android 跟踪方法调用,如何连续跟踪Android手机的位置?

    我需要编写一个应用程序,每隔5分钟确定一次移动电话的当前位置(使用所有免费的可用位置提供商)并将其发送到服务器. 如果某个位置提供程序在应用程序启动时不起作用,但稍后可用,则应用程序也应处理其位置数据 ...

  4. java 记录方法调用链

    缘由是服务器总是报redisPool中连接不够 但是不知道是在哪里获取线时忘关了 由于调用处太多不方变个个看 于是就添加日志记录下是哪里调用的 getJedis这个方法 StackTraceEleme ...

  5. Java的JVM运行时栈结构和方法调用详解

    详细介绍了Java 的JVM的运行时栈结构,以及和方法调用详解,包括解析调用和分派调用. JVM对于方法的执行是基于栈的,方法调用--入栈,方法调用完毕--出栈,了解JVM的运行时栈结构,有助于我们更 ...

  6. IDEA 查看类及属性的调用链

    IDEA 查看类属性的调用链 1.Find Usages 1.1. 查看类方法属性被调用链 IDEA提供的 Find Usages是一个查看调用链非常好用的方法,它的快捷键是Alt+F7 Find U ...

  7. IDEA完整的调用链显示

    IDEA完整的调用链显示 idea时序图显示完整方法调用链 安装插件 - SequenceDiagram 重启之后,右击方法就会显示 SequenceDiagram Diagram- , 确定之后显示 ...

  8. php 利用debug_backtrace方法跟踪代码调用

    在开发过程中,例如要修改别人开发的代码或调试出问题的代码,需要对代码流程一步步去跟踪,找到出问题的地方进行修改.如果有一个方法可以获取到某段代码是被哪个方法调用,并能一直回溯到最开始调用的地方(包括调 ...

  9. 调用链与日志的关联式跟踪查询

    调用链与日志的关联式跟踪查询 长假过完,相信无缘支付宝中国锦鲤的你一定已经回来工作了.虽然轻轻松松与全球免单大礼包失之交臂,但不要丧气,小编悄悄为你准备了一份秘籍,助你在2018年最后不到三个月的时间 ...

最新文章

  1. java.lang.classnotfoundexception解决方法
  2. java https soap,Java Https Soap Server(Tomcat-Axis2)
  3. 动画学信奥 漫画学算法 CSP-J入门级 (一)、计算机基础与编程环境(依据「NOI大纲」)
  4. “1天一朵云”,这是如何做到的?
  5. 从偶然的机会发现一个mysql特性到wooyun waf绕过题
  6. [Python] * 和 ** 的用法
  7. 搅动PC市场风云,荣耀何以成为破局者?
  8. ida 反编译 php,飘云阁安全论坛在IDA pro中的这样一段反编译代码能够改写成易语言吗 - Powered by Discuz!...
  9. 数组和集合有什么区别
  10. 2022 Google翻译修复工具 V1.3 【谷歌浏览器无法翻译网页问题解决】
  11. 从来不流鼻涕php没有毛病,睡觉的时候为什么不会流鼻涕?
  12. 站在巨人的肩膀上-听课感想
  13. b站电脑测试用什么软件,使用BiliBili访问诊断工具检测哔哩哔哩网络的方法
  14. 化工原理计算机辅助设计,化工原理课程设计心得三篇
  15. 2019高考(高中)数学必背重点公式大全
  16. Java集合(一)Java集合及其关系
  17. 非标准分布随机数生成 - 逆变换ITM与舍选法Rejection
  18. 图像分割-种子区域生长
  19. AES-GCM算法 Java与Python互相加解密
  20. AE cc 2018 详细安装教程

热门文章

  1. 太卷了,华为某领导说招外包只要985!
  2. 中文预训练模型研究进展
  3. 单片机数码管6位时钟c语言,单片机6位数码管时钟
  4. 快递取件码生成软件_一种分布式的取件码生成方法技术
  5. Opencv分类器的训练(内含文件批量改名工具及负样本图包下载地址)
  6. Android am与pm命令详解
  7. 承受自律的苦,不要承受自责的悔
  8. 如何将图片转化为base64编码格式,在css中显示
  9. npx:调用项目内部安装的模块
  10. 首席新媒体黎想教程:如何从零开始策划微博运营?