NDC和MDC的区别

Java中使用的日志的实现框架有很多种,常用的log4j和logback以及java.util.logging,而log4j是apache实现的一个开源日志组件(Wrapped implementations),logback是slf4j的原生实现(Native implementations)。需要说明的slf4j是Java简单日志的门面(The Simple Logging Facade for Java),如果使用slf4j日志门面,必须要用到slf4j-api,而logback是直接实现的,所以不需要其他额外的转换以及转换带来的消耗,而slf4j要调用log4j的实现,就需要一个适配层,将log4j的实现适配到slf4j-api可调用的模式。

说完基本的日志框架的区别之后,我们再看看NDC和MDC。

不管是log4j还是logback,打印的日志要能体现出问题的所在,能够快速的定位到问题的症结,就必须携带上下文信息(context information),那么其存储该信息的两个重要的类就是NDC(Nested Diagnostic Context)和MDC(Mapped Diagnositc Context)。

NDC采用栈的机制存储上下文,线程独立的,子线程会从父线程拷贝上下文。其调用方法如下:

1.开始调用

NDC.push(message);

2.删除栈顶消息

NDC.pop();

3.清除全部的消息,必须在线程退出前显示的调用,否则会导致内存溢出。

NDC.remove();

4.输出模板,注意是小写的[%x]

log4j.appender.stdout.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ssS}] [%x] : %m%n

MDC采用Map的方式存储上下文,线程独立的,子线程会从父线程拷贝上下文。其调用方法如下:

1.保存信息到上下文

MDC.put(key, value);

2.从上下文获取设置的信息

MDC.get(key);

3.清楚上下文中指定的key的信息

MDC.remove(key);

4.清除所有

clear()

5.输出模板,注意是大写[%X{key}]

log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m - %X{key}%n

最后需要注意的是:

  • Use %X Map中全部数据
  • Use %X{key} 指定输出Map中的key的值
  • Use %x 输出Stack中的全部内容

MDC的使用例子

  1. //MdcUtils.java
  2. // import ...MdcConstants // 这个就是定义一个常量的类,定义了SERVER、SESSION_ID等
  3. import org.apache.commons.lang3.StringUtils;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.slf4j.MDC;
  7. public class MdcUtils {
  8. private final static Logger logger = LoggerFactory.getLogger(MdcUtils.class);
  9. private static void put(String key, Object value) {
  10. if (value != null) {
  11. String val = value.toString();
  12. if (StringUtils.isNoneBlank(key, val)) {
  13. MDC.put(key, val);
  14. }
  15. }
  16. }
  17. public static String getServer() {
  18. return MDC.get(MdcConstants.SERVER);
  19. }
  20. public static void putServer(String server) {
  21. put(MdcConstants.SERVER, server);
  22. }
  23. public static String getSessionId() {
  24. return MDC.get(MdcConstants.SESSION_ID);
  25. }
  26. public static void putSessionId(String sId) {
  27. put(MdcConstants.SESSION_ID, sId);
  28. }
  29. public static void clear() {
  30. MDC.clear();
  31. logger.debug("mdc clear done.");
  32. }
  33. }

上述工具类中MdcConstants是定义一个常量的类,定义了SERVER、SESSION_ID等,put方法就是调用了slf4j的MDC的put方法。其他方法类比。

看看使用该工具类的具体方式:

  1. // MdcClearInterceptor.java
  2. import ...MdcUtils; // 导入上面的工具类
  3. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. public class MdcClearInterceptor extends HandlerInterceptorAdapter {
  7. @Override
  8. public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
  9. throws Exception {
  10. MdcUtils.clear();
  11. }
  12. }

在该拦截器中,重写了afterConcurrentHandlingStarted方法,该方法执行了工具类的clear方法,也就是通过调用slf4j的clear方法清除了本次会话上下文的日志信息。为什么要放在afterConcurrentHandlingStarted方法中呢?这恐怕得从springmvc的拦截器的实现说起。

springmvc的拦截HandlerInterceptor接口定义了三个方法(代码如下),具体说明在方法注释上:

  1. public interface HandlerInterceptor {
  2. //在控制器方法调用前执行
  3. //返回值为是否中断,true,表示继续执行(下一个拦截器或处理器)
  4. //false则会中断后续的所有操作,所以我们需要使用response来响应请求
  5. boolean preHandle(
  6. HttpServletRequest request, HttpServletResponse response,
  7. Object handler)
  8. throws Exception;
  9. //在控制器方法调用后,解析视图前调用,我们可以对视图和模型做进一步渲染或修改
  10. void postHandle(
  11. HttpServletRequest request, HttpServletResponse response,
  12. Object handler, ModelAndView modelAndView)
  13. throws Exception;
  14. //整个请求完成,即视图渲染结束后调用,这个时候可以做些资源清理工作,或日志记录等
  15. void afterCompletion(
  16. HttpServletRequest request, HttpServletResponse response,
  17. Object handler, Exception ex)
  18. throws Exception;
  19. }

很多时候,我们只需要上面这3个方法就够了,因为我们只需要继承HandlerInterceptorAdapter就可以了,HandlerInterceptorAdapter间接实现了HandlerInterceptor接口,并为HandlerInterceptor的三个方法做了空实现,因而更方便我们定制化自己的实现。

相对于HandlerInterceptor,HandlerInterceptorAdapter多了一个实现方法afterConcurrentHandlingStarted(),它来自HandlerInterceptorAdapter的直接实现类AsyncHandlerInterceptor,AsyncHandlerInterceptor接口直接继承了HandlerInterceptor,并新添了afterConcurrentHandlingStarted()方法用于处理异步请求,当Controller中有异步请求方法的时候会触发该方法时,异步请求先支持preHandle、然后执行afterConcurrentHandlingStarted。异步线程完成之后执行preHandle、postHandle、afterCompletion。

那至于这些可能用到的日志字段从什么地方赋值呢,也就是什么地方调用MDCUtils.put()方法呢?一般我们都会实现一个RequestHandlerInterceptor,在preHandler方法中处理日志字段即可。如下:

  1. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  2. throws Exception {
  3. if (DispatcherType.ASYNC.equals(request.getDispatcherType())) {
  4. return true;
  5. }
  6. // 开始保存信息到日志上下文
  7. MdcUtils.putServer(request.getServerName());
  8. String sId = request.getHeader(HeaderConstants.SESSION_ID);
  9. MdcUtils.putSessionId(sId);
  10. if (sessionWhiteList.contains(request.getPathInfo())) {
  11. return true;
  12. }
  13. // TODO 处理其他业务
  14. }

还没完,就目前看,我们已经有两个自定义的拦截器实现了。怎么使用,才能将日志根据我们的意愿正确的打印呢?必然,拦截器是有顺序的,如果配置了多个拦截器,会形成一条拦截器链,执行顺序类似于AOP,前置拦截先定义的先执行,后置拦截和完结拦截(afterCompletion)后注册的后执行。

Soga,我们需要清除上次请求的一些无用的信息,再次将我们的信息写入到MDC中(拦截器的配置在DispatcherServlet中),由于afterConcurrentHandlingStarted()方法需要异步请求触发,因此我们需要在web.xml的DispatchServlet配置增加<async-supported>true</async-supported>配置。

  1. <mvc:interceptors>
  2. <bean class="com.xxx.handler.MdcClearInterceptor"/>
  3. <bean class="com.xxx.handler.RequestContextInterceptor"/>
  4. </mvc:interceptors>

或者这样:

  1. <mvc:interceptors>
  2. <!-- 前置拦截器 -->
  3. <mvc:interceptor>
  4. <!-- 这里面还以增加一些拦截条件-->
  5. <!--<mvc:exclude-mapping path="/user/logout"/>-->
  6. <!-- 用户退出登录请求 -->
  7. <!-- <mvc:exclude-mapping path="/home/"/> -->
  8. <!--在home中定义了无须登录的方法请求,直接过滤拦截-->
  9. <!-- <mvc:mapping path="/**"/>-->
  10. <bean class="com.xxx.handler.MdcClearInterceptor"/>
  11. </mvc:interceptor>
  12. <!-- 后置拦截器 -->
  13. <mvc:interceptor>
  14. <bean class="com.xxx.handler.RequestContextInterceptor"/>
  15. </mvc:interceptor>
  16. </mvc:interceptors>

该文首发《虚怀若谷》个人博客,转载前请务必署名,转载请标明出处。

古之善为道者,微妙玄通,深不可识。夫唯不可识,故强为之容:

豫兮若冬涉川,犹兮若畏四邻,俨兮其若客,涣兮若冰之释,敦兮其若朴,旷兮其若谷,混兮其若浊。

孰能浊以静之徐清?孰能安以动之徐生?

保此道不欲盈。夫唯不盈,故能敝而新成。

请关注我的微信公众号:下雨就像弹钢琴,Thanks♪(・ω・)ノ

Java日志Log4j或者Logback的NDC和MDC功能的更多相关文章

  1. Java日志框架:logback详解

    为什么使用logback 记得前几年工作的时候,公司使用的日志框架还是log4j,大约从16年中到现在,不管是我参与的别人已经搭建好的项目还是我自己主导的项目,日志框架基本都换成了logback,总结 ...

  2. 【转】Java日志框架:logback详解

    为什么使用logback 记得前几年工作的时候,公司使用的日志框架还是log4j,大约从16年中到现在,不管是我参与的别人已经搭建好的项目还是我自己主导的项目,日志框架基本都换成了logback,总结 ...

  3. Java日志介绍(3)-Logback

    Logback 继承自Log4j,它建立在有十年工业经验的日志系统之上.它比其它所有的日志系统更快并且更小,包含了许多独特并且有用的特性. 1.配置 1.1.加载配置 Logback能够在初始化期间自 ...

  4. java日志框架之logback(一)——logback工程简介

    Logback工程 致力于成为log4j工程的继承者 Logback的架构足够泛型化,故能够应用于许多不同的环境.当前,logback划分为三个组件: logback-core logback-cla ...

  5. (网页)Java日志记录框架Logback配置详解(企业级应用解决方案)(转)

    转自CSDN: 前言 Logback是现在比较流行的一个日志记录框架,它的配置比较简单学习成本相对较低,所以刚刚接触该框架的朋友不要畏惧,多花点耐心很快就能灵活应用了.本篇博文不会具体介绍Logbac ...

  6. Log4j,Log4j2,logback,slf4j日志学习

    日志学习笔记 Log4j Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.数据库等:我们也可以控制每一条日志的输出格式:通过定义每一条 ...

  7. Log4j,Log4j2,logback,slf4j日志学习(转)

    日志学习笔记Log4jLog4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.数据库等:我们也可以控制每一条日志的输出格式:通过定义每一条日志 ...

  8. Java日志 #01# 入门

    很多人在学习完一个东西之后就会忘掉自己作为初学者时的体验.. 例如刚接触git的时候自己也是一头雾水,然后别人问起来,老是会说:xxxx#!@#,就是这么回事儿,有什么不好懂的. 其实从不懂到懂,再到 ...

  9. java日志组件介绍(common-logging,log4j,slf4j,logback )

    转自:http://www.blogjava.net/daiyongzhi/archive/2014/04/13/412364.html common-logging是apache提供的一个通用的日志 ...

随机推荐

  1. 性能测试瓶颈判断(LR&Windowns)

    性能测试瓶颈判断(LR&Windowns) 一.判断CPU瓶颈(Processor) 1, %processor time 如果该值持续超过95%,表明瓶颈是CPU.可以考虑增加一个处理器或换 ...

  2. MybatisPlus报错Invalid bound statement (not found)的解决方案

    今天使用MybatisPlus,测试时报错Invalid bound statement (not found) 使用自定义的mapper接口中的方法可以执行,而调用MybatisPlus中baseM ...

  3. Spring MVC-从零开始-view-ViewResolver

    主要ViewResolver简介 InternalResourceViewResolver 将逻辑视图名解析为一个路径 BeanNameViewResolver 将逻辑视图名解析为bean的name属 ...

  4. centos6.9实时查看tomcat运行日志

    1.切换到tomcat的logs目录下 cd /usr/local/apache-tomcat-/logs 2.执行命令,查看日志 tail -f catalina.out 3.退出 Ctrl+c

  5. netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放

    ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机. 内存分配分两个阶段: 第一阶段,初始化时分配内存.第二阶段: 内存不够用时分配新的内存.ByteBuf抽象层没有定义 ...

  6. IOS上传到App Store出现证书未安装问题

    今天在提交自己的APP到苹果商店去审核的时候,编译成功后.upload过程中,提示 XXX Select the certificates you wish to include in this pr ...

  7. TF-IDF算法——原理及实现

    TF-IDF算法是一种用于信息检索与数据挖掘的常用加权技术.TF的意思是词频(Term - frequency),IDF的意思是逆向文件频率(inverse Document frequency). ...

  8. 微人事 star 数超 10k,如何打造一个 star 数超 10k 的开源项目

    看了下,微人事(https://github.com/lenve/vhr)项目 star 数超 10k 啦,松哥第一个 star 数过万的开源项目就这样诞生了. 两年前差不多就是现在这个时候,松哥所在 ...

  9. GUI篇 tkinter (Label,Button)之一

    import tkinterfrom tkinter import * # tkinter._test() # 实例化一个窗口对象base = tkinter.Tk()# 修改窗口的标题base.wm ...

  10. Windows系统调用中的现场保存

    Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html Windows系统调用中的现场保存 我们之前介绍过三环进零环的步骤 ...