使用 Log4j 的 NDC/MDC 改进日志

摘要: 对系统关键路径记录日志是一种很常用的问题记录和分析方法,在复杂的 web 应用中,我们除了记录日志,还需要能够通过日志有效地识别请求的用户信息,平时我们的做法很多是在记录的时候将 session 或ip 地址等用户相关的信息,在日志打印时加进去,这么做并不是不可以,问题是麻烦。那么有没有更好的办法可以解决呢?有,怎么做?请看文章内容
一、问题产生

一般软件系统都会由简单到复杂,功能少的,简单的软件系统用用一般的日志输出也就能够解决问题了,但是这种软件系统真正能用的也太少了,实际场景中往往是软件么越做越复杂,越做越臃肿,且不说软件系统设计好坏,总归这是一个软件系统所要经历的,那么在系统中用日志的方式添加追踪点,帮助解决问题就成了比较常用的方法。而对于网站系统,通常除了知道这个日志是在哪个跟踪点产生的,还需要知道是由谁(往往是哪个用户,哪个 IP 等)产生的,这时就会想到往日志里塞入这类信息,但并不是每段代码都那么容易获取到用户的信息,特别是那些分层比较多,比较内层的方法,将一个对象穿透到应用的各个部分,想想都有点不好办。那么用户相关的信息每一个请求都是有办法获取到的,那有没有办法不侵入系统的内部而实现获取这些信息呢?原来 Log4j 早就为我们准备好了。

二、了解 NDC/MDC

关于 NDC/MDC 的详细介绍可以看这里:在 Web 应用中增加用户跟踪功能

简而言之:NDC是一种在同一个线程内将信息保存起来,提供日志输出时使用。MDC 与 NDC 相比,信息以 Map 方式保存,支付多个健值对。

使用方式上,NDC 通过在 PatternLayout 增加 %x 获取信息,而 MDC 通过 %X{key} 获取对应 key 的信息。 注意下,MDC 是大写 X,我在后面的试验开始时使用的小写的,结果无法正确输出

三、试着写一个

思路:通过 filter 将 session 中保存的用户信息放入 MDC,通过调整 log4j.properties 输出需要的信息。

//定义 Filter
package net.caiban.pc.erp.filter;

import net.caiban.pc.erp.config.AppConst;
import net.caiban.pc.erp.domain.SessionUser;
//import org.apache.log4j.MDC;  //这里没有选择用 Log4j 的 MDC,而是换成了 slf4j
import org.slf4j.MDC;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * @author mays (mays@caiban.net)
 *
 * created on 2016-5-8
 */
public class Log4jMDCFilter implements Filter {

   @Override
   public void destroy() {
   }

   @Override
   public void doFilter(ServletRequest rq, ServletResponse rp,
         FilterChain chain) throws IOException, ServletException {
      HttpServletRequest request= (HttpServletRequest) rq;
      HttpServletResponse response = (HttpServletResponse) rp;

        try {
            putMDC(request, response); //保存信息
            chain.doFilter(request, response);
        }finally {
            clearMDC(request); //记得 clear 相关信息,否则会导致内存溢出
        }
        return ;
   }

   @Override
   public void init(FilterConfig config) throws ServletException {
   }

    public void putMDC(HttpServletRequest request, HttpServletResponse response){
        MDC.put("remoteAddr", request.getRemoteAddr());
        MDC.put("remoteHost", request.getRemoteHost());
        SessionUser user = (SessionUser) request.getSession().getAttribute(AppConst.SESSION_KEY);
        if(user!=null){
            MDC.put("uid", String.valueOf(user.getUid()));
            MDC.put("cid", String.valueOf(user.getCid()));
            MDC.put("account", user.getAccount());
        }
    }

    public void clearMDC(HttpServletRequest request){
        MDC.clear();
    }
}

log4j 配置

#############default level and appender####################
log4j.rootCategory=info,stdout

###################appender stdout##########################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Threshold = debug
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m %X{uid} %X{remoteAddr}%n

关于的配置:

%X{uid} %X{remoteAddr}

随后随便找个位置打印一句日志,效果如下:

ERROR [http-nio-8080-exec-8] (ApiController.java:193) - log4j test MDC 6 127.0.0.1

大致的方法就这么些了,但实际应用中这么写还是不够的,下面看下可以做的改进

四、可以继续改进的地方
  1. 丰富信息:网站或软件系统应根据自己的需要获取请求中可以被拿到的任何信息,并将其放入 MDC,例如URI,Referer等

  2. 可以在 MDC 中将请求的时间截放入,同时可以利用过虑器统计请求耗时,最终将耗时信息提供给日志,以便输出

  3. 日志输出应重新组织下,输出日志到合适的位置,而不是像我示例那样直接在 console 中输出

另外有几点要注意:

  1. 千万记得 finally 要清除 MDC 中保存的信息

  2. 我在示例中未使用 Log4j 的 MDC,而是换成了 slf4j 的 MDC,功能没什么差别,但是 slf4j 可以直接使用 MDC.clear(),而 Log4j 的 MDC 需要一个个 remove 放入的 key。另外 slf4j 只有 MDC,没有 NDC。关于 slf4j MDC 的使用,还可以看下这篇文章(虽然文章排版实在有点乱):Slf4j MDC 使用和 基于 Logback 的实现分析

使用 Log4j 的 NDC/MDC 改进日志
 
  • 发表于 2年前 
  • 阅读 585 
  • 收藏 8 
  • 点赞 2 
  • 评论 0
摘要: 对系统关键路径记录日志是一种很常用的问题记录和分析方法,在复杂的 web 应用中,我们除了记录日志,还需要能够通过日志有效地识别请求的用户信息,平时我们的做法很多是在记录的时候将 session 或ip 地址等用户相关的信息,在日志打印时加进去,这么做并不是不可以,问题是麻烦。那么有没有更好的办法可以解决呢?有,怎么做?请看文章内容
一、问题产生

一般软件系统都会由简单到复杂,功能少的,简单的软件系统用用一般的日志输出也就能够解决问题了,但是这种软件系统真正能用的也太少了,实际场景中往往是软件么越做越复杂,越做越臃肿,且不说软件系统设计好坏,总归这是一个软件系统所要经历的,那么在系统中用日志的方式添加追踪点,帮助解决问题就成了比较常用的方法。而对于网站系统,通常除了知道这个日志是在哪个跟踪点产生的,还需要知道是由谁(往往是哪个用户,哪个 IP 等)产生的,这时就会想到往日志里塞入这类信息,但并不是每段代码都那么容易获取到用户的信息,特别是那些分层比较多,比较内层的方法,将一个对象穿透到应用的各个部分,想想都有点不好办。那么用户相关的信息每一个请求都是有办法获取到的,那有没有办法不侵入系统的内部而实现获取这些信息呢?原来 Log4j 早就为我们准备好了。

二、了解 NDC/MDC

关于 NDC/MDC 的详细介绍可以看这里:在 Web 应用中增加用户跟踪功能

简而言之:NDC是一种在同一个线程内将信息保存起来,提供日志输出时使用。MDC 与 NDC 相比,信息以 Map 方式保存,支付多个健值对。

使用方式上,NDC 通过在 PatternLayout 增加 %x 获取信息,而 MDC 通过 %X{key} 获取对应 key 的信息。 注意下,MDC 是大写 X,我在后面的试验开始时使用的小写的,结果无法正确输出

三、试着写一个

思路:通过 filter 将 session 中保存的用户信息放入 MDC,通过调整 log4j.properties 输出需要的信息。

//定义 Filter
package net.caiban.pc.erp.filter;

import net.caiban.pc.erp.config.AppConst;
import net.caiban.pc.erp.domain.SessionUser;
//import org.apache.log4j.MDC;  //这里没有选择用 Log4j 的 MDC,而是换成了 slf4j
import org.slf4j.MDC;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * @author mays (mays@caiban.net)
 *
 * created on 2016-5-8
 */
public class Log4jMDCFilter implements Filter {

   @Override
   public void destroy() {
   }

   @Override
   public void doFilter(ServletRequest rq, ServletResponse rp,
         FilterChain chain) throws IOException, ServletException {
      HttpServletRequest request= (HttpServletRequest) rq;
      HttpServletResponse response = (HttpServletResponse) rp;

        try {
            putMDC(request, response); //保存信息
            chain.doFilter(request, response);
        }finally {
            clearMDC(request); //记得 clear 相关信息,否则会导致内存溢出
        }
        return ;
   }

   @Override
   public void init(FilterConfig config) throws ServletException {
   }

    public void putMDC(HttpServletRequest request, HttpServletResponse response){
        MDC.put("remoteAddr", request.getRemoteAddr());
        MDC.put("remoteHost", request.getRemoteHost());
        SessionUser user = (SessionUser) request.getSession().getAttribute(AppConst.SESSION_KEY);
        if(user!=null){
            MDC.put("uid", String.valueOf(user.getUid()));
            MDC.put("cid", String.valueOf(user.getCid()));
            MDC.put("account", user.getAccount());
        }
    }

    public void clearMDC(HttpServletRequest request){
        MDC.clear();
    }
}

log4j 配置

#############default level and appender####################
log4j.rootCategory=info,stdout

###################appender stdout##########################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Threshold = debug
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m %X{uid} %X{remoteAddr}%n

关于的配置:

%X{uid} %X{remoteAddr}

随后随便找个位置打印一句日志,效果如下:

ERROR [http-nio-8080-exec-8] (ApiController.java:193) - log4j test MDC 6 127.0.0.1

大致的方法就这么些了,但实际应用中这么写还是不够的,下面看下可以做的改进

四、可以继续改进的地方
  1. 丰富信息:网站或软件系统应根据自己的需要获取请求中可以被拿到的任何信息,并将其放入 MDC,例如URI,Referer等

  2. 可以在 MDC 中将请求的时间截放入,同时可以利用过虑器统计请求耗时,最终将耗时信息提供给日志,以便输出

  3. 日志输出应重新组织下,输出日志到合适的位置,而不是像我示例那样直接在 console 中输出

另外有几点要注意:

  1. 千万记得 finally 要清除 MDC 中保存的信息

  2. 我在示例中未使用 Log4j 的 MDC,而是换成了 slf4j 的 MDC,功能没什么差别,但是 slf4j 可以直接使用 MDC.clear(),而 Log4j 的 MDC 需要一个个 remove 放入的 key。另外 slf4j 只有 MDC,没有 NDC。关于 slf4j MDC 的使用,还可以看下这篇文章(虽然文章排版实在有点乱):Slf4j MDC 使用和 基于 Logback 的实现分析

来自: https://my.oschina.net/mays/blog/671849?fromerr=8O7CEiEg
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值