Java日志记录:显示调用者的源行号(不是日志记录助手方法)
众多(叹气…)Java日志框架都很好地显示了创建日志消息的方法的源文件名行号:Java日志记录:显示调用者的源行号(不是日志记录助手方法),java,logging,log4j,apache-commons-logging,Java,Logging,Log4j,Apache Commons Logging,众多(叹气…)Java日志框架都很好地显示了创建日志消息的方法的源文件名行号: log.info("hey"); [INFO] [Foo:413] hey 但是,如果中间有一个helper方法,那么实际的调用方将是helper方法,这不是太多信息 log_info("hey"); [INFO] [LoggingSupport:123] hey 在确定要打印的源位置时,是否有方法告诉日志记录系统从调用堆栈中删除一个帧 我认为这是特定于实现的;我需要的是通过Commons日志记录的Log4
log.info("hey");
[INFO] [Foo:413] hey
但是,如果中间有一个helper方法,那么实际的调用方将是helper方法,这不是太多信息
log_info("hey");
[INFO] [LoggingSupport:123] hey
在确定要打印的源位置时,是否有方法告诉日志记录系统从调用堆栈中删除一个帧
我认为这是特定于实现的;我需要的是通过Commons日志记录的Log4J,但我有兴趣了解其他选项。请注意,提供行号是非常昂贵的,无论是从Log4J还是以下内容中自然获得的。你必须接受这个代价 您可以使用以下API:
StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
StackTraceElement stackTraceElement = ...;
stackTraceElement.getLineNumber();
更新: 你得自己计算。因此:
- 要求log4j不要输出它(以您的日志格式)
- 并在消息的开头插入行号(发送到log4j的字符串)
- 在适当的时候使用显式记录器(我想是作为参数传递的)(我们有时会为特定的上下文定义特定的记录器;例如,我们有一个用于发送数据库请求的记录器,不管它是什么类;这允许我们在需要时将对配置文件所做的更改减少到一个位置(de-)激活它们…)
- 为调用类使用记录器:在这种情况下,您可以推断调用方类名,而不是传递参数
- 这是不可能开箱即用的。在这种情况下,您最好在调用者中创建记录器,并将其传递给util方法。这样,你至少可以知道电话是从哪里打来的。为应答添加详细信息。(对不起,noob用户,不知道比创建单独答案更好的方法)
您可以将其放在MDC上下文中,而不是将行号粘贴到消息上。请参见org.apache.log4j.MDC
例如:
StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
StackTraceElement stackTraceElement = ...;
int l = stackTraceElement.getLineNumber();
MDC.put("myLineNumber", l);
这允许用户在其log4j配置文件中使用mylineNumber
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="Line(%X{myLineNumber})- %m%n"/>
</layout>
注意:这允许用户控制行号在消息中显示的位置和方式。但是,由于获取stacktrace的成本非常高,因此仍然需要找到关闭该功能的方法 备选答案
可以要求log4j使用该方法排除helper类
log(字符串callerFQCN、优先级、对象消息、Throwable t)
并将助手类指定为“callerFQCN”
例如,下面是一个使用帮助器的类:
public class TheClass {
public static void main(String...strings) {
LoggingHelper.log("Message using full log method in logging helper.");
LoggingHelper.logNotWorking("Message using class info method");
}}
以及助手的代码:
public class LoggingHelper {
private static Logger LOG = Logger.getLogger(LoggingHelper.class);
public static void log(String message) {
LOG.log(LoggingHelper.class.getCanonicalName(), Level.INFO, message, null);
}
public static void logNotWorking(String message) {
LOG.info(message);
} }
第一个方法将输出预期结果
Line(TheClass.main(TheClass.java:4)) Message using full log method in logging helper.
Line(LoggingHelper.logNotWorking(LoggingHelper.java:12)) Message using class info method
行(class.main(class.java:4))消息,在logging helper中使用完整日志方法。
使用类信息方法生成一行(loggingheloper.logNotWorking(loggingheloper.java:12))消息
使用此方法时,Log4j将正常工作,避免在不需要时计算堆栈跟踪。如果您有自己的日志实用程序方法,则可以将行号和文件名添加到日志参数列表中,并采用cpp路由。i、 e.在编译之前,对源代码进行预处理,以替换诸如uLine和FILE之类的标记。作为一个额外的好处,这不会像在运行时计算那样占用太多的资源。也许您可以使用stack跟踪元素实现log helper函数,获取行号,并使用带有一些特定注释的方法绕过框架,如
public @interface SkipFrame {}
// helper function
@SkipFrame // not necessary on the concrete log function
void log(String... message) {
// getStackTrace()...
int callerDepth = 2; // a constant number depends on implementation
StackTraceElement callerElement = null;
for (StackTraceElement e: stackTrace) {
String className, methodName = e.getClassName, getMethodName()...
Class callClass = Class.forName(className);
// since there maybe several methods with the same name
// here skip those overloaded methods
Method callMethod = guessWhichMethodWithoutSignature(callClass, methodName);
SkipFrame skipFrame = callMethod.getAnnotation(SkipFrame.class);
if (skipFrame != null)
continue; // skip this stack trace element
if (callerDepth-- == 0) {
callerElement = e;
break;
}
}
assert callerDepth == 0;
assert callerElement != null;
Log4j.info(callerElement.getLineNumber()... + "message... ");
}
@SkipFrame
void logSendMail(Mail mailObject) {
log("Send mail " + mailObject.getSubject());
}
因此,如果helper函数是嵌套的,或者有更多使用的helper函数,只需在所有这些函数上标记SkipFrame注释,就可以得到您真正想要的正确源代码行号 有一个非常简单的解决方案,只需将(包装类的完全限定类名)添加到您的logger helper:
public class MyLogger extends Logger {
private static final String FQCN = MyLogger.class.getName() + ".";
protected MyLogger(String name) {
super(name);
}
public void info(final Object msg) {
super.log(FQCN, Level.INFO, msg, null);
}
//etc...
在你的工人阶级,你只需要做:
public class MyClass {
private static final Logger LOG = MyLogger.getLogger();
private void test()
{
LOG.info("test");
}
}
对于Log4j2,答案完全通过使用Log4j2手册中所述的记录器包装器提供。您只需生成(使用org.apache.logging.log4j.core.tools.generate$ExtendedLogger工具,如图所示)一个具有单个存根级别的记录器包装器,然后对其进行调整,以创建模仿使用logIfEnabled(FQCN、级别、标记、消息、可丢弃)的自定义日志方法-可能忽略存根级别并使用常规的存根级别-如果需要,删除或注释存根级别及其方法)。为此,格式化消息可能会有所帮助 源代码行虽然昂贵,但是可以通过使用配置中给定的%l位置转换模式元素,或者更具体地使用%l行号和/或%M转换方法,轻松地显示为完整位置信息的一部分
现在有完整的例子:我想Log4J正在做类似的事情。我想做的是告诉它在计算中跳过另一帧。我可以自己做所有的计算,但我仍然需要以某种方式将这些信息传递到日志系统,否则日志格式选项将无法识别我的奇特行号。是的,你必须自己计算。因此,请log4j不要输出它(以您的日志格式),并在消息的开头插入行号说明(发送给log4j的字符串)。(我将此添加到答案中)现代JVM的成本要低得多。您真的建议您应该在每个记录的消息上手动插入行号吗?这不是很容易维护的。事实上,如果我在分配给我的项目中看到这个,我会辞职。是的,这会给我正确的记录器类别名称(但仍然是错误的行号)。这是一个进步…我想也许