柳暗花明又一村的sl4j MDC

本来好好的在看 Spring cloud sleuth 的,结果在其文档里窥到这一句,“Adds trace and span ids to the Slf4J MDC ”,内心全是问号,slf4j MDC 是个啥???


官方文档 给出的解释:“大多数现实世界的分布式系统都需要同时处理多个客户端。在这种系统的典型多线程实现中,不同的线程将处理不同的客户端。将一个客户端的日志记录输出与另一个客户端的日志记录输出区分开需要为每一个客户端实例化新的单独的记录器,这可能增加管理开销。一种更轻松的技术包括为服务给定客户端的每个日志请求加盖唯一标记,MDC 中包含该技术的一种变体。"

文档还给出了几个重点:

  • MDC类只包含静态方法。它使开发人员可以将信息放置在诊断上下文中
  • MDC是在每个线程的 基础上进行管理的:假设MDC信息是在每个线程的基础上管理的,则每个线程将拥有自己的副本,开发人员无需在使用进行编程时担心线程安全性或同步性,MDC因为它可以安全透明地处理这些问题。
  • put() and get() 影响当前线程和当前线程的子线程。
  • 建议 remove() 方法在 finally 块中调用,确保代码无论如何都可以调用它们。

这些不很好地解决了我们公司之前系统中日志记录一直困扰我的问题嘛?

问题是这样的:我们在 controller 层记录了大量 error 的日志,并实现了自定义的 appender ,该 appender 保证将日志记录到 es 中。我们的产品编号,用户名都存在 session 中的,需求是我们想在日志中看见这些产品编号以及用户名。

本来有这样一种解决方案:修改 log.error 方法,把少的信息以参数形式补充进去。鉴于这种方法太简单,直接淘汰。(其实是要改动的地方太多了,我懒,并且对于后续日志记录没有任何好处)。

后面偶然在调试的时候,发现 appender 日志记录与controller 采用的是同一个线程,再后来,就看到了 MDC,那么,这不正好匹配并解决了我的问题了嘛?

具体的解决思路是这样的:

  1. servlet 的 filter 中为MDC 添加产品编号等信息

    	@Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            // 存放产品编号于MDC中,以便于日志记录
            if(request instanceof HttpServletRequest){
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                // 该值应该和 com.gysoft.sso.bean.SessionVariable 中的 LOGIN_INFO 字段值保持一致
                String LOGIN_INFO = "loginInfo";
                final HttpSession session = httpServletRequest.getSession(false);
                if(session != null){
                    MDC.put("productNum", Optional.ofNullable(session.getAttribute(LOGIN_INFO)).map(o->{
                        if(o instanceof LoginInfo){
                            return ((LoginInfo) o).getProductNum();
                        }
                        return null;
                    }).orElse(""));
                }
            }
            chain.doFilter(request, response);
        }
    
  2. appender 取得值,然后记录

    String productNum = MDC.get("productNum");
    if(productNum != null){
    	// 记录值
        ...
        // 移除该值
        MDC.remove("productNum");
    }
    

测试以后,解决了我们的问题;MDC 的实现原理,我没有去看,我相信它肯定能用,它也相信我看不懂…


后来,宁哥提出了一个更加简单的解决方案(看来我一直都把问题想复杂了):

  • appender 中定义一个 ThreadLocal;

    /*
    * 建议定义成这样,需要考虑初始值的指定,这个看自己代码需要吧。没有指定,处理的时候,就需要考虑 null 的* 情况
    */
    private static final ThreadLocal basicLogInfoThreadLocal = new ThreadLocal();
    
    // 对外提供修改 basicLogInfoThreadLocal 值的方法
    
  • filter 中修改该 threadLocal 的值;

这种方案也可以,加油洛!

你可能感兴趣的:(矛与盾)