本来是想能在打印日志时获取关键方法的调用链,比如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