用 Java 和一般情况登录:最佳实践?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/906233/
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
Logging in Java and in general: Best Practices?
提问by Malax
Sometimes when I see my logging code I wonder if I am doing it right. There might be no definitive answer to that, but I have the following concerns:
有时,当我看到我的日志记录代码时,我想知道我是否做得对。对此可能没有明确的答案,但我有以下担忧:
Library Classes
图书馆类
I have several library classes which might log some INFO
messages. Fatal Errors are reported as exceptions. Currently I have a static logger instance in my classes with the class name as the logging name. (Log4j's: Logger.getLogger(MyClass.class)
)
我有几个库类可能会记录一些INFO
消息。致命错误报告为异常。目前我的类中有一个静态记录器实例,类名作为日志记录名称。(Log4j的:Logger.getLogger(MyClass.class)
)
Is this the right way? Maybe the user of this library class doesn't want any messages from my implementation or wants to redirect them to an application specific log. Should I allow the user to set a logger from the "outside world"? How do you handle such cases?
这是正确的方法吗?也许这个库类的用户不想要来自我的实现的任何消息,或者想要将它们重定向到特定于应用程序的日志。我应该允许用户从“外部世界”设置记录器吗?您如何处理此类案件?
General logs
一般日志
In some applications my classes might want to write a log message to a specific log not identified by the class' name. (I.e.: HTTP Request log
) What is the best way to do such a thing? A lookup service comes to mind...
在某些应用程序中,我的类可能希望将日志消息写入未由类名称标识的特定日志。(即:)HTTP Request log
做这样的事情最好的方法是什么?我想到了查找服务...
采纳答案by cletus
Your conventions are pretty standard and quite fine (imho).
您的约定非常标准且非常好(恕我直言)。
The one thing to watch is memory fragmentation from excessive unnedded debug calls so, with Log4J (and most other Java logging frameworks), you end up with something like this:
需要注意的一件事是过多的 unnedded 调试调用造成的内存碎片,因此,使用 Log4J(和大多数其他 Java 日志框架),您最终会得到如下结果:
if (log.isDebugEnabled()) {
log.debug("...");
}
because constructing that log message (that you probably aren't using) could be expensive, especially if done thousands or millions of times.
因为构建该日志消息(您可能没有使用)可能很昂贵,尤其是在完成数千或数百万次之后。
Your INFO level logging shouldn't be too "chatty" (and from what you say, it sounds like it isn't). INFO messages should be generally meaningful and significant, like the application being started and stopped. Things that you might want to know if you encounter a problem. Debug/fine level logging is more used for when you actually have a problem you're trying to diagnose. Debug/fine logging is typically only turned on when needed. Info is typically on all the time.
您的 INFO 级别日志记录不应太“健谈”(从您所说的来看,听起来并非如此)。INFO 消息通常应该是有意义和重要的,就像正在启动和停止的应用程序一样。如果遇到问题,您可能想知道的事情。调试/精细级别日志记录更多地用于当您尝试诊断实际遇到的问题时。调试/精细日志记录通常仅在需要时打开。信息通常一直都在。
If someone doesn't want specific INFO messages from your classes, they are of course free to change your log4j configuration to not get them. Log4j is beautifully straightforward in this department (as opposed to Java 1.4 logging).
如果有人不想从您的课程中获得特定的 INFO 消息,他们当然可以随意更改您的 log4j 配置以不获取它们。Log4j 在这方面非常简单(与 Java 1.4 日志记录相反)。
As for your HTTP thing, I've generally not found that to be an issue with Java logging because typically a single class is responsible for what you're interested in so you only need to put it in one place. In the (rare in my experience) when you want common log messages across seemingly unrelated classes, just put in some token that can easily be grepped for.
至于你的 HTTP 事情,我通常没有发现这是 Java 日志记录的问题,因为通常一个类负责你感兴趣的东西,所以你只需要把它放在一个地方。在(在我的经验中很少见)当您想要跨看似不相关的类的公共日志消息时,只需放入一些可以轻松获取的标记。
回答by John Feminella
The preferred option for the kind of log4j configuration you're describing is to use the log4j configuration file. This allows users of your implementation to do exactly what you're asking for, since they can override your configuration later on with something more suitable for their own implementation. See herefor a very thorough primer.
您所描述的那种 log4j 配置的首选选项是使用log4j 配置文件。这允许您的实现的用户完全按照您的要求执行操作,因为他们可以稍后使用更适合他们自己的实现的内容来覆盖您的配置。请参阅此处了解非常详尽的入门。
回答by KarlP
I have probably stolen this from somewhere, but it's nice.
我可能从某个地方偷了这个,但这很好。
It reduces the risk of mixing up loggers when copying and pasti^h^h^h refactoring, and it's less to type.
它降低了在复制和粘贴^h^h^h 重构时混淆记录器的风险,并且输入更少。
In your code:
在您的代码中:
private final static Logger logger = LoggerFactory.make();
...and in LoggerFactory:
...在 LoggerFactory 中:
public static Logger make() {
Throwable t = new Throwable();
StackTraceElement directCaller = t.getStackTrace()[1];
return Logger.getLogger(directCaller.getClassName());
}
(Note that the stackdump is done during initialisation. The stacktrace will probablynot be optimized away by the JVM but there are actually no guarantees)
(请注意,堆栈转储是在初始化期间完成的。堆栈跟踪可能不会被 JVM 优化掉,但实际上并没有保证)
回答by justinpitts
With respect to instantiating loggers, I have had some success using an Eclipse Java Template for setting up my loggers:
关于实例化记录器,我使用 Eclipse Java 模板设置记录器取得了一些成功:
private static Logger log = Logger.getLogger(${enclosing_type}.class);
This avoids the problem of a JVM mucking about with your stack trace, and reduces (trivially, perhaps) the overhead from creating the stack trace in the first place.
这避免了 JVM 处理堆栈跟踪的问题,并减少(可能是微不足道的)首先创建堆栈跟踪的开销。
The great thing about using a template like this is that you can share it with your team if you want to set a consistent standard across the board for loggers.
使用这样的模板的好处在于,如果您想为记录器设置一个统一的标准,您可以与您的团队共享它。
It looks like IntelliJ supports the same concept for a template variable representing the name of the enclosing type. I don't see a way to do it easily in NetBeans.
看起来 IntelliJ 支持表示封闭类型名称的模板变量的相同概念。我没有看到在 NetBeans 中轻松完成的方法。
回答by Yauhen
As an addition, I think it's important to mean Simple Logging Facade for Java (SLF4J) (http://www.slf4j.org/). Due to some issues of using different logging frameworks in diversified parts of a big project, SLF4J is the de facto standard to solve a problem to manage these parts successfully, isn't it?
另外,我认为重要的是 Java 的 Simple Logging Facade (SLF4J) ( http://www.slf4j.org/)。由于在一个大项目的不同部分使用不同的日志框架的一些问题,SLF4J 是解决问题以成功管理这些部分的事实上的标准,不是吗?
The second one notion: it's seems that some old-school tasks can be substituted by Aspect-Oriented-Programming, Spring frmwrk has it's own implementation, AOP-approach for logging considered hereat StackOverflow and hereon Spring blog.
第二个概念:它似乎有些老派的任务可以被1-3面向方面编程,春frmwrk有它自己的实现,AOP的方式进行记录认为这里在StackOverflow的和这里的春天博客。
回答by serv-inc
In @cletus' answer, he wrote of the problem of
在@cletus 的回答中,他写到了
if (log.isDebugEnabled()) {
log.debug("val is " + value);
}
which might be overcome by using SL4J. It provides a formatting help
这可以通过使用SL4J来克服。它提供了格式化帮助
log.debug("val is {}", value);
where the message is only constructed if the level is debug.
仅当级别为调试时才构造消息。
So, nowaday, using SL4J and its companion logger, Logback, is advisedfor performance and stability reasons.
因此,现在,出于性能和稳定性的原因,建议使用 SL4J 及其配套记录器 Logback 。
回答by Patrick
I'm reviewing log-levels of an application and I'm currently detecting a pattern:
我正在查看应用程序的日志级别,并且我目前正在检测一种模式:
private static final Logger logger = Logger.getLogger(Things.class)
public void bla() {
logger.debug("Starting " + ...)
// Do stuff
...
logger.debug("Situational")
// Algorithms
for(Thing t : things) {
logger.trace(...)
}
// Breaking happy things
if(things.isEmpty){
logger.warn("Things shouldn't be empty!")
}
// Catching things
try {
...
} catch(Exception e) {
logger.error("Something bad happened")
}
logger.info("Completed "+...)
}
A log4j2-file defines a socket-appender, with a fail-over file-appender. And a console-appender. Sometimes I use log4j2 Markers when the situation requires it.
log4j2-file 定义了一个 socket-appender,带有一个故障转移文件-appender。还有一个控制台附加程序。有时我会在情况需要时使用 log4j2 标记。
Thought an extra perspective might help.
认为一个额外的视角可能会有所帮助。
回答by Saptarshi Basu
The following is the set of guidelines I follow in all my projects to ensure good performance. I have come to form this set of guidelines based on the inputs from various sources in the internet.
以下是我在所有项目中遵循的一套指导方针,以确保良好的性能。我根据互联网上各种来源的输入来形成这套指南。
As on today, I believe Log4j 2 is by far the best option for logging in Java.
和今天一样,我相信 Log4j 2 是迄今为止登录 Java 的最佳选择。
The benchmarks are available here. The practice that I follow to get the best performance is as follows:
可以在此处获得基准。我为获得最佳性能而遵循的做法如下:
- I avoid using SLF4J at the moment for the following reasons:
- It has some concurrency issues with Markers which I want to use to manage the logging of SQL statements (Markers not as powerful as slf4j- Refer to the first comment by Ralph Goers)
- It does not support the Java 8 Lambda which, again, I want to use for better performance (Support the lambda expression in the Logger)
- Do all regular logging using asynchronous logger for better performance
- Log error messages in a separate file using synchronous logger because we want to see the error messages as soon as it occurs
- Do not use location information, such as filename, class name, method name, line number in regular logging because in order to derive those information, the framework takes a snapshot of the stack and walk through it. This impacts the performance. Hence use the location information only in the error log and not in the regular log
- For the purpose of tracking individual requests handled by separate threads, consider using thread context and random UUID as explained here
- Since we are logging errors in a separate file, it is very important that we log the context information also in the error log. For e.g. if the application encountered an error while processing a file, print the filename and the file record being processed in the error log file along with the stacktrace
- Log file should be grep-able and easy to understand. For e.g. if an application processes customer records in multiple files, each log message should be like below:
- 由于以下原因,我目前避免使用 SLF4J:
- 它有一些与标记的并发问题,我想用它来管理 SQL 语句的日志记录(标记不如 slf4j 强大- 请参阅 Ralph Goers 的第一条评论)
- 它不支持 Java 8 Lambda,我想再次使用它以获得更好的性能(支持 Logger 中的 lambda 表达式)
- 使用异步记录器执行所有常规日志记录以获得更好的性能
- 使用同步记录器将错误消息记录在单独的文件中,因为我们希望在错误发生时立即查看错误消息
- 不要在常规日志记录中使用位置信息,例如文件名、类名、方法名、行号,因为为了导出这些信息,框架会拍摄堆栈快照并遍历它。这会影响性能。因此仅在错误日志中使用位置信息,而不在常规日志中使用
- 对于跟踪由单独的线程来处理单个请求的目的,可以考虑使用线程上下文和随机UUID作为解释这里
- 由于我们将错误记录在单独的文件中,因此将上下文信息也记录在错误日志中非常重要。例如,如果应用程序在处理文件时遇到错误,则在错误日志文件中打印文件名和正在处理的文件记录以及堆栈跟踪
- 日志文件应该是 grep-able 且易于理解的。例如,如果应用程序处理多个文件中的客户记录,则每个日志消息应如下所示:
12:01:00,127 INFO FILE_NAME=file1.txt - Processing starts 12:01:00,127 DEBUG FILE_NAME=file1.txt, CUSTOMER_ID=756 12:01:00,129 INFO FILE_NAME=file1.txt - Processing ends
12:01:00,127 INFO FILE_NAME=file1.txt - Processing starts 12:01:00,127 DEBUG FILE_NAME=file1.txt, CUSTOMER_ID=756 12:01:00,129 INFO FILE_NAME=file1.txt - Processing ends
- Log all SQL statements using an SQL marker as shown below and use a filter to enable or disable it:
- 使用 SQL 标记记录所有 SQL 语句,如下所示,并使用过滤器启用或禁用它:
private static final Marker sqlMarker = MarkerManager.getMarker("SQL"); private void method1() { logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE"); }
private static final Marker sqlMarker = MarkerManager.getMarker("SQL"); private void method1() { logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE"); }
- Log all parameters using Java 8 Lambdas. This will save the application from formatting message when the given log level is disabled:
- 使用 Java 8 Lambdas 记录所有参数。当给定的日志级别被禁用时,这将保存应用程序的格式化消息:
int i=5, j=10; logger.info("Sample output {}, {}", ()->i, ()->j);
int i=5, j=10; logger.info("Sample output {}, {}", ()->i, ()->j);
Do not use String concatenation. Use parameterized message as shown above
Use dynamic reloading of logging configuration so that the application automatically reloads the changes in the logging configuration without the need of application restart
Do not use
printStackTrace()
orSystem.out.println()
The application should shut down the logger before exiting:
不要使用字符串连接。使用如上所示的参数化消息
使用日志配置的动态重新加载,以便应用程序自动重新加载日志配置中的更改,而无需重新启动应用程序
不要使用
printStackTrace()
或System.out.println()
应用程序应在退出前关闭记录器:
LogManager.shutdown();
LogManager.shutdown();
- Finally, for everybody's reference, I use the following configuration:
- 最后,供大家参考,我使用如下配置:
<?xml version="1.0" encoding="UTF-8"?> <Configuration monitorinterval="300" status="info" strict="true"> <Properties> <Property name="filePath">${env:LOG_ROOT}/SAMPLE</Property> <Property name="filename">${env:LOG_ROOT}/SAMPLE/sample </Property> <property name="logSize">10 MB</property> </Properties> <Appenders> <RollingFile name="RollingFileRegular" fileName="${filename}.log" filePattern="${filePath}/sample-%d{yyyy-dd-MM}-%i.log"> <Filters> <MarkerFilter marker="SQL" onMatch="DENY" onMismatch="NEUTRAL" /> </Filters> <PatternLayout> <Pattern>%d{HH:mm:ss,SSS} %m%n </Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="${logSize}" /> </Policies> </RollingFile> <RollingFile name="RollingFileError" fileName="${filename}_error.log" filePattern="${filePath}/sample_error-%d{yyyy-dd-MM}-%i.log" immediateFlush="true"> <PatternLayout> <Pattern>%d{HH:mm:ss,SSS} %p %c{1.}[%L] [%t] %m%n </Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="${logSize}" /> </Policies> </RollingFile> </Appenders> <Loggers> <AsyncLogger name="com" level="trace"> <AppenderRef ref="RollingFileRegular"/> </AsyncLogger> <Root includeLocation="true" level="trace"> <AppenderRef ref="RollingFileError" level="error" /> </Root> </Loggers> </Configuration>
<?xml version="1.0" encoding="UTF-8"?> <Configuration monitorinterval="300" status="info" strict="true"> <Properties> <Property name="filePath">${env:LOG_ROOT}/SAMPLE</Property> <Property name="filename">${env:LOG_ROOT}/SAMPLE/sample </Property> <property name="logSize">10 MB</property> </Properties> <Appenders> <RollingFile name="RollingFileRegular" fileName="${filename}.log" filePattern="${filePath}/sample-%d{yyyy-dd-MM}-%i.log"> <Filters> <MarkerFilter marker="SQL" onMatch="DENY" onMismatch="NEUTRAL" /> </Filters> <PatternLayout> <Pattern>%d{HH:mm:ss,SSS} %m%n </Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="${logSize}" /> </Policies> </RollingFile> <RollingFile name="RollingFileError" fileName="${filename}_error.log" filePattern="${filePath}/sample_error-%d{yyyy-dd-MM}-%i.log" immediateFlush="true"> <PatternLayout> <Pattern>%d{HH:mm:ss,SSS} %p %c{1.}[%L] [%t] %m%n </Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="${logSize}" /> </Policies> </RollingFile> </Appenders> <Loggers> <AsyncLogger name="com" level="trace"> <AppenderRef ref="RollingFileRegular"/> </AsyncLogger> <Root includeLocation="true" level="trace"> <AppenderRef ref="RollingFileError" level="error" /> </Root> </Loggers> </Configuration>
- The required Maven dependencies are here:
- 所需的 Maven 依赖项在这里:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.6</version> </dependency> <!-- (Optional)To be used when working with the applications using Log4j 1.x --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>2.8.1</version> </dependency>
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.6</version> </dependency> <!-- (Optional)To be used when working with the applications using Log4j 1.x --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>2.8.1</version> </dependency>