一.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. Python爬虫系统化学习(4)

    Python爬虫系统化学习(4) 在之前的学习过程中,我们学习了如何爬取页面,对页面进行解析并且提取我们需要的数据. 在通过解析得到我们想要的数据后,最重要的步骤就是保存数据. 一般的数据存储方式有两 ...

  2. 鹅厂二面,nginx回忆录

    前天二面鹅厂,面试官问出了"nginx你了解吗?"这样宽泛直白的句式,我一时抓不到重点,一时语噻. 下班想了一下,平时潜移默化用到不少nginx的能力,但在面试的时候没有吹成对应的 ...

  3. FreeBSD 将降低对 i386 架构的支持力度

    FreeBSD 开发团队宣布,从 FreeBSD 13.0 开始,对 i386 架构的支持级别将降级为 Tier 2,未来的 14.0 可能还将会在此基础上进一步降低对 i386 架构的支持.而对于 ...

  4. elf.h

    1 /* This file defines standard ELF types, structures, and macros. 2 Copyright (C) 1995-2019 Free So ...

  5. Github 1.9K Star的数据治理框架-Amundsen

    Amundsen的使命,整理有关数据的所有信息,并使其具有普遍适用性. 这是Amundsen官网的一句话,对于元数据的管理工作,复杂且繁琐.可用的工具很多各有千秋,数据血缘做的较好的应该是Apache ...

  6. 教你如何用Python模拟http请求(GET,POST)

    模拟http请求有什么用呢? 我们现在使用的所有需要使用网络的:软件 应用 app 网站里面的绝大部分功能都是通过http协议来工作的 什么是http协议? http协议,超文本传输协议(HTTP,H ...

  7. CVE-2020-1472 Zerologon

    CVE-2020-1472 Zerologon 漏洞简介 CVE-2020-1472是继MS17010之后一个比较好用的内网提权漏洞,影响Windows Server 2008R 2至Windows ...

  8. 【Visual Studio调教小记录】C++指针靠前靠后??

    本文地址:https://www.cnblogs.com/oberon-zjt0806/p/14631149.html 甜咸之争 经常写C++的基本上避不开使用指针,而且C++中指针类型的写法大体上有 ...

  9. [矩阵乘法]裴波拉契数列III

    [ 矩 阵 乘 法 ] 裴 波 拉 契 数 列 I I I [矩阵乘法]裴波拉契数列III [矩阵乘法]裴波拉契数列III Description 求数列f[n]=f[n-1]+f[n-2]+1的第N ...

  10. ECharts绘制折线图

    首先看实现好的页面 实现 首先引入echarts工具 // vue文件中引入echarts工具 let echarts = require('echarts/lib/echarts') require ...