一.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. pyinstaller打包exe运行失败

    使用Pyinstaller来打包自己开发的软件时遇到的几个问题及解决方法.工具主要功能是数据分析,使用机器学习算法完成数据训练和预测功能.主要用到了两个学习库keras和sklearn,所以说在打包时 ...

  2. dubbo实战之三:使用Zookeeper注册中心

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. MYSQL的replace into

    replace into t(id, update_time) values(1, now()); 或 replace into t(id, update_time) select 1, now(); ...

  4. Learn Python the Hard Way,ex37-1

    本练习为复习python的符号和关键字 关键字有: #and or False True print(1==0 and 2==0, 1==0 or 2==0) print(False) print(T ...

  5. 卷积神经网络学习笔记——轻量化网络MobileNet系列(V1,V2,V3)

    完整代码及其数据,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/DeepLearningNote 这里结合网络的资料和Mo ...

  6. FHRP - 网关冗余协议

    通常情况下,在终端设备进入网络前,都会有一个 Router 充当网络,作为第一跳的网络地址.但假设路由器发生故障,此时终端设备就无法再接入互联网. 为了防止这样的问题,一般会再加入一台路由器充当备份. ...

  7. CSS轮廓和圆角

    1 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset=" ...

  8. P1303_A*B Problem(JAVA语言)

    思路:BigInteger 三杀! //四行搞定 题目描述 求两数的积. 输入输出格式 输入格式: 两行,两个数. 输出格式: 积 输入输出样例 输入样例#1: 复制 1 2 输出样例#1: 复制 2 ...

  9. 2018年11月16日SQL Server实验内容(触发器实验)

    --注意:先把studentmanager数据库中的所有表用select into命令复制一份, --然后用复制后新表完成下面的实验,同时,对每个触发器都要进行验证. select *into dep ...

  10. java例题_43 求0—7所能组成的奇数个数

    1 /*43 [程序 43 求奇数个数] 2 题目:求 0-7 所能组成的奇数个数. 3 */ 4 5 /*分析 6 * 1.0不能作最高位且最低位只能是1,3,5,7; 7 * 2.没有限定是几位数 ...