为什么会用到MDC?

本人使用Java两年时间,鉴于经验有限,在开发java后端代码过程中,为了定位问题,希望同一个线程的requestId可以从web层的日志一直输出到dao层,这样使用Linux命令 grep 的时候,可以把同一个线程的相关日志都检索出来,一开始我是这样实现的:

 在每次请求的时候,获取到请求的sessionId或者在web层生成一个sessionId,并将该sessionId透传到service层,dao层等,然后在每次log中将该log输出到日志中。

  

这个方案是完全可以实现上述功能的,但是代码侵入型强且代码冗余。为了实现从web端到底层的所有log输出同一个线程的sessionId,需要透传该id且显示打印到日志中。

于是我在思考,肯定是有更合适的方式解决此类问题,因此找到了MDC这个东东。

什么是MDC?

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。具体介绍参考 链接

自己的理解,MDC相当于一个全局的哈希表,配合AOP/Filter/Interceptor这类工具,在每个请求到来时,将对应的sessionId put到MDC中,同时在log输出中增加 %X{对应的key},会自动将每个线程相关的日志增加上sessionId这个字段,很方便。

关于MDC的底层实现原理,可参考这篇博客

使用Demo

下面以Interceptor为例,看下MDC的使用。

具体使用环境:
spring boot工程

构造一个拦截器

package xxx;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.slf4j.MDC;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /***
* 日志拦截器的Demo
*
* @author xxx
* @since 2018/12/06
*/
public class LogInterceptor extends HandlerInterceptorAdapter {
private final static String REQUEST_ID = "REQUEST_ID"; @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 删除requestId
MDC.remove(REQUEST_ID);
} @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestId = UUID.randomUUID().toString().replace("-", "");
// 在拦截器中将对应的requestId放到MDC中
MDC.put(REQUEST_ID, requestId);
return true;
} }

添加拦截器

package xxx;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /**
* 注册拦截器Demo
*
* @author xxx
* @since 2018/12/06
*/
@Configuration
@ComponentScan(basePackageClasses={WebMvcConfigDemo.class})
public class WebMvcConfigDemo extends WebMvcConfigurerAdapter { /** 把相关的拦截器注入为Bean */
@Bean
public HandlerInterceptor logInterceptor() {
return new LogInterceptor();
} /** 添加拦截器 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
} }

日志配置

<property name="CONSOLE_LOG_PATTERN" value="%red(%date{yyyy-MM-dd HH:mm:ss.SSS}) %X{REQUEST_ID}  %green([%16.16thread]) %highlight(%-5level) %boldGreen(%-40.40logger{39}) - %msg%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"></property>

  

测试demo

web层代码

@GetMapping(value = "/getById")
public Result getById(@RequestParam(name = "id") Long id) {
logger.info("==========test log requestId in controller==============");
return dataplusAuthorityTenantService.testMDCInService(id);
}

service层代码

@Override
public Result testMDCInService(Long id) {
logger.info("==========test log requestId in service==============");
return super.get(id);
}

测试输出

[31m2018-12-06 19:49:45.298[0;39m ed4b3d86377140af8f8f3f138dfc0f78  [32m[-nio-7001-exec-1][0;39m [34mINFO [0;39m [1;32mc.a.DataplusAuthorityTenantApiController[0;39m - ==========test log requestId in controller==============
[31m2018-12-06 19:49:45.316[0;39m ed4b3d86377140af8f8f3f138dfc0f78 [32m[-nio-7001-exec-1][0;39m [34mINFO [0;39m [1;32ms.d.i.DataplusAuthorityTenantServiceImpl[0;39m - ==========test log requestId in service==============

  

MDC带来的好处

1 应急

如果你的系统已经上线,突然日志中要增加一些额外信息,如果直接改代码,那你的代码都需要打补丁;如果直接扔在MDC中,直接配置在log中即可。

2 代码规范

在多线程环境中(现在几乎没有单线程),可直接通过拦截器/过滤器/AOP+log配置方式直接输出每个线程唯一的sessionId,不需要侵入到每行代码;

3 日志链路追踪

同2,尤其是喜欢在日志文件中使用grep命令的童鞋,一键grep;

参考文章

Slf4j的MDC初尝试的更多相关文章

  1. R语言爬虫初尝试-基于RVEST包学习

    注意:这文章是2月份写的,拉勾网早改版了,代码已经失效了,大家意思意思就好,主要看代码的使用方法吧.. 最近一直在用且有维护的另一个爬虫是KINDLE 特价书爬虫,blog地址见此: http://w ...

  2. SQLSERVER2012里的扩展事件初尝试(下)

    SQLSERVER2012里的扩展事件初尝试(下) SQLSERVER2012里的扩展事件初尝试(上) 我们继续文章扩展事件在Denali CTP3里的新UI(二)里的这个实验 脚本文件下载:http ...

  3. SQLSERVER2012里的扩展事件初尝试(上)

    SQLSERVER2012里的扩展事件初尝试(上) SQLSERVER2012里的扩展事件初尝试(下) 周未看了这两篇文章: 扩展事件在Denali CTP3里的新UI(一) 扩展事件在Denali ...

  4. 基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪

    原文链接:基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪 一.日志系统 1.日志框架 在每个系统应用中,我们都会使用日志系统,主要是为了记录必要的信息和方便排 ...

  5. 自定义spring boot starter 初尝试

    自定义简单spring boot starter 步骤 从几篇博客中了解了如何自定义starter,大概分为以下几个步骤: 1 引入相关依赖: 2 生成属性配置类: 3 生成核心服务类: 4 生成自动 ...

  6. codefirst初尝试

    Code First 约定 借助 CodeFirst,可通过使用 C# 或Visual Basic .NET 类来描述模型.模型的基本形状可通过约定来检测.约定是规则集,用于在使用 Code Firs ...

  7. 中文编程语言之Z语言初尝试: ZLOGO 4

    原文: https://zhuanlan.zhihu.com/p/31505895. 作者为本人. @TKT2016 开发的Z语言(ZLOGO是它的一个部分)是本人至今看到的唯一一个仍活跃开发的开源且 ...

  8. 2017-12-24 手机编程环境初尝试-用AIDE开发Android应用

    前不久才接触到纯粹用手机进行编程的开发者, 当时颇有孤陋寡闻之感, 因为之前听说过手机编程还是一些在线编程学习网站开发的学习环境, 没有想过真的有用它做实际开发的. 此文用AIDE免费版在自己的手机上 ...

  9. 2017-11-28 中文编程语言之Z语言初尝试: ZLOGO 4

    "中文编程"知乎专栏原文. 作者为本人. @TKT2016 开发的Z语言(ZLOGO是它的一个部分)是本人至今看到的唯一一个仍活跃开发的开源且比较完整的中文编程语言项目. 它的源码 ...

随机推荐

  1. ruoyi前后端分离版:添加新页面,不需登录也可访问

    添加QRcode页面代码 <template> <div class="register"> <el-form ref="registerF ...

  2. 【C++函数题目】重载求数组中最小值的函数

    题目来源:https://acm.ujn.edu.cn Time Limit: 1 Sec  Memory Limit: 128 MB Description 写一个函数名称为miniElement( ...

  3. tf.data(二) —— 并行化 tf.data.Dataset 生成器

    在处理大规模数据时,数据无法全部载入内存,我们通常用两个选项 使用tfrecords 使用 tf.data.Dataset.from_generator() tfrecords的并行化使用前文已经有过 ...

  4. 一文掌握GitHub Actions基本概念与配置

    CI/CD包含很多流程,如拉取代码.测试.构建打包.登录远程服务器.部署发布等等. 而Github Actions是GitHub推出的一个CI/CD工具,类似工具还有TravisCI.Jenkins等 ...

  5. django--ORM表的多对一关系

    多对一关系是什么 Django使用django.db.models.ForeignKey定义多对一关系. ForeignKey需要一个位置参数:与该模型关联的类  class Info(models. ...

  6. 6G显卡显存不足出现CUDA Error:out of memory解决办法

    ​ 从6月初开始,6G显存的显卡开始出现CUDA Error:out of memory的问题,这是因为dag文件一直在增加,不过要增加到6G还需要最少两年的时间. 现在出现问题的原因是1.内核太古老 ...

  7. 【python基础】第08回 流程控制 for循环

    本章内容概要 1.循环结构之 for 循环 本章内容详解 1.循环结构之for循环 1.1 语法结构 for 变量名 in 可迭代对象: #字符串 列表 字典 元组 for 循环的循环体代码 针对变量 ...

  8. Min_25 筛与杜教筛

    杜教筛 \(\) 是 \(\) 的前缀和,\(\), \(\) 同理. 假设 \( × = ℎ\) ,并且 \(, \) 易求出,\(\) 难求出. 那么 \[H () = \sum_{ \cdot ...

  9. Codeforces Round #780 (Div. 3)

    A. Vasya and Coins 题目链接 题目大意 Vasya 有 a 个 1-burle coin,有 b 个 2-burle coin,问他不能通过不找钱支付的价格的最小值. 思路 如果 a ...

  10. NTT 学习笔记

    引入 \(\tt NTT\) 和 \(\tt FFT\) 有什么不一样呢? 就是 \(\tt NTT\) 是可以用来取模的,而且没有复数带来的精度误差. 最最重要的是据说 \(\tt NTT\) 常数 ...