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");
}
}
我在 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");
}
}