前言

近期在做日志优化,需要记录业务日志,日志内容包括业务的请求、响应信息。当然还应该有具体的调用方法位置、日志的所在的方法等通用信息。

解决方案

一般情况下,在程序有异常信息时可以打印出整个调用堆栈信息,但是现在需要的是正常调用下来获取到当前方法的调用帧。

JAVA9之前

Thread.dumpStack()

这个可以打印出当前的堆栈错误信息,本质上是new一个Exception,调用了printStackTrace()方法,但是这个方法只建议用于调试。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class StackTraceExample1 {

    public static void main(String[] args) {
        one();
    }

    public static void one() {
        two();
    }

    private static void two() {
        three();
    }

    private static void three() {
        Thread.dumpStack();
    }

}

执行后输出如下:

1
2
3
4
5
6
java.lang.Exception: Stack trace
    at java.base/java.lang.Thread.dumpStack(Thread.java:1379)
    at cn.imcompany.stack.StackTraceExample1.three(StackTraceExample1.java:25)
    at cn.imcompany.stack.StackTraceExample1.two(StackTraceExample1.java:21)
    at cn.imcompany.stack.StackTraceExample1.one(StackTraceExample1.java:17)
    at cn.imcompany.stack.StackTraceExample1.main(StackTraceExample1.java:13)

直接以异常的方式显示出来了。

Thread.currentThread().getStackTrace()

获取当前线程的调用堆栈信息,返回为StackTraceElement的数组,追踪源码,查看实现也是new了一个Exception,只不过调用了getStackTrace()方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class StackTraceExample2 {

    public static void main(String[] args) {
        one();
    }

    public static void one() {
        two();
    }

    private static void two() {
        three();
    }

    public static void three() {

        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        Arrays.stream(stackTraceElements).forEach(System.out::println);
    }
}

执行后的输出如下:

1
2
3
4
5
java.base/java.lang.Thread.getStackTrace(Thread.java:1598)
cn.imcompany.stack.StackTraceExample2.three(StackTraceExample2.java:28)
cn.imcompany.stack.StackTraceExample2.two(StackTraceExample2.java:23)
cn.imcompany.stack.StackTraceExample2.one(StackTraceExample2.java:19)
cn.imcompany.stack.StackTraceExample2.main(StackTraceExample2.java:15)

完整的打印出了调用栈信息,但是如果只是想要获取一部分关注的堆栈或者想丢弃不需要的信息处理起来是比较麻烦的。

JAVA9中的StackWalker

为了更方便的获取(按需获取)调用堆栈信息,充分的融合JAVA8的流(延迟访问)处理方式,JAVA9中引入了StackWalker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class StackTraceExample3 {

    public static void main(String[] args) {
        one();
    }

    public static void one() {
        two();
    }

    private static void two() {
        three();
    }

    private static void three() {

        StackWalker.getInstance(StackWalker.Option.SHOW_REFLECT_FRAMES)
                .walk(sf -> sf.collect(Collectors.toList()))
                .forEach(System.out::println);
    }

}

输出结果为:

1
2
3
4
cn.imcompany.stack.StackTraceExample3.three(StackTraceExample3.java:29)
cn.imcompany.stack.StackTraceExample3.two(StackTraceExample3.java:23)
cn.imcompany.stack.StackTraceExample3.one(StackTraceExample3.java:19)
cn.imcompany.stack.StackTraceExample3.main(StackTraceExample3.java:15)

在获取完StackWalker的实例后,可以调用walk方法来对StackFrame进行过滤,所有的堆栈信息都在StackFrame中维护。

StackWalker中的Options

在获取实例时可以指定一个或者多个Options,StackWalker中提供了三种Option,分别是:

  1. RETAIN_CLASS_REFERENCE,保留Class对象,配置了该Option支持StackWalker#getCallerClass()方法
  2. SHOW_REFLECT_FRAMES,显示所有的反射调用Frame,在只配置此选项下,调用StackWalker#getCallerClass()方法会抛出UnsupportedOperationException
  3. SHOW_HIDDEN_FRAMES,显示所有隐藏的Frame,它是SHOW_REFLECT_FRAME父集,所以也会显示出所有反射调用Frame,在只配置此选项下,调用StackWalker#getCallerClass()方法会抛出UnsupportedOperationException