47

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

log.info("hey");

 [INFO] [Foo:413] hey

但是如果中间有一个辅助方法,实际的调用者将是辅助方法,这并没有太多信息。

log_info("hey");

[INFO] [LoggingSupport:123] hey

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

我想这是特定于实现的;我需要的是通过 Commons Logging 的 Log4J,但我有兴趣了解其他选项。

4

8 回答 8

36

替代答案。

可以通过使用方法要求log4j排除helper类

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)) 在日志帮助程序中使用完整日志方法的消息。
Line(LoggingHelper.logNotWorking(LoggingHelper.java:12)) 使用类信息方法的消息

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

于 2009-09-28T11:16:39.503 回答
5

请注意,给出行号是非常昂贵的,无论是从 Log4j 中自然获得的还是以下。你必须接受这个代价...

您可以使用以下 API:

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

更新:

你必须自己计算。所以:

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

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

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

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

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

}
于 2013-10-07T21:49:45.173 回答
2

向 KLE 答案添加详细信息。(对不起,新手用户,不知道比创建单独答案更好的方法)

您可以将其放在 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>

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

于 2009-09-28T10:40:33.413 回答
2

对于 Log4j2,答案完全是通过使用 Log4j2 手册中的Example Usage of a Generated Logger Wrapper中描述的记录器包装器来提供的。可以简单地生成(使用那里说明的 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)

于 2016-02-13T10:20:13.880 回答
1

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

于 2009-09-28T10:00:18.083 回答
1

如果您有自己的日志记录实用程序方法,则可以将行号和文件名添加到日志记录参数列表并采用 cpp 路由。即在编译之前预处理您的源代码以替换_ LINE _ 和_ FILE _ 等标签。作为一个额外的好处,这不会像在运行时计算出那么多资源。

于 2009-09-28T11:32:54.547 回答
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 注释,您将获得您真正想要的正确源代码行号。

于 2009-12-08T05:35:54.890 回答