Javalogging:显示调用者的源代码行号(不是日志助手方法)
Java的众多(叹息…)日志框架都能很好地显示创build日志消息的方法的源文件名的行号:
log.info("hey"); [INFO] [Foo:413] hey
但是如果在两者之间有一个辅助方法,那么实际的调用者将是辅助方法,而且这不是太丰富。
log_info("hey"); [INFO] [LoggingSupport:123] hey
当找出打印源位置时,是否有办法告诉日志系统从调用堆栈中删除一帧?
我想这是特定的实现。 我需要的是通过Commons Logging的Log4J,但我有兴趣了解其他选项。
替代答案。
可以通过使用该方法来请求log4j排除助手类
Category.log(String callerFQCN,Priority level,Object message,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))在日志助手中使用完整日志方法的消息。 行(LoggingHelper.logNotWorking(LoggingHelper.java:12))使用类信息方法的消息
使用此方法时,Log4j将照常工作,避免在不需要时计算堆栈跟踪。
请注意,给出行号是非常昂贵的 ,无论是从Log4j或以下自然获得。 你必须接受这个成本…
您可以使用以下API:
StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace(); StackTraceElement stackTraceElement = ...; stackTraceElement.getLineNumber();
更新:
你将不得不自己计算。 所以:
- 要求log4j不要输出(以你的日志格式),
- 并在你的消息的开头插入你自己的行号(你发送给log4j的string)。
根据你喜欢你的logging器,你的帮手方法可能:
- 使用一个显式的Logger(作为parameter passing给我),在适当的时候(我们有时为特定的上下文定义特定的logging器;例如,我们有一个logging器发送我们的数据库请求,不pipe它是什么类;这使我们可以减less到一个地方对我们的configuration文件所做的更改,当我们想要(去)激活它们…)
- 使用一个logging器的调用类:在这种情况下,而不是传递参数, 你可以推断出类似的调用者类名称 …
将详细信息添加到KLE答案。 (对不起,noob用户,不知道比创build一个单独的答案更好的方式)
不要把行号粘在消息上,你可以把它放在MDC上下文中。 请参阅org.apache.log4j.MDC
例如:
StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace(); StackTraceElement stackTraceElement = ...; int l = stackTraceElement.getLineNumber(); MDC.put("myLineNumber", l);
这允许用户在他们的log4jconfiguration文件中使用mylineNumber
<layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="Line(%X{myLineNumber})- %m%n"/> </layout>
注意:允许用户控制消息中的行号出现的位置和方式。 但是,由于获取堆栈跟踪的代价非常高,您仍然需要find一种方法来closures该function。
对于Log4j2,答案完全由使用logging器包装提供,如Log4j2手册中“生成的logging器包装程序的使用范例”下所述 。 可以简单地使用单个STUB级别生成(使用org.apache.logging.log4j.core.tools.Generate $ ExtendedLogger工具)一个包含一个STUB级别的logging器包装器,然后调整它以创build自定义日志logging方法,模拟使用logIfEnabled (FQCN,LEVEL,Marker,message,Throwable) – 可能忽略STUB级别并使用常规级别 – 如果需要,删除或注释掉STUB级别及其方法。 为此,FormattedMessage可能会有所帮助。
然后通过使用configuration中给出的PatternLayout中的%l位置转换模式元素,或者更具体地使用%L行号和/或%行,可以很容易地将源代码行显示为完整位置信息的一部分。 M方法转换。
现在完整的例子如下: Java Logging:Log4j Version2.x:显示最终客户端调用者的方法(不是中间日志logging帮助器方法)
出来,有一个非常简单的解决scheme,只需添加FQCN (包装类的完全合格的类名)到您的logging器助手:
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...
在你的工作class里,你只需要做:
public class MyClass { private static final Logger LOG = MyLogger.getLogger(); private void test() { LOG.info("test"); } }
这是不可能的。 在这种情况下最好的做法是在调用者中创buildlogging器并将其传递给util方法。 这样,你至less可以知道来电的来源。
如果您有自己的日志logging实用程序方法,则可以将日志参数列表中的行号和文件名添加到cpp路由中。 即在进行编译之前,预处理源代码,以replace_ LINE _和_ FILE _之类的标记。 作为额外的好处,这不会占用尽可能多的资源。
也许你可以使用栈跟踪元素来实现日志帮助器function,获取行号,并绕过具有特定注释的方法的帧,
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()); }
因此,如果辅助函数是嵌套的,或者使用了更多的辅助函数,只需在所有的辅助函数上标记SkipFrame注释,就可以得到你真正想要的正确的源代码行号。