Log4j中统一类内Debug方法后,如何显示原始调用行号?
这确实是个常见的问题——当你用自定义方法包装日志调用时,日志框架默认会记录包装方法的行号,而不是实际调用你这个debug()方法的位置。原因很简单:日志框架是从当前线程的栈轨迹中定位调用者的,它会找到最上层的非日志框架自身的方法,也就是你的debug()方法,所以行号自然是这里的。
不过别担心,有几种可靠的方法可以解决这个问题,下面给你详细说明:
方法1:使用SLF4J的LocationAwareLogger接口(推荐)
SLF4J提供了LocationAwareLogger接口,专门用来处理这种需要自定义调用者位置的场景。它允许你指定一个"全限定类名(FQCN)",日志框架会根据这个类名跳过栈帧,找到实际的调用者。
代码示例:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.spi.LocationAwareLogger; public class YourLoggingClass { // 常规的SLF4J Logger private static final Logger LOGGER = LoggerFactory.getLogger(YourLoggingClass.class); // 转为LocationAwareLogger接口实例 private static final LocationAwareLogger LOCATION_AWARE_LOGGER = (LocationAwareLogger) LOGGER; // 指定当前类的全限定名,用于让日志框架跳过当前类的栈帧 private static final String FQCN = YourLoggingClass.class.getName(); public void debug(String message) { if (LOGGER.isDebugEnabled()) { LOCATION_AWARE_LOGGER.log( null, // Marker,不需要可以传null FQCN, // 我们的类名,用来定位栈帧 LocationAwareLogger.DEBUG_INT, // 日志级别 message, // 日志消息 null, // 消息参数数组,不需要传null null // 异常信息,不需要传null ); } } // 实际调用示例 public void xyz() { debug("Into method xyz"); // ... 业务逻辑 debug("Exiting method xyz"); } }
这个方法的优势是它是SLF4J的标准接口,不管你底层用的是Logback、Log4j2还是其他SLF4J实现,都能兼容,而且性能开销很小。
方法2:直接调用底层日志框架的API(以Logback为例)
如果你明确使用的是Logback作为底层日志框架,可以直接调用Logback的Logger.log()方法,手动指定调用者的位置信息。这种方式需要你获取Logback的Logger实例,然后传递栈轨迹中的调用者信息:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; public class YourLoggingClass { private static final Logger LOGGER = LoggerFactory.getLogger(YourLoggingClass.class); private static final Logger LOGBACK_LOGGER = (Logger) LOGGER; public void debug(String message) { if (LOGGER.isDebugEnabled()) { // 获取当前线程的栈轨迹 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // stackTrace[0]是getStackTrace()方法本身,stackTrace[1]是当前的debug()方法,stackTrace[2]就是实际调用者 StackTraceElement caller = stackTrace[2]; LOGBACK_LOGGER.log( caller.getClassName(), caller.getMethodName(), caller.getFileName(), caller.getLineNumber(), Level.DEBUG, message, null // 异常信息,不需要传null ); } } }
这种方式更直接,但缺点是和Logback绑定了,如果以后换其他日志框架,代码需要调整。另外,获取栈轨迹有轻微的性能开销,但因为我们已经在isDebugEnabled()判断之后才执行,所以只有在debug模式开启时才会触发,对生产环境影响可以忽略。
方法3:调整日志框架的配置(部分框架支持)
有些日志框架支持通过配置来调整栈帧的偏移量,比如Logback可以在logger配置中添加callerDataDepth属性,但这种方式不够灵活,一般只适用于固定层数的包装,所以不如前面两种方法可靠。
注意事项
- 确保你的SLF4J和底层日志框架版本兼容,比如
LocationAwareLogger是SLF4J 1.6.0及以上版本才支持的。 - 如果你需要包装更多日志级别(比如info、warn),可以用同样的逻辑扩展方法,或者抽象成通用的私有方法来复用代码。
内容的提问来源于stack exchange,提问作者Sekhar

