Log4j2:如何在不使用 Throwable 的情况下获取 Class 名称和行号?

Log4j2: How can I get Class Name and Line Number without using Throwable?

我在 Log4j2 上开发了一个包装器 class。使用 OSGi 的声明式服务,我发布了一个自定义记录器服务,使用我自己的记录器接口,包装器 class 是实现。包装器 class 仅用于以编程方式配置记录器、消息格式化和添加更多方法,最后它调用 Log4j2 的记录方法。

我想打印源 class/file 日志文件中请求的每个日志的名称和行号。选项 %C/%F 和 %L 仅打印有关我的包装器 class 中我实际调用 log 方法的位置的信息。

所以作为锻炼,我每次都将 new Throwable 作为参数传递,这样我就可以使用布局 %throwable{short.lineNumber} 。但对于嵌入式应用程序来说,这是一个昂贵的过程。

我的主要问题是获取行号,因为对于文件名,我至少可以从 Log4j2 请求一个新的记录器,其中包含请求记录器服务的每个服务的名称,并将其保存在地图中。

有没有办法追溯来电者?我希望有一个类似的解决方案,适用于您不想在记录器服务的每个使用者上都有 LOG4j2 jar 的应用程序。仅供参考,我不想使用任何 XML 文件,所有配置都是以编程方式进行的。

您可以使用

StackTraceElement[] stes = Thread.currentThread().getStackTrace();

不过我不确定这会便宜多少。

我所做的是使每条消息都是唯一的(对于 class)并避免包含行号。您可以在 IDE 中搜索唯一消息以查找行号。 class 应该在记录器的名称中。

Log4j2 在内部遍历堆栈跟踪以提取位置信息。通过向 org.apache.logging.log4j.spi.ExtendedLogger#logMessage 方法指定正确的 FQCN(完全限定的 class 名称),它知道在堆栈跟踪中停止的位置。

Log4j2 包含一个为记录器包装器生成代码的工具。文档在这里(在自定义日志级别手册页中): http://logging.apache.org/log4j/2.x/manual/customloglevels.html#CustomLoggers

尝试使用此工具生成记录器包装器,并将您的自定义包装器基于生成的代码。此生成的代码将使用正确的 FQCN 并将能够生成正确的位置信息。

正如@Remko Popma 所说,这是一个简单的实现:
首先创建你的 ExtendedLogger class:

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;

public class ExtLogWithLine extends ExtendedLoggerWrapper {
    private static final long serialVersionUID = 8239280349129059055L;

    // define your wrapper class here
    private static final String FQCN = WrapperLog.class.getName();

    private final ExtendedLoggerWrapper logger;

    private ExtLogWithLine(final Logger logger) {
        super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());
        this.logger = this;
    }

    public static ExtLogWithLine create(final String name) {
        final Logger wrapped = LogManager.getLogger(name);
        return new ExtLogWithLine(wrapped);
    }

    @Override
    public void debug(String info) {
        if (isDebugEnabled()) {
            logger.logIfEnabled(FQCN, Level.DEBUG, null, info, (Throwable) null);
        }
    }

    @Override
    public void info(String info) {
        if (isInfoEnabled()) {
            logger.logIfEnabled(FQCN, Level.INFO, null, info, (Throwable) null);
        }
    }

    @Override
    public void warn(String info) {
        if (isWarnEnabled()) {
            logger.logIfEnabled(FQCN, Level.WARN, null, info, (Throwable) null);
        }
    }

    @Override
    public void error(String info) {
        if (isErrorEnabled()) {
            logger.logIfEnabled(FQCN, Level.ERROR, null, info, (Throwable) null);
        }
    }
}

然后使用扩展记录器打印日志:
public class WrapperLog {

    public void testLog() {
       ExtLogWithLine logger = ExtLogWithLine.create("xxx");
       
       // log will print out with correct line number
       logger.info("see the line number");
    }
}