Java 日志记录和一般情况:最佳实践?

发布于 2024-07-21 05:22:17 字数 462 浏览 6 评论 0 原文

有时,当我看到我的日志代码时,我想知道我做得是否正确。 对此可能没有明确的答案,但我有以下顾虑:

库类

我有几个库类可能会记录一些 INFO 消息。 致命错误被报告为异常。 目前,我的类中有一个静态记录器实例,其类名作为日志记录名称。 (Log4j 的:Logger.getLogger(MyClass.class))

这是正确的方法吗? 也许这个库类的用户不想要来自我的实现的任何消息,或者想要将它们重定向到应用程序特定的日志。 我应该允许用户从“外部世界”设置记录器吗? 您如何处理此类情况?

常规日志

在某些应用程序中,我的类可能希望将日志消息写入未由类名称标识的特定日志。 (即:HTTP 请求日志)执行此类操作的最佳方法是什么? 我想到了查找服务......

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))

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...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

想念有你 2024-07-28 05:22:17

你们的约定非常标准而且很好(恕我直言)。

需要注意的一件事是过多的无用调试调用产生的内存碎片,因此,使用 Log4J(以及大多数其他 Java 日志框架),您最终会得到这样的结果:

if (log.isDebugEnabled()) {
  log.debug("...");
}

因为构造该日志消息(您可能没有使用)可能是昂贵,特别是如果执行数千或数百万次。

您的 INFO 级别日志记录不应该太“喋喋不休”(从您的说法来看,听起来似乎并非如此)。 INFO 消息通常应该是有意义且重要的,例如正在启动和停止的应用程序。 如果您遇到问题,您可能想知道的事情。 调试/精细级别日志记录更多地用于当您确实遇到要尝试诊断的问题时。 调试/精细日志记录通常仅在需要时打开。 信息通常始终处于开启状态。

如果有人不想要来自您的类的特定 INFO 消息,他们当然可以随意更改您的 log4j 配置以不获取它们。 Log4j 在这方面非常简单(与 Java 1.4 日志记录相反)。

至于你的 HTTP 事情,我通常不认为这是 Java 日志记录的问题,因为通常单个类负责你感兴趣的内容,所以你只需要把它放在一个地方。 在(在我的经验中很少见)当您想要跨看似不相关的类的通用日志消息时,只需放入一些可以轻松查找的标记即可。

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:

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.

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).

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.

掌心的温暖 2024-07-28 05:22:17

以下是我在所有项目中遵循的一套准则,以确保良好的性能。 我根据互联网上各种来源的输入形成了这套指南。

到目前为止,我相信 Log4j 2 是迄今为止 Java 日志记录的最佳选择。

基准测试可在此处获取。 为了获得最佳性能,我遵循的做法如下:

  1. 出于以下原因,我目前避免使用 SLF4J:
  2. 使用异步记录器执行所有常规日志记录以获得更好的性能
  3. 使用以下命令将错误消息记录在单独的文件中同步记录器,因为我们希望在错误发生时立即看到错误消息
  4. 不要在常规日志记录中使用位置信息,例如文件名、类名、方法名、行号,因为为了导出这些信息,框架会拍摄以下信息的快照堆栈并遍历它。 这会影响性能。 因此,仅在错误日志中使用位置信息,而不是在常规日志中使用
  5. ,以跟踪由单独线程处理的单个请求,请考虑使用线程上下文和随机UUID,如说明此处
  6. 由于我们在单独的文件中记录错误,因此将上下文信息也记录在错误日志。 例如,如果应用程序在处理文件时遇到错误,则打印错误日志文件中正在处理的文件名和文件记录以及堆栈
  7. 跟踪日志文件应该是可 grep 的且易于理解。 例如,如果应用程序处理多个文件中的客户记录,则每个日志消息应如下所示:
12:01:00,127 INFO FILE_NAME=file1.txt - 处理开始 
  12:01:00,127 调试 FILE_NAME=file1.txt,CUSTOMER_ID=756 
  12:01:00,129 INFO FILE_NAME=file1.txt - 处理结束 
  
  1. 使用 SQL 标记记录所有 SQL 语句,如下所示,并使用过滤器启用或禁用它:
私有静态最终标记 sqlMarker =  
    MarkerManager.getMarker("SQL"); 

  私有无效方法1() { 
      logger.debug(sqlMarker, "从员工中选择 *"); 
  } 
  
  1. 使用 Java 8 Lambda 记录所有参数。 当给定的日志级别被禁用时,这将使应用程序免于格式化消息:
int i=5, j=10; 
  logger.info("样本输出{}, {}", ()->i, ()->j); 
  
  1. 不要使用字符串连接。 使用如上所示的参数化消息

  2. 使用动态重新加载日志记录配置,以便应用程序自动重新加载日志记录配置中的更改,而无需重新启动应用程序

  3. 不要使用 printStackTrace()System.out.println()

  4. 应用程序应在退出前关闭记录器:

LogManager.shutdown(); 
  
  1. 最后,供大家参考,我使用如下配置:
 
  <配置monitorinterval="300" status="info" strict="true"> 
      <属性> 
          <属性名称=“文件路径”>${env:LOG_ROOT}/SAMPLE 
          <属性名称=“文件名”>${env:LOG_ROOT}/SAMPLE/sample 
           
          <属性名称=“logSize”>10 MB 
       
      <附加器> 
           
              <过滤器> 
                   
               
              <图案布局> 
                  <模式>%d{HH:mm:ss,SSS} %m%n 
                   
               
              <政策> 
                  <基于时间的触发策略 
                      间隔=“1”调制=“真”/> 
                  <基于大小的触发策略 
                      大小=“$ {logSize}”/> 

               
           
           
              <图案布局> 
                  <模式>%d{HH:mm:ss,SSS} %p %c{1.}[%L] [%t] %m%n 
                   
               
              <政策> 
                  <基于时间的触发策略 
                      间隔=“1”调制=“真”/> 
                  <基于大小的触发策略 
                      大小=“$ {logSize}”/> 
               
           
       
      <记录器> 
           
              > 
           
          <根 includeLocation="true" level="trace"> 
              > 
           
       
   
  
  1. 所需的 Maven 依赖项在这里:
<依赖>; 
      org.apache.logging.log4j 
      log4j-api; 
      <版本>2.8.1 
   
  <依赖关系> 
      org.apache.logging.log4j 
      log4j-core; 
      <版本>2.8.1 
   
  <依赖> 
      com.lmax; 
      disruptor 
      <版本>3.3.6 
   
   
  <依赖关系> 
      org.apache.logging.log4j 
      log4j-1.2-api; 
      <版本>2.8.1 
   
  

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.

The benchmarks are available here. The practice that I follow to get the best performance is as follows:

  1. I avoid using SLF4J at the moment for the following reasons:
  2. Do all regular logging using asynchronous logger for better performance
  3. Log error messages in a separate file using synchronous logger because we want to see the error messages as soon as it occurs
  4. 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
  5. For the purpose of tracking individual requests handled by separate threads, consider using thread context and random UUID as explained here
  6. 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
  7. 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:
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
  1. Log all SQL statements using an SQL marker as shown below and use a filter to enable or disable it:
private static final Marker sqlMarker = 
  MarkerManager.getMarker("SQL");

private void method1() {
    logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE");
}
  1. Log all parameters using Java 8 Lambdas. This will save the application from formatting message when the given log level is disabled:
int i=5, j=10;
logger.info("Sample output {}, {}", ()->i, ()->j);
  1. Do not use String concatenation. Use parameterized message as shown above

  2. Use dynamic reloading of logging configuration so that the application automatically reloads the changes in the logging configuration without the need of application restart

  3. Do not use printStackTrace() or System.out.println()

  4. The application should shut down the logger before exiting:

LogManager.shutdown();
  1. 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>
  1. The required Maven dependencies are here:
<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>
强辩 2024-07-28 05:22:17

@cletus 的回答中,他写道

if (log.isDebugEnabled()) {
  log.debug("val is " + value);
}

可以通过使用 SLF4J。 它提供了格式化帮助

log.debug("val is {}", value);

,其中仅当级别为调试时才构造消息。

因此,出于性能和稳定性原因,现在建议使用 SL4J 及其配套记录器 Logback 。

In @cletus' answer, he wrote of the problem of

if (log.isDebugEnabled()) {
  log.debug("val is " + value);
}

which might be overcome by using SLF4J. It provides a formatting help

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 advised for performance and stability reasons.

只为一人 2024-07-28 05:22:17

关于实例化记录器,我使用 Eclipse Java 模板来设置记录器取得了一些成功:

private static Logger log = Logger.getLogger(${enclosing_type}.class);

这避免了 JVM 处理堆栈跟踪的问题,并减少了(也许是微不足道的)创建堆栈跟踪的开销首先。

使用这样的模板的好处是,如果您想为记录器全面设置一致的标准,您可以与您的团队共享它。

看起来 IntelliJ 支持表示封闭类型名称的模板变量的相同概念。 我没有找到在 NetBeans 中轻松完成此操作的方法。

With respect to instantiating loggers, I have had some success using an Eclipse Java Template for setting up my loggers:

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.

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.

深爱成瘾 2024-07-28 05:22:17

我可能是从某个地方偷来的,但它很好。

它降低了复制和 Pasti^h^h^h 重构时混淆记录器的风险,并且打字更少。

在您的代码中:

private final static Logger logger = LoggerFactory.make();

...以及在 LoggerFactory 中:

public static Logger make() {
    Throwable t = new Throwable();
    StackTraceElement directCaller = t.getStackTrace()[1];
    return Logger.getLogger(directCaller.getClassName());
}

(请注意,堆栈转储是在初始化期间完成的。堆栈跟踪可能不会被优化掉由 JVM 但实际上没有保证)

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.

In your code:

private final static Logger logger = LoggerFactory.make();

...and in 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 probably not be optimized away by the JVM but there are actually no guarantees)

剩一世无双 2024-07-28 05:22:17

我正在检查应用程序的日志级别,并且当前正在检测一种模式:

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 "+...)
}

log4j2 文件定义了一个套接字附加程序,以及一个故障转移文件附加程序。 和一个控制台附加程序。 有时,当情况需要时我会使用 log4j2 标记。

认为额外的视角可能会有所帮助。

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.

Thought an extra perspective might help.

挥剑断情 2024-07-28 05:22:17

您所描述的 log4j 配置类型的首选选项是使用 log4j 配置文件。 这允许您的实现的用户完全按照您的要求进行操作,因为他们可以稍后使用更适合他们自己的实现的内容覆盖您的配置。 请参阅此处了解非常详尽的入门知识。

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 here for a very thorough primer.

浅笑轻吟梦一曲 2024-07-28 05:22:17

作为补充,我认为重要的是指 Simple Logging Facade for Java (SLF4J) (http://www.slf4j。 org/)。 由于在大型项目的不同部分使用不同的日志框架会出现一些问题,SLF4J 是解决成功管理这些部分的问题的事实上的标准,不是吗?

第二个概念:似乎一些老式任务可以被 面向方面编程替代,Spring frmwrk 有它自己的实现这里考虑了 AOP 日志记录方法 StackOverflow 和 Spring 博客上的此处

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?

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 here at StackOverflow and here on Spring blog.

燃情 2024-07-28 05:22:17

对于任何记录器 API,我们至少设置以下日志级别:
错误> 警告> 信息> 调试> TRACE

我们可以使用每个日志级别来写入不同类型的日志,以更好地理解我们收集的跟踪:

Trace – 如果我们在入口点的每个方法中使用方法名称编写跟踪,那就更好了和方法参数以及带有返回值/对象的退出点,

注意 - 最好遵循我们的编码指南并模块化编写方法,那么在这种情况下,我们不需要在方法之间写入多个日志行打印数据。

调试 – 我们将在方法中间添加调试日志,以显示满足哪些 if/else/switch 条件,以及我们从数据库获取的数据并在方法中使用它等等。
注意 – 不要在调试中添加那些作为参数发送或作为值返回的数据,因为这些数据已经通过跟踪级别打印(尽量不要多次打印相同的日志)。

信息 – 假设客户端有日志级别信息,那么如果他们看到日志,您想向他显示什么消息以及所有内容,因此将这些内容添加到信息中。 示例 – Blabla 连接已成功创建/删除/修改,或者 Blabla 链接已锁定/解锁,或者为 blabla 节点触发了 blabla 同步。

警告 – 这是罕见的情况,但在编写代码时,我们遇到了一些在正常情况下不可能出现的情况,它只是由于任何过时的条目或发生任何损坏而出现,通常我们忽略这种情况,但如果我们添加这样的条件并在那里添加交战日志会更好。 示例 – 我正在从一个表中查询,该表的列不是主键或唯一的条件,但被告知它总是只返回一行,所以 get(0) 也是如此,因此在这种情况下我们应该写一个条件,例如 if resultSet.size > 1 添加一些带有更好消息的警告日志。

错误 – 错误日志应该出现在每个意外的 catch 块中,并且应该正确打印完整的堆栈跟踪(而不仅仅是错误消息)。 此外,在 catch 块中,人们抛出一个新的异常,而不记录现有的异常跟踪,在这种情况下,我们无法获得实际的异常点。 因此,在每个 catch 块中写入带有完整堆栈跟踪的错误日志是非常必要的。

For anylogger API, we have at leaset these Log Levels:
ERROR > WARN > INFO > DEBUG > TRACE

And we can use each log level to write different types of logs to achieve better understanding of our collected traces:

Trace – It would be better if we write a trace in every method at entry point with method name and method argument as well as at exit point with return value/object,

Note – It is better to follow our coding guidelines and write the methods modular, then in that case we don’t need to write multiple logs line in between the method to print the data.

Debug – Debug log we will add in middle of method to show which if/else/switch condition got satisfied, also the data which we get it from DB and using it in the method and so on.
Note –don’t add those data in debug which is being send as an argument or return as a value, since those data already getting printed by Trace level (try not to print same logs multiple times).

Info – Imagine client has log level info, so what message and all you want to show him if they see the log, so add those things in info. Example – Blabla connection created/deleted/modified successfully or Blabla link locked/Unlocked or blabla sync triggered for blabla node/nodes.

Warn – It is rare condition but while writing the code we come across some condition which is not possible in normal case, it only come due to any stale entry or any breakage happens, normally we ignore this condition, but it would be better if we add such condition and add warring log there. Example – I was querying from one table with condition on column which is not primary key or unique but it was told that it will always return only one row so do get(0), so in such case we should write one condition like if resultSet.size > 1 add some warning log with better messages.

Error – Error log should be present in every catch block which is not expected, and it should print complete stack trace properly (and not only the error message). Also in catch block people are throwing a new Exception without logging existing Exception trace, in such scenario we do not get the actual point of exception. So, writing Error log in every catch block with complete stack trace is very much mandatory.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文