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

Mal*_*lax 64 java logging log4j

有时当我看到我的日志代码时,我想知道我是否做得对.可能没有明确的答案,但我有以下问题:

图书馆课程

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

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

一般日志

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

cle*_*tus 38

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

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

if (log.isDebugEnabled()) {
  log.debug("...");
}
Run Code Online (Sandbox Code Playgroud)

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

你的INFO级别日志记录不应该太"健谈"(从你说的,听起来不是这样).INFO消息通常应该是有意义且重要的,例如启动和停止应用程序.如果遇到问题,您可能想知道的事情.当您确实遇到正在尝试诊断的问题时,调试/精细级别日志记录会更常用.调试/精细记录通常仅在需要时打开.信息通常始终在线.

如果有人不想从您的类中获取特定的INFO消息,他们当然可以自由地更改您的log4j配置以获取它们.Log4j在这个部门非常简单(而不是Java 1.4日志记录).

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

  • 这是使用slf4j提供的{} -construct的另一个好理由. (7认同)
  • 作为一个fyi,在Scala中,可以编写一个调试方法来完成该代码,而不使用详细参数.编译器会自动将debug()参数中的代码转换为仅在debug()决定调用它时才进行求值的函数.因此,除非isDebugEnabled()为true,否则不会连接字符串.实现此目的的示例代码:override def debug(msg:=> AnyRef)= if(isDebugEnabled)logger.debug(msg)http://github.com/dpp/liftweb/blob/338119b8d7a76adcb9f45e9aa8a2b946d9c81118/lift-util/src/main /scala/net/liftweb/util/Log.scala (3认同)

Sap*_*asu 10

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

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

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

  1. 由于以下原因,我现在避免使用SLF4J:
  2. 使用异步记录器执行所有常规日志记录以获得更好的性
  3. 使用同步记录器在单独的文件中记录错误消息,因为我们希望一旦发生错误消息就会看到错误消息
  4. 不要在常规日志记录中使用位置信息,例如文件名,类名,方法名,行号,因为为了获取这些信息,框架会获取堆栈的快照并遍历它.这会影响性能.因此,仅在错误日志中使用位置信息,而不是在常规日志中使用
  5. 对于跟踪由单独的线程来处理单个请求的目的,可以考虑使用线程上下文和随机UUID作为解释这里
  6. 由于我们在单独的文件中记录错误,因此我们在错误日志中记录上下文信息非常重要.例如,如果应用程序在处理文件时遇到错误,请在错误日志文件中打印文件名和正在处理的文件记录以及堆栈跟踪
  7. 日志文件应该易于理解且易于理解.例如,如果应用程序处理多个文件中的客户记录,则每条日志消息应如下所示:
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
Run Code Online (Sandbox Code Playgroud)
  1. 使用SQL标记记录所有SQL语句,如下所示,并使用过滤器启用或禁用它:
private static final Marker sqlMarker = 
  MarkerManager.getMarker("SQL");

private void method1() {
    logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE");
}
Run Code Online (Sandbox Code Playgroud)
  1. 使用Java 8 Lambdas记录所有参数.这将在禁用给定日志级别时保存应用程序的格式化消息:
int i=5, j=10;
logger.info("Sample output {}, {}", ()->i, ()->j);
Run Code Online (Sandbox Code Playgroud)
  1. 不要使用字符串连接.使用参数化消息,如上所示

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

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

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

LogManager.shutdown();
Run Code Online (Sandbox Code Playgroud)
  1. 最后,对于每个人的参考,我使用以下配置:
<?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>
Run Code Online (Sandbox Code Playgroud)
  1. 所需的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>
Run Code Online (Sandbox Code Playgroud)


ser*_*inc 9

@cletus的回答中,他写到了问题

if (log.isDebugEnabled()) {
  log.debug("val is " + value);
}
Run Code Online (Sandbox Code Playgroud)

这可以通过使用SL4J来克服.它提供格式化帮助

log.debug("val is {}", value);
Run Code Online (Sandbox Code Playgroud)

仅在级别为debug时才构造消息.

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

  • 由于Java不支持[名称调用](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name),因此"评估"一词可能会有点误导.但是,[参数化消息](http://slf4j.org/faq.html#logging_performance)不会"在禁用日志语句的情况下产生参数构造的成本".正如您已经指出的那样,这更加简洁并且可以带来更好的性能. (2认同)
  • 我有个问题.如果我们将变量:`value`更改为方法:`getValue()`.并且该方法可能花费大量时间.`if(log.isDebugEnabled()){log.debug("val is"+ getValues());}`的成本与`log.debug("val is {}",getValue())相同;` (2认同)

jus*_*tts 6

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

private static Logger log = Logger.getLogger(${enclosing_type}.class);
Run Code Online (Sandbox Code Playgroud)

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

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

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


Kar*_*rlP 5

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

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

在你的代码中:

private final static Logger logger = LoggerFactory.make();
Run Code Online (Sandbox Code Playgroud)

...以及 LoggerFactory 中:

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

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

  • 请记住,虚拟机可以随意省略堆栈帧或执行其他优化,因此您最终可能会得到一个空的 StackTraceElements 数组。有关详细信息,请参阅 http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Throwable.html#getStackTrace()。 (11认同)

Pat*_*ick 5

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

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 "+...)
}
Run Code Online (Sandbox Code Playgroud)

log4j2-file 定义了一个 socket-appender,带有一个故障转移文件-appender。还有一个控制台附加程序。有时我会在情况需要时使用 log4j2 标记。

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