【日志追踪】(微服务应用和单体应用)-logback中的MDC机制
一.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机制的更多相关文章
- .NET Core微服务之路:不断更新中的目录 (v0.43)
原文:.NET Core微服务之路:不断更新中的目录 (v0.43) 微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑, ...
- 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发
<ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...
- .NET Core微服务之基于Polly+AspectCore实现熔断与降级机制
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.熔断.降级与AOP 1.1 啥是熔断? 在广义的解释中,熔断主要是指为控制股票.期货或其他金融衍生产品的交易风险,为其单日价格波动幅度 ...
- 基于微服务架构、运行于容器中的.NET Core示例应用eShopOnContainers
eShopOnContainers 是 <.NET Microservices – Architecture for Containerized .NET Applications>这本微 ...
- Spring cloud微服务安全实战-3-1 API安全 常见的安全机制
不考虑微服务这种复杂的环境下,只是写一个简单的api的时候,如何来保证api的安全. 什么是API
- JAVA微服务应用(1)--SpringBoot中的REST API调用(学习笔记)
好长时间没有写学习小结了,最近宁正好看了小马哥的微服务系列之<Spring Boot>系列,颇有收获,并且公司也布置一个课题就是关于Spring中的REST API调用.于是乎回归本行,再 ...
- 【Azure微服务 Service Fabric 】Service Fabric中应用开启外部访问端口及微服务之间通过反向代理端口访问问题
问题描述 1) 当成功的在Service Fabric集群中部署了应用后,如何来访问呢?如果是一个Web服务,它的URL又是什么呢? 2) 当Service Fabric集群中,服务之间如需要相互访问 ...
- Spring Cloud微服务实践之路- Eureka Server 中的第一个异常
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER ...
- 微服务实施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 ...
随机推荐
- Flask:Jinjia模板
Jinja2是Python下一个被广泛应用的模版引擎,他的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能. 一.变量 1.1 手动传入的变量: 基本类型:{{ var }} 字 ...
- linux 设置DNS解决,不能ping 域名的问题
vi /etc/resolv.conf nameserver 114.114.114.114
- WPF 实战 - 翻页控件
1. 先上效果 <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> ...
- nginx安装&负载均衡配置&nginx反爬虫&nginx命令
Nginx安装 wget https://nginx.org/download/nginx-1.14.0.tar.gz tar -zxvf nginx-1.14.0.tar.gz cd nginx-1 ...
- 微服务架构Day16-SpringBoot之监控管理
监控管理使用步骤 通过引入spring-boot-starter-actuator,可以使用SpringBoot提供应用监控和管理的功能.可以通过HTTP,JMX,SSH协议来进行操作,自动得到审计, ...
- java网络通信不止UDP,TCP
预备知识 多线程 实现多线程 线程池 IO流 核心功能就是读和写 扩展功能对什么读写,怎么读写,如何优化读写 网络基础 IP IP规定网络上所有的设备都必须有一个独一无二的IP地址,就好比是邮件上都必 ...
- python基础学习之描述符和装饰器
描述符的了解: 描述符协议: python描述符是一个"绑定行为"的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有: __get__, __set__, 和__ ...
- ch1_5_2求无序序列中第k小的元素
import java.util.Arrays; import java.util.PriorityQueue; public class ch1_5_2求无序序列中第k小的元素 { public s ...
- Hashtable 渐渐被人们遗忘了,只有面试官还记得,感动
尽人事,听天命.博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 「CS-Wiki」Gitee ...
- Nginx记录用户请求Header到access log
为了统计和其它用途,经常有人需要自定义Nginx日志,把http请求中的某个字段记录到日志中,刚好在看lua+nginx的文章,第一想到的是用lua赋值来做,但是想想有点小恶心,于是Google了一番 ...