ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java 面试题之 Logback 打印日志是如何获取当前方法名称的? [打印本页]

作者: 圆咕噜咕噜    时间: 2023-12-15 16:15
标题: Java 面试题之 Logback 打印日志是如何获取当前方法名称的?
在 Java 中,有四种方法可以获取当前正在执行方法体的方法名称,分别是:
本文将根据以上四种方法来给大家进行具体讲解,不过不知道大家有没有想过,获取当前执行方法体的方法名称有什么用嘞?
它可以用于日志记录、异常处理、测试框架等方面。例如我们可以在方法的开始和结束时打印出当前方法名和参数,以便追踪程序的执行流程和性能。在介绍完以上四种方法后,就会给大家揭晓面试题答案。
1.使用 Thread.currentThread().getStackTrace()方法

这种方法是通过获取当前线程的堆栈跟踪信息,然后从中提取出当前方法名的。具体的代码如下:
  1. // 获取当前方法名
  2. String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
  3. // 打印当前方法名
  4. System.out.println("当前方法名:" + methodName);
复制代码
这种方法的优点是简单易用,不需要创建额外的对象。缺点是性能较低,因为 Thread.currentThread().getStackTrace() 方法获取堆栈跟踪信息需要遍历整个调用栈,而且需要保证线程安全性。
2.使用异常对象的 getStackTrace()方法

这种方法是通过创建一个新的异常对象,然后从其堆栈跟踪信息中提取出当前方法名和参数的。具体的代码如下:
  1. // 获取当前方法名
  2. String methodName = new Exception().getStackTrace()[0].getMethodName();
  3. // 打印当前方法名
  4. System.out.println("当前方法名:" + methodName);
复制代码
这种方法的优点是不需要获取堆栈跟踪信息,而且不会创建异常对象,因此性能和可读性都较好。缺点是需要创建额外的对象,而且代码较为复杂,不太直观。
3.匿名内部类的 getClass().getEnclosingMethod()方法

这种方法是通过创建一个匿名内部类的对象,然后从其类对象中获取当前方法的方法对象,再从方法对象中获取当前方法名和参数的。具体的代码如下:
  1. // 获取当前方法名
  2. String methodName = new Object(){}.getClass().getEnclosingMethod().getName();
  3. // 打印当前方法名
  4. System.out.println("当前方法名:" + methodName);
复制代码
这种方法的优点是不需要获取堆栈跟踪信息,而且不会创建异常对象,因此性能和可读性都较好。缺点是需要创建额外的对象,而且代码较为复杂,不太直观。
4.Java 9 的 Stack-Walking API

Java 9 引入了 Stack-Walking API,以惰性且高效的方式遍历 JVM 堆栈帧。可以使用这个 API 找到当前正在执行的方法,具体的代码如下:
  1. StackWalker walker = StackWalker.getInstance();
  2. Optional<String> optional = walker.walk(frames -> frames
  3.         .findFirst()
  4.         .map(StackWalker.StackFrame::getMethodName));
  5. System.out.println("当前方法名:" + optional.get());
复制代码
首先,我们使用 StackWalker.getInstance() 工厂方法获取 StackWalker 实例。然后我们使用 walk() 方法从上到下遍历栈帧:
Stack-Walking API 的优点
与以上方法相比,Stack-Walking API 有很多优点:
StackWalker 是以一种懒惰的方式逐一遍历堆栈。在需要获取当前方法名称时,我们可以只获取顶部帧,而不需要捕获整个堆栈跟踪。
推荐作者开源的 H5 商城项目 waynboot-mall,这是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。分模块设计、简洁易维护,欢迎大家点个 star、关注我。
github 地址:https://github.com/wayn111/waynboot-mall
经典例子:Logback

Logback 是一个流行的 Java 日志框架,它是 Log4j 的继承者,由 Log4j 的创始人设计。Logback 有以下特点:
不知道大家有没有想过,我们在使用 Logback 日志框架中打印日志时,是如何获取当前执行方法体的方法名称的嘞?在 Spring 项目中,我们一般是通过 Logback 的 xml 文件 parttern 属性来配置日志格式的。xml 配置如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3.     <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="dev"/>
  4.     <property name="logPath" value="/home/logs/${appName}"/>
  5.     <property name="pattern"
  6.               value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{request_id}] [%thread] [%-5level] %logger{36}:%L %M - %msg%n"/>
  7.    
  8.     <appender name="STDOUT" >
  9.         
  10.         <encoder>
  11.             <pattern>${pattern}</pattern>
  12.         </encoder>
  13.     </appender>
  14.    
  15.     <appender name="INFO" >
  16.         <file>${logPath}/info.log</file>
  17.         <encoder>
  18.             <pattern>${pattern}</pattern>
  19.         </encoder>
  20.         <rollingPolicy >
  21.             <fileNamePattern>${logPath}/run.%d{yyyy-MM-dd}.log</fileNamePattern>
  22.         </rollingPolicy>
  23.     </appender>
  24.     ...
  25. </configuration>
复制代码
可以看到我们配置的日志输出格式是 %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{request_id}] [%thread] [%-5level] %logger{36}:%L %M - %msg%n,Logback 在打印日志时,会解析这个日志输出格式,最后将 %M 占位符替换为当前方法名称。
解析日志格式的源码就在 FormattingConverter 类的 write() 方法中,write() 方法中会执行 convert() 方法,这个方法就是执行占位符替换的。源码截图如下,

如上图根据类名我们可以看到红线框起来的 MethodOfCallerConverter 类就是用来执行 %M 占位符替换逻辑的,代码如下,
  1. public class MethodOfCallerConverter extends ClassicConverter {
  2.     public String convert(ILoggingEvent le) {
  3.         StackTraceElement[] cda = le.getCallerData();
  4.         if (cda != null && cda.length > 0) {
  5.             // 返回当前方法名称
  6.             return cda[0].getMethodName();
  7.         } else {
  8.             return CoreConstants.NA;
  9.         }
  10.     }
  11. }
复制代码
方法逻辑如下,
如上,我们只需要看下 le.getCallerData() 方法的堆栈是从哪里获取来的,就能知道本题的答案了。
进入 LoggingEvent 源码类中,我们可以发现堆栈获取逻辑,源码如下,
  1. public class LoggingEvent implements ILoggingEvent {
  2.     public StackTraceElement[] getCallerData() {
  3.         if (callerDataArray == null) {
  4.             // 堆栈初始化
  5.             callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass,
  6.                     loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
  7.         }
  8.         return callerDataArray;
  9.     }
  10.     ...
  11. }
复制代码
Ok,到这里离胜利就只差一步了。进一步查看 CallerData.extract(new Throwable(), fqnOfLoggerClass,loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages()) 方法,源码如下,
  1. public class CallerData {
  2.     public static StackTraceElement[] extract(Throwable t, String fqnOfInvokingClass, final int maxDepth,
  3.             List<String> frameworkPackageList) {
  4.         if (t == null) {
  5.             return null;
  6.         }
  7.         StackTraceElement[] steArray = t.getStackTrace();
  8.         StackTraceElement[] callerDataArray;
  9.         ...
  10.         callerDataArray = new StackTraceElement[desiredDepth];
  11.         for (int i = 0; i < desiredDepth; i++) {
  12.             callerDataArray[i] = steArray[found + i];
  13.         }
  14.         return callerDataArray;
  15.     }
  16.     ...
  17. }
复制代码
为了突出源码逻辑的重点,这里我删去了一部分代码,是为了让大家更好的看清楚 Logback 中堆栈信息的初始化,其实用的就是异常对象的 getStackTrace() 方法。也就是上面源码中 StackTraceElement[] steArray = t.getStackTrace() 方法所体现的。
那么到这里我就可以下一个结论了, Logback 日志框架中打印日志时,就是使用异常对象的 getStackTrace() 方法来获取当前执行方法的方法名称的。
总结

本文有介绍四种方法获取当前执行方法名称,一般情况下大家使用异常对象的 getStackTrace() 方法以及匿名内部类的 getClass().getEnclosingMethod() 方法都是可以的,它们的性能都 OK,代码书写复杂程度都大差不差。在 Java 9 以后推荐使用 Stack-Walking API,它的功能更为强大,与程序里的堆栈语意也跟为契合,性能 OK,并且还是线程安全的。
关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4