登录Java和一般:最佳实践?

时间:2009-05-25 10:45:40

标签: java logging log4j

有时当我看到我的日志代码时,我想知道我是否正确行事。可能没有明确的答案,但我有以下问题:

图书馆课程

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

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

常规日志

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

8 个答案:

答案 0 :(得分:37)

你的约定非常标准,非常好(imho)。

要注意的一件事是来自过多的无限制调试调用的内存碎片,因此,对于Log4J(以及大多数其他Java日志框架),您最终会得到类似这样的内容:

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

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

您的INFO级别日志记录不应该太“繁琐”(并且从您所说的,听起来不是这样)。 INFO消息通常应该是有意义且重要的,例如启动和停止应用程序。如果遇到问题,您可能想知道的事情。当您确实遇到正在尝试诊断的问题时,调试/精细级别日志记录会更多地用于。调试/精细记录通常仅在需要时打开。信息通常一直在上。

如果某人不希望您的课程中有特定的INFO消息,他们当然可以自由更改您的log4j配置以获取它们。 Log4j在这个部门非常简单(与Java 1.4日志记录相反)。

至于你的HTTP事情,我一般都没有发现这是Java日志记录的问题,因为通常一个类负责你感兴趣的内容,所以你只需要把它放在一个地方。在(在我的经验中很少见)当你想要看似不相关的类的常见日志消息时,只需输入一些可以轻易获取的令牌。

答案 1 :(得分:7)

@cletus' answer中,他写了

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

可以使用SL4J克服。它提供格式化帮助

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

只有在级别为debug时才构造消息。

所以,现在,出于性能和稳定性原因,使用SL4J及其伴随记录器Logback为advised

答案 2 :(得分:7)

以下是我在所有项目中遵循的准则,以确保良好的表现。我已经根据互联网上各种来源的输入来制定这套指导方针。

就像今天一样,我相信Log4j 2是迄今为止用于登录Java的最佳选择。

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

  1. 由于以下原因,我现在避免使用SLF4J:
  2. 使用异步记录器进行所有常规日志记录以获得更好的性能
  3. 使用同步记录器在单独的文件中记录错误消息,因为我们希望在出现错误消息时立即看到错误消息
  4. 不要在常规日志记录中使用位置信息,例如文件名,类名,方法名,行号,因为为了获取这些信息,框架会获取堆栈的快照并遍历它。这会影响性能。因此,仅在错误日志中使用位置信息,而不是在常规日志中使用
  5. 为了跟踪由不同线程处理的各个请求,请考虑使用线程上下文和随机UUID,如解释here
  6. 由于我们在单独的文件中记录错误,因此在错误日志中记录上下文信息非常重要。对于例如如果应用程序在处理文件时遇到错误,请在错误日志文件中打印文件名和正在处理的文件记录以及stacktrace
  7. 日志文件应该易于理解且易于理解。对于例如如果应用程序处理多个文件中的客户记录,则每条日志消息应如下所示:
  8. 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. 使用SQL标记记录所有SQL语句,如下所示,并使用过滤器启用或禁用它:
    2. private static final Marker sqlMarker = 
        MarkerManager.getMarker("SQL");
      
      private void method1() {
          logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE");
      }
      
      1. 使用Java 8 Lambdas记录所有参数。这将在禁用给定日志级别时保存应用程序格式化消息:
      2. int i=5, j=10;
        logger.info("Sample output {}, {}", ()->i, ()->j);
        
        1. 不要使用字符串连接。使用参数化消息,如上所示

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

        3. 请勿使用printStackTrace()System.out.println()

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

        5. LogManager.shutdown();
          
          1. 最后,对于每个人的参考,我使用以下配置:
          2. <?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. 所需的Maven依赖项如下:
            2. <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>
              

答案 3 :(得分:6)

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

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

这样可以避免JVM在堆栈跟踪中出现问题,并且可以减少(通常可能)创建堆栈跟踪的开销。

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

看起来IntelliJ支持表示封闭类型名称的模板变量的相同概念。我没有看到在NetBeans中轻松实现这一目标的方法。

答案 4 :(得分:4)

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

答案 5 :(得分:4)

我可能从某个地方偷了这个,但这很好。

它可以降低复制和消息时混合记录器的风险,并且可以减少重写次数。

在您的代码中:

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

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

答案 6 :(得分:4)

我正在审核某个应用程序的日志级别,而且我目前正在检测某个模式:

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文件定义了一个socket-appender,带有一个故障转移文件追加器。还有一个控制台appender。有时我会在情况需要时使用log4j2标记。

认为额外的观点可能有所帮助。

答案 7 :(得分:2)

另外,我认为简单的Java日志记录(SLF4J)(http://www.slf4j.org/)很重要。由于在大项目的多样化部分中使用不同的日志框架的一些问题,SLF4J是解决成功管理这些部分的问题的事实上的标准,不是吗?

第二个概念:似乎有些老派任务可以用Aspect-Oriented-Programming代替,Spring frmwrk有自己的implementation,AOP方法用于记录在StackOverflow上考虑了here,在Spring博客上考虑了here