Java Logging:显示调用者的源行号(不是日志助手方法)

时间:2009-09-28 09:52:07

标签: java logging log4j apache-commons-logging

Java的众多(叹息......)日志框架都可以很好地显示创建日志消息的方法的源文件名的行号:

log.info("hey");

 [INFO] [Foo:413] hey

但是如果中间有一个辅助方法,那么实际的调用者将是辅助方法,而且信息量不大。

log_info("hey");

[INFO] [LoggingSupport:123] hey

有没有办法告诉日志记录系统在确定要打印的源位置时从callstack中删除一个帧?

我认为这是特定于实现的;我需要的是Log4J通过Commons Logging,但我很想知道其他选项。

8 个答案:

答案 0 :(得分:33)

替代答案。

可以通过使用

方法请求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)) Message using full log method in logging helper.
Line(LoggingHelper.logNotWorking(LoggingHelper.java:12)) Message using class info method

使用此方法时,Log4j将照常工作,如果不需要,则避免计算堆栈跟踪。

答案 1 :(得分:5)

请注意,提供行号是非常昂贵,无论是从Log4j还是以下内容中获得的。你必须接受这笔费用......

您可以使用以下API:

    StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
    StackTraceElement stackTraceElement = ...;
    stackTraceElement.getLineNumber();

更新:

你必须自己计算。所以:

  • 要求log4j不输出它(以您的日志格式),
  • 并在邮件的开头插入行号说明(您发送到log4j的字符串)。

根据您喜欢的记录器的方式,您的帮助方法可以:

  • 在适当的时候使用显式的Logger(作为参数传递)(我们有时会为特定的上下文定义特定的记录器;例如,我们有一个记录器用于发送我们的数据库请求,无论它是什么类;这允许当我们想要(取消)激活它们时,我们将对配置文件所做的更改减少到一个位置......)
  • 使用Logger作为调用类:在这种情况下,不是传递参数,您可以同样推断出调用者类名称 ...

答案 2 :(得分:4)

结果是有一个非常简单的解决方案,只需将FQCN(包装类'完全限定的类名)添加到记录器助手中:

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

}

答案 3 :(得分:2)

向KLE答案添加详细信息。 (抱歉,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>

注意:允许用户控制行号在消息中的显示位置和方式。但是,由于获取堆栈跟踪的成本非常高,因此您仍需要找到关闭该功能的方法。

答案 4 :(得分:2)

对于Log4j2,答案完全由使用logger包装器提供,如Example Usage of a Generated Logger Wrapper下的Log4j2手册中所述。可以简单地生成(使用那里所示的org.apache.logging.log4j.core.tools.Generate $ ExtendedLogger工具)具有单个STUB级别的记录器包装器,然后调整它以创建模仿logIfEnabled使用的自定义日志记录方法(FQCN,LEVEL,Marker,message,Throwable) - 可能忽略STUB级别并使用常规级别 - 然后根据需要删除或注释掉STUB级别及其方法)。为此,FormattedMessage可能会有所帮助。

然后,通过使用配置中给出的PatternLayout中的%l位置转换模式元素,或者更具体地使用%L,可以轻松地将源行尽管显示为完整位置信息的一部分。行号和/或%M方法转换。

现在有完整的例子:Java Logging: Log4j Version2.x: show the method of an end-client caller (not an intermediate logging helper method)

答案 5 :(得分:1)

这不是开箱即用的。在这种情况下,您可以做的最好的事情是在调用者中创建记录器并将其传递给util方法。这样,您至少可以了解来电的来源。

答案 6 :(得分:1)

如果您有自己的日志记录实用程序方法,则可以将linenumber和filename添加到日志记录参数列表中并使用cpp路由。即,在编译之前预处理您的源代码以替换_ LINE _和_ FILE _等标记。作为一个额外的奖励,这不会像在运行时弄清楚那样多的资源。

答案 7 :(得分:0)

也许您可以使用堆栈跟踪元素实现日志帮助函数,获取行号,并使用带有某些特定注释的方法绕过框架,例如,

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注释,您就会得到正确的源代码行。