Java Logging:显示调用者的源代码行号(不是日志帮助方法)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1486233/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Logging: show the source line number of the caller (not the logging helper method)
提问by Thilo
The numerous (sigh...) logging frameworks for Java all do a nice job of showing the line number of the source file name for the method that created the log message:
Java 的众多(叹息...)日志框架都很好地显示了创建日志消息的方法的源文件名的行号:
log.info("hey");
[INFO] [Foo:413] hey
But if have a helper method in between, the actual caller will be the helper method, and that is not too informative.
但是如果在两者之间有一个辅助方法,实际的调用者将是辅助方法,这不会提供太多信息。
log_info("hey");
[INFO] [LoggingSupport:123] hey
Is there a way to tell the logging system to remove one frame from the callstack when figuring out the source location to print?
有没有办法告诉日志系统在确定要打印的源位置时从调用堆栈中删除一帧?
I suppose that this is implementation specific; what I need is Log4J via Commons Logging, but I am interested to hear about other options.
我想这是特定于实现的;我需要的是通过 Commons Logging 的 Log4J,但我有兴趣了解其他选项。
回答by KLE
Please note that giving the line number is something very costly, either for what you get naturally from Log4j or the following. You have to accept that cost...
请注意,对于您从 Log4j 或以下内容中自然获得的内容,提供行号非常昂贵。你必须接受这个代价……
You could use the following APIs:
您可以使用以下 API:
StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
StackTraceElement stackTraceElement = ...;
stackTraceElement.getLineNumber();
Updated:
更新:
You would have to calculate it yourself. So:
你必须自己计算。所以:
- ask log4j not to output it (in your logging format),
- and insert yourself the line number explicitement in the beginning of your message (the String you send to log4j).
- 要求 log4j 不要输出它(以您的日志格式),
- 并在消息的开头插入自己的行号显式(您发送到 log4j 的字符串)。
Depending how you prefer your loggers, your helper method may:
根据您喜欢记录器的方式,您的辅助方法可能会:
- use an explicit Logger (passed as a parameter I guess), when appropriate (we sometimes define specific loggers for specific context ; for example, we have a logger for sending our database requests, no matter what class does it ; this allow us to reduce to one place the changes made to our configuration file, when we want to (de-)activate them ...)
- use a Logger for the calling class : in this case, instead of passing the parameter, you can deduce the caller class namelikewise...
- 在适当的时候使用显式的 Logger(我猜是作为参数传递的)(我们有时会为特定的上下文定义特定的记录器;例如,我们有一个记录器用于发送我们的数据库请求,无论它是什么类;这允许我们减少当我们想要(取消)激活它们时,将对我们的配置文件所做的更改放在一个地方...)
- 对调用类使用 Logger :在这种情况下,您可以类似地推导出调用者类名,而不是传递参数...
回答by Aaron Digulla
This isn't possible out of the box. The best you can do in this case is to create the logger in the caller and pass it to the util method. This way, you can at least get an idea where the call has come from.
这是开箱即用的。在这种情况下,您可以做的最好的事情是在调用者中创建记录器并将其传递给 util 方法。这样,您至少可以了解呼叫的来源。
回答by vdr
Adding details to KLE answer. (sorry, noob user, don't know better way than creating a separate answer )
向 KLE 答案添加详细信息。(对不起,菜鸟用户,不知道比创建单独的答案更好的方法)
Instead of sticking the line number to the message, you can put it in the MDC context. See org.apache.log4j.MDC
您可以将其放在 MDC 上下文中,而不是将行号粘贴到消息中。参见 org.apache.log4j.MDC
For example:
例如:
StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
StackTraceElement stackTraceElement = ...;
int l = stackTraceElement.getLineNumber();
MDC.put("myLineNumber", l);
That allows users to use mylineNumber in their log4j configuration file
这允许用户在他们的 log4j 配置文件中使用 mylineNumber
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="Line(%X{myLineNumber})- %m%n"/>
</layout>
Note: that allows the user to control where and how the line number appears in the message. However, since getting the stacktrace is very costly, you still need to find a way to switch off the feature.
注意:这允许用户控制行号出现在消息中的位置和方式。但是,由于获取堆栈跟踪的成本非常高,因此您仍然需要找到关闭该功能的方法。
回答by vdr
Alternative answer.
替代答案。
It is possible to ask log4j to exclude the helper class by using the method
可以通过使用方法要求 log4j 排除 helper 类
Category.log(String callerFQCN, Priority level, Object message, Throwable t)
Category.log(String callerFQCN, Priority level, Object message, Throwable t)
and specifying the helper class as 'callerFQCN'.
并将帮助程序类指定为“callerFQCN”。
For example here is a class using a helper:
例如,这里是一个使用助手的类:
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");
}}
and the code of the helper:
和助手的代码:
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);
} }
The first method will output your expected result.
第一种方法将输出您的预期结果。
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
When using this method, Log4j will work as usual, avoiding calculating the stack trace if it is not required.
使用此方法时,Log4j 将照常工作,如果不需要,则避免计算堆栈跟踪。
回答by vdr
If you have your own logging utility methods, you could add linenumber and filename to the logging argument list and take the cpp route. i.e. Preprocess you source to replace tags like _ LINE_ and _ FILE_ before you do the compile. As an added bonus this would not take nerly as much resources as figuring out at runtime.
如果您有自己的日志实用程序方法,则可以将行号和文件名添加到日志参数列表并采用 cpp 路由。即在编译之前预处理您的源代码以替换 _ LINE_ 和 _ FILE_等标签。作为一个额外的好处,这不会像在运行时找出那么多资源。
回答by Xiè Jìléi
Maybe you can implement the log helper function using the stack trace element, get the line numbers, and bypass the frames with method with some specific annotations, like,
也许您可以使用堆栈跟踪元素实现日志帮助器功能,获取行号,并使用带有一些特定注释的方法绕过帧,例如,
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());
}
Thus, if the helper function is nested, or there are more utilized helper functions, just mark the SkipFrame annotation on all of them and you will get the correct source line number what you really wanted.
因此,如果辅助函数是嵌套的,或者有更多使用的辅助函数,只需在所有辅助函数上标记 SkipFrame 注释,您将获得您真正想要的正确源代码行号。
回答by savemaxim
Comes out that there is a very simple solution, just add FQCN(The wrapper class' fully qualified class name) to your logger helper:
出来有一个非常简单的解决方案,只需将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...
In Your working class you just do:
在您的工人阶级中,您只需:
public class MyClass {
private static final Logger LOG = MyLogger.getLogger();
private void test()
{
LOG.info("test");
}
}
回答by Webel IT Australia - upvoter
For Log4j2 the answer is provided completely by the use of logger wrappers as described in the Log4j2 manual under Example Usage of a Generated Logger Wrapper. One can simply generate (using the org.apache.logging.log4j.core.tools.Generate$ExtendedLogger tools illustrated there) a logger wrapper with a single STUB level, and then adapt that to create custom logging methods mimicking the use of the logIfEnabled(FQCN, LEVEL, Marker, message, Throwable) - possibly ignoring the STUB level and using the regular ones - then if desired, deleting or commenting out the STUB level and its methods). For this purpose the FormattedMessage can be helpful.
对于 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 可能会有所帮助。
The source line, while expensive, can then be easily shown as part of the full location information by using the %l location conversion pattern element in the PatternLayoutgiven in the configuration, or more specifically using the %L line number and/or the %M method conversion.
源代码行虽然很昂贵,但可以通过使用配置中给出的PatternLayout 中的 %l 位置转换模式元素,或更具体地使用 %L 行号和/或 % M方法转换。
Now with complete example at: Java Logging: Log4j Version2.x: show the method of an end-client caller (not an intermediate logging helper method)