【日志追踪】(微服务应用和单体应用)-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 ...
随机推荐
- vuex中辅助函数的使用方法
mapState import { mapState } from 'vuex' export default { // ... computed:{ ...mapState({ // 箭头函数可使代 ...
- PVE连接Wi-Fi
连接wifi参考文章: https://lzxz1234.cn/archives/417 services看不全信息 connmanctl> services 之后信息太多,看不到上面的怎么办? ...
- kubernetes Pod亲和性
三种调度粘性,主要根据官方文档说明: NodeSelector(定向调度).NodeAffinity(Node亲和性).PodAffinity(Pod亲和性). 1. nodeSelecto ...
- 大家最常用的编程论坛是哪个呢,欢迎评论!!掘金16 juejin 简书41 jianshu 博客85 csdn137 csdn
软件编程交流论坛 掘金 16 juejin 简书 41 jianshu 博客 85 cnblogs csdn 137 csdn stackoverflow 0 思否 github 大家最常用的 ...
- Mybatis系列全解(七):全息视角看Dao层两种实现方式之传统方式与代理方式
封面:洛小汐 作者:潘潘 一直以来 他们都说为了生活 便追求所谓成功 顶级薪水.名牌包包 还有学区房 · 不过 总有人丢了生活 仍一无所获 · 我比较随遇而安 有些事懒得明白 平日里问心无愧 感兴趣的 ...
- JavaWeb随笔整理
JavaWeb随笔整理 为方便阅读,故整理了相关学习笔记 前端相关 HTML CSS JavaScript BootStrap 数据库相关 MySQL基础 MySQL表的约束和数据库设计 MySQL多 ...
- 无限可能 | Flutter 2 重点更新一览
我们非常高兴在本周发布了 Flutter 2.自 Flutter 1.0 发布至今已有两年多的时间,在如此短暂的时间内,我们解决了 24,541 个 issue,合并了来自 765 个贡献者的 17, ...
- 如何实现一个简易版的 Spring - 如何实现 @Autowired 注解
前言 本文是 如何实现一个简易版的 Spring 系列第四篇,在 上篇 介绍了 @Component 注解的实现,这篇再来看看在使用 Spring 框架开发中常用的 @Autowired 注入要如何实 ...
- 【译】Rust宏:教程与示例(二)
原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...
- Java8的新特性--并行流与串行流
目录 写在前面 Fork/Join框架 Fork/Join框架与传统线程池的区别 传统的线程池 Fork/Join框架 Fork/Join框架的使用 Java8中的并行流 写在前面 我们都知道,在开发 ...