slf4j 的MDC (捎带主动获取方法堆栈)

slf4j 的MDC (附带主动获取方法堆栈)
1. 主动获取方法调用链
本来是想能在打印日志时获取关键方法的调用链,比如Dao层是关键点,那能获取这个方法是被哪个Service调用,这个Service又是被哪个Controller调用,并且这些调用传递的参数分别是什么,这样对定位问题就很方便了。
初步设想是通过主动获取调用堆栈:
public static void test()
    {
        // 这个ex定义在哪里,打印出来的就是哪个方法被调用的堆栈
        Throwable ex = new Throwable();

        StackTraceElement[] stackElements = ex.getStackTrace();

        if(stackElements != null)
        {
            System.out.println(stackElements.length);
            for(int i = 0; i < stackElements.length; i++)
            {
                System.out.println(stackElements[i].getClassName());
                System.out.println(stackElements[i].getFileName());
                System.out.println(stackElements[i].getLineNumber());
                System.out.println(stackElements[i].getMethodName());
            }
        }
    }


在idea里直接run main,打印:
7
com.dd.domain.template.Template
Template.java
194
test
-----------------------------------
com.dd.domain.template.Template
Template.java
165
main
-----------------------------------
sun.reflect.NativeMethodAccessorImpl
NativeMethodAccessorImpl.java
-2
invoke0
-----------------------------------
sun.reflect.NativeMethodAccessorImpl
NativeMethodAccessorImpl.java
39
invoke
-----------------------------------
sun.reflect.DelegatingMethodAccessorImpl
DelegatingMethodAccessorImpl.java
25
invoke
-----------------------------------
java.lang.reflect.Method
Method.java
597
invoke
-----------------------------------
com.intellij.rt.execution.application.AppMain
AppMain.java
134
main
-----------------------------------


如果只是debug main, 则打印:
2
com.dd.domain.template.Template
Template.java
194
test
-----------------------------------
com.dd.domain.template.Template
Template.java
165
main
-----------------------------------

这也说明idea里debug和run时的启动类不一样。
这种方式有两个问题:
1)能获取到这个堆栈,但不能获取到调用参数,
2)另外这个ex必须定义在“关键方法”里才能获取到方法调用链


2. 使用slf4j的MDC为一个调用链设置唯一标识UUID
后来确定通过MDC来为一个调用流程设置一个唯一UUID,出现问题时返回这个UUID,搜索日志UUID,则可以获取一个业务调用流程所有的日志信息。

但是要注意:
1)这只能保证一个线程内的调用是同一个UUID,如果主线程里又启动了其他子线程,则子线程不会被跟踪。
2)MDC是通过web.xml的Filter来实现过滤页面请求,但这种也会拦截到对静态资源的请求,比如js,css等,根据需要做单独过滤,比如:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        // 排除.js,.css,.png等资源文件进入Filter。只拦截Controller请求,Controller请求url不带"."
        if (((HttpServletRequest) request).getRequestURI().contains(".") || ROOT_URL.equals(((HttpServletRequest) request).getRequestURI()))
        {
            chain.doFilter(request, response);
            return;
        }

        try
        {
            /*
             * This code puts the value "UUID" to the Mapped Diagnostic context.
             * Since MDc is a static class, we can directly access it without creating a new object from it.
             * Here, instead of hard coding the user name, the value can be retrieved from a HTTP Request object.
             */
            String uuid = UUID.randomUUID().toString();
            MDC.put("requestUUID", uuid.replaceAll("-", "").toUpperCase());
            chain.doFilter(request, response);
        }
        finally
        {
            MDC.remove("requestUUID");
        }
    }

具体操作详情可以参考:
http://veerasundar.com/blog/2009/11/log4j-mdc-mapped-diagnostic-context-example-code/
http://logback.qos.ch/manual/mdc.html