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注释,就可以得到你真正想要的正确的源代码行号。