一.MDC介绍

  MDC(Mapped Diagnostic Contexts)映射诊断上下文,该特征是logback提供的一种方便在多线程条件下的记录日志的功能,

  某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很麻烦。
  MDC正是用于解决上述问题的,MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

  通俗点来说:

    MDC为每一个请求创建一条独立的可识别的的日志,方便追踪和查看,特别是在分布式系统中,分布式日志追踪往往对于问题诊断是特别重要的;

二.MDC在单体应用中的案例

  环境:JDK8+Springboot2.x(已经默认集成了logback日志框架)

  过滤器或者拦截器中设置MDC日志,为了尽量每个请求尽量唯一,这里使用UUID作为ID

/**
* @author: Gabriel
* @date: 2020/1/28 21:14
* @description 过滤器
*/
@Slf4j
@Component
@WebFilter(urlPatterns = "/**",filterName = "tlFilter")
public class ThreadLocalFilter implements Filter { @Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("过滤器初始化");
} @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//添加MDC日志
String logtrackId = UUID.randomUUID(true).toString();
MDC.put("logTrackId",logtrackId);
log.info("过滤器执行中");
try {
filterChain.doFilter(servletRequest, servletResponse);
}finally{
//移除MDC日志
MDC.remove(logtrackId);
}
log.info("过滤器执行完成");
} @Override
public void destroy() {
log.info("过滤器销毁"); }
}

  logback.xml配置文件

  日志输出需要添加  %X{logTrackId} 其中logTrackId为MDC的key值

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 注意:如果部署在在linux系统中 这里可以文件路径替换为linux文件路径即可-->
<property name="LOG_HOME" value="src/main/resources/log.properties"/>
<!--<property name="LOG_HOME" value="/Users/weibinbin/logs/carton"/>--> <!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%c-%L][%X{logTrackId}] %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
</appender> <!-- 按照每天生成info日志文件 -->
<appender name="infoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%c-%L][%X{logTrackId}] %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/info/info.%d{yyyyMMdd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>500 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>365</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
<prudent>false</prudent>
</appender> <!-- 按照每天生成error日志文件 -->
<appender name="errorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%c-%L][%X{logTrackId}] %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/error/error.%d{yyyyMMdd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>500 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>365</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<prudent>false</prudent>
</appender> <!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="infoLog"/>
<appender-ref ref="errorLog"/>
</root> </configuration>

  测试

  

三.MDC在分布式系统中的应用

  这里以SpringCloud微服务框架来说明 日志请求链路

  1.网关服务(以Zuul为例)在预处理过滤器中,在请求进行路由之前设置MDC 日志ID,传递到下游的应用服务

/**
* @Author: Gabriel
* @Date: 2019/6/30 12:37
* @Version 1.0
* @Discription Zuul API网关过滤器
*/
@Slf4j
@Component
public class AccessFilter extends ZuulFilter { /**
*
过滤器的4中类型
pre:请求在被路由之前执行
routing:在路由请求时调用
post:在routing和errror过滤器之后调用
error:处理请求时发生错误调用 */
@Override
public String filterType() {
return "pre";
} /**
* 过滤器的顺序
* 数字越小,越被优先被执行
* @return
*/
@Override
public int filterOrder() {
return 0;
} /**
* 过滤器是否被执行
* 返回true / false
* 实际运用中我们可以利用该方法来指定过滤器的有效范围
* @return
*/
@Override
public boolean shouldFilter() {
return true;
} /**
* 过滤器执行的具体逻辑
* 这里的例子是校验头信息中是否有accessToken 这个信息
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
String logTrackId = UUID.randomUUID().toString(); MDC.put("logTrackId", logTrackId); //获取请求上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest(); //添加请求头
ctx.addZuulRequestHeader("logTrackId", logTrackId); //打印日志
log.info("send {} request to {}", request.getMethod(),request.getRequestURL().toString()); // ...省略相关业务代码,主要演示日志链路
return null;
}
}

  2.应用服务 拦截器中收到网关请求头传递过来的MDC 日志ID,进行PUT到本次请求关联的先成功中,请求执行完成后,销毁该日志ID

/**
* @author: Gabriel
* @date: 2020/2/9 1:02
* @description 拦截器
*/
@Slf4j
@Component
public class URIInterceptor implements HandlerInterceptor { @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 统一日志标记
String logTrackId = request.getHeader("logTrackId");
if (StringUtils.isEmpty(logTrackId)) {
logTrackId = String.valueOf(System.currentTimeMillis());
}
MDC.put("logTrackId", logTrackId); request.setCharacterEncoding("UTF-8");
request.setAttribute("interfaceStartTime", System.currentTimeMillis()); response.setCharacterEncoding("UTF-8");
response.setHeader("content-type", "text/html;charset=UTF-8"); String uri = request.getRequestURI();
String method = request.getMethod();
String userAgent = request.getHeader("User-Agent");
log.info(String.format("##########【%s】,%s,%s", uri, method, userAgent)); return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String uri = request.getRequestURI();
String method = request.getMethod();
long interfaceStartTime = (Long) request.getAttribute("interfaceStartTime");
long interfaceEndTime = System.currentTimeMillis(); long times = interfaceEndTime - interfaceStartTime;
if (times > 1000) {
log.info(String.format("==========【%s】,%s,耗时:%s,请检查是否异常", uri, method, times));
} else {
log.info(String.format("==========【%s】,%s,耗时:%s", uri, method, times));
}
MDC.remove("logTrackId");
}
}

  拦截器注册

/**
* @author: Gabriel
* @date: 2020/2/9 1:08
* @description 拦截器注册
*/
@Configuration
@Order
public class MyWebMvcConfig implements WebMvcConfigurer { @Autowired
private URIInterceptor uriInterceptor; @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(uriInterceptor).addPathPatterns("/**");
}
}
Gabriel

【日志追踪】(微服务应用和单体应用)-logback中的MDC机制的更多相关文章

  1. .NET Core微服务之路:不断更新中的目录 (v0.43)

    原文:.NET Core微服务之路:不断更新中的目录 (v0.43) 微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑, ...

  2. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  3. .NET Core微服务之基于Polly+AspectCore实现熔断与降级机制

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.熔断.降级与AOP 1.1 啥是熔断? 在广义的解释中,熔断主要是指为控制股票.期货或其他金融衍生产品的交易风险,为其单日价格波动幅度 ...

  4. 基于微服务架构、运行于容器中的.NET Core示例应用eShopOnContainers

    eShopOnContainers 是 <.NET Microservices – Architecture for Containerized .NET Applications>这本微 ...

  5. Spring cloud微服务安全实战-3-1 API安全 常见的安全机制

    不考虑微服务这种复杂的环境下,只是写一个简单的api的时候,如何来保证api的安全. 什么是API

  6. JAVA微服务应用(1)--SpringBoot中的REST API调用(学习笔记)

    好长时间没有写学习小结了,最近宁正好看了小马哥的微服务系列之<Spring Boot>系列,颇有收获,并且公司也布置一个课题就是关于Spring中的REST API调用.于是乎回归本行,再 ...

  7. 【Azure微服务 Service Fabric 】Service Fabric中应用开启外部访问端口及微服务之间通过反向代理端口访问问题

    问题描述 1) 当成功的在Service Fabric集群中部署了应用后,如何来访问呢?如果是一个Web服务,它的URL又是什么呢? 2) 当Service Fabric集群中,服务之间如需要相互访问 ...

  8. Spring Cloud微服务实践之路- Eureka Server 中的第一个异常

    EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER ...

  9. 微服务实施Spring Boot/Spring Cloud中踩过的坑(转)

    http://tietang.wang/2016/09/08/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%AE%9E%E6%9 ...

随机推荐

  1. 心脏滴血(CVE-2014-0160)检测与防御

    用Nmap检测 nmap -sV --script=ssl-heartbleed [your ip] -p 443 有心脏滴血漏洞的报告: ➜ ~ nmap -sV --script=ssl-hear ...

  2. AOP面试造火箭始末

    本文已整理致我的github地址,欢迎大家 star 支持一下 这是一个困扰我司由来已久的难题,Dubbo 了解过吧,对外提供的服务可能有多个方法,一般我们为了不给调用方埋坑,会在每个方法里把所有异常 ...

  3. 剑指 Offer 29. 顺时针打印矩阵 + 蛇形矩阵 + 模拟 + 思维题

    剑指 Offer 29. 顺时针打印矩阵 Offer_29 题目描述: 题解分析: 题目的初衷是将这道题当做一个简单题处理 这道题一开始想的太复杂了,其实可以参考迷宫广度优先搜索的过程,只不过在选定一 ...

  4. MySQL时间戳unix_timestamp

    函数:FROM_UNIXTIME作用:将MYSQL中以INT(11)存储的时间以"YYYY-MM-DD"格式来显示.语法:FROM_UNIXTIME(unix_timestamp, ...

  5. Java程序员必备后台前端框架--Layui【从入门到实战】(一)

    layui入门使用及图标的使用 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] [编程工具:IDEA] 下载Layui与文件分析 下载直接去官网下载即可 文件分析 下载完成后,解压会 ...

  6. 怎样将大批量文件进行循环分组(reduce)?

    背景   当有时候一个文件夹下有几万个几十万个文件时,我们的桌面终端打开这个文件夹可能会卡.或者将文件进行批量上传时,如果是在文件夹下全选,那么基本上浏览器就卡死了,当然也不能这样子操作滴~   题主 ...

  7. C语言中字符串详解

    C语言中字符串详解 字符串时是C语言中非常重要的部分,我们从字符串的性质和字符串的创建.程序中字符串的输入输出和字符串的操作来对字符串进行详细的解析. 什么是字符串? C语言本身没有内置的字符串类型, ...

  8. irace package -- 参数调优神器

    目录 1. irace 是什么 2. 安装 irace 3. irace 的运行机制 4. irace 的配置环境 4.1. parameters 4.2. target algorithm runn ...

  9. Shell 正则表达式详解

    Shell 正则表达式 什么是正则表达式? 正则表达式在每种语言中都会有,功能就是匹配符合你预期要求的字符串. 为什么要学正则表达式? 在企业工作中,我们每天做的linux运维工作中,时刻都会面对大量 ...

  10. k8s删除节点

    k8s 删除节点 线上环境 # ctl get nodes NAME STATUS ROLES AGE VERSION 10.0.0.123 Ready <none> 104d v1.20 ...