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

vulvrdjw  于 2022-11-06  发布在  Java
关注(0)|答案(9)|浏览(155)

有时当我看到我的日志代码时,我怀疑我是否做对了。可能没有明确的答案,但我有以下顾虑:

程式库类别

我有几个库类,可能会记录一些INFO消息。致命错误被报告为异常。目前我在我的类中有一个静态记录器示例,类名作为记录名称。(Log4j的:(单位:Logger.getLogger(MyClass.class)
这是正确的方式吗?也许这个库类的用户不希望从我的实现中得到任何消息,或者希望将它们重定向到一个应用程序特定的日志。我应该允许用户从“外部世界”设置一个日志记录器吗?你如何处理这种情况?

常规日志

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

new9mtju

new9mtju1#

你的惯例是相当标准和相当罚款(imho)。
需要注意的一件事是过多的不必要的调试调用导致的内存碎片,因此,使用Log4J(以及大多数其他Java日志记录框架),您最终会得到如下结果:

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

因为构造日志消息(您可能没有使用)的成本可能很高,尤其是在进行数千次或数百万次的情况下。
INFO级别的日志记录不应过于“冗长”INFO消息通常应该是有意义和重要的,比如应用程序的启动和停止。当你遇到问题时你可能想知道的事情。调试/精细级日志记录更多地用于当你实际遇到问题时你试图诊断。调试/精细日志记录通常只在需要时打开。2信息通常一直打开。
如果有人不想从您的类中得到特定的INFO消息,他们当然可以自由地更改您的log4j配置以不得到它们。Log4j在这方面非常简单(与Java 1.4日志记录相反)。
至于HTTP,我通常不会发现Java日志记录有什么问题,因为通常一个类负责您感兴趣的内容,所以您只需要将它放在一个地方。在(我的经验中很少见)当您希望在看似不相关的类之间使用公共日志消息时,只需放入一些可以轻松使用grepped的标记。

jckbn6z7

jckbn6z72#

以下是我在所有项目中遵循的一套指导方针,以确保良好的绩效。我是根据互联网上各种来源的输入形成这套指导方针的。
到目前为止,我相信Log4j 2是用Java进行日志记录的最佳选择。
性能指标评测可通过here获得。我为获得最佳性能而遵循的实践如下:

  • 我目前避免使用SLF 4J,原因如下:
  • 它与我想用来管理SQL语句日志的标记器存在一些并发问题(Markers not as powerful as slf4j-请参阅Ralph Goers的第一条评论)
  • 它不支持Java 8 Lambda,我想再次使用它来获得更好的性能(Support the lambda expression in the Logger
  • 使用异步日志记录器执行所有常规日志记录以获得更好的性能
  • 使用同步记录器将错误消息记录在单独的文件中,因为我们希望在错误发生时立即看到错误消息
  • 不要在常规日志记录中使用位置信息,如文件名、类名、方法名、行号,因为为了获得这些信息,框架会获取堆栈的快照并遍历它。这会影响性能。因此,仅在错误日志中使用位置信息,而不要在常规日志中使用
  • 为了跟踪由单独线程处理的各个请求,请考虑使用线程上下文和随机UUID,如here所述
  • 由于我们在单独的文件中记录错误,因此在错误日志中记录上下文信息非常重要。例如,如果应用程序在处理文件时遇到错误,则在错误日志文件中打印文件名和正在处理的文件记录沿着堆栈跟踪
  • 日志文件应支持grep且易于理解。例如,如果应用程序处理多个文件中的客户记录,则每条日志消息应如下所示:
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语句,并使用过滤器启用或禁用它:

private static final Marker sqlMarker = 
  MarkerManager.getMarker("SQL");

private void method1() {
    logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE");
}

1.使用Java 8 Lambda记录所有参数。这将在禁用给定日志级别时保存应用程序格式化消息:

int i=5, j=10;
logger.info("Sample output {}, {}", ()->i, ()->j);

1.不要使用字符串串联。请使用如上所示的参数化消息
1.使用动态重新加载日志配置,以便应用程序自动重新加载日志配置中的更改,而无需重新启动应用程序
1.不要使用printStackTrace()System.out.println()
1.应用程序应在退出之前关闭记录器:

LogManager.shutdown();

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>

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

vawmfj5a3#

在@克里特斯的回答中,他写到了

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

这可以通过使用SLF4J来解决。它提供了格式设置帮助

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

其中仅当级别为调试时才构造消息。
因此,现在,出于性能和稳定性的原因,使用SL 4J及其配套日志记录器Logback是advised

wsewodh2

wsewodh24#

关于示例化记录器,我已经使用Eclipse Java模板成功地设置了我的记录器:

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

这就避免了JVM对堆栈跟踪的干扰问题,并减少了(也许是微不足道的)最初创建堆栈跟踪的开销。
使用这样一个模板的好处是,如果您希望为记录器设置一个一致的标准,您可以与您的团队共享它。
IntelliJ似乎也支持同样的概念,即用模板变量来表示封闭类型的名称。我看不出有什么方法可以在NetBeans中轻松地做到这一点。

k10s72fa

k10s72fa5#

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

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标记器。
我想多看一点可能会有帮助。

mhd8tkvw

mhd8tkvw6#

对于您所描述的log4j配置类型,首选选项是使用log4j配置文件。这允许您的实现的用户完全按照您的要求进行操作,因为他们可以在以后用更适合他们自己的实现的配置来覆盖您的配置。请参阅here以获得非常全面的入门知识。

8yparm6h

8yparm6h7#

我可能是从哪里偷来的,但它很漂亮。
它降低了在复制和后期重构时混淆记录器的风险,并且减少了类型输入。

  • 在您的代码中:*
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());
}

(Note堆栈转储是在初始化过程中完成的。堆栈跟踪 * 可能 * 不会被JVM优化掉,但实际上没有任何保证)

cld4siwp

cld4siwp8#

作为补充,我认为Simple Logging Facade for Java(SLF4J)(http://www.slf4j.org/)是很重要的。由于在大型项目的不同部分使用不同日志记录框架的一些问题,SLF4J是解决成功管理这些部分的问题的事实上的标准,不是吗?
第二个观念:看起来一些老式的任务可以用Aspect-Oriented-Programming来代替,Spring frmwrk有它自己的implementation,用于日志记录的AOP方法在StackOverflow被认为是here,在Spring blog被认为是here

2mbi3lxu

2mbi3lxu9#

对于任何日志程序API,我们至少设置了以下日志级别:错误〉警告〉信息〉调试〉跟踪
而且我们可以使用每个日志级别来编写不同类型的日志,以更好地了解我们收集的跟踪:

跟踪-如果我们在每个方法的入口点用方法名和方法参数以及在出口点用返回值/对象写一个跟踪,

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

Debug-我们将在方法中间添加调试日志,以显示满足了哪个if/else/switch条件,以及我们从数据库中获取的数据和在方法中使用的数据等。注意-不要在调试中添加那些作为参数发送或作为值返回的数据,因为这些数据已经由跟踪级别打印(尽量不要多次打印相同的日志)。
信息-假设客户端具有日志级别信息,那么如果他们看到日志,您希望向他们显示什么消息和所有内容,因此将这些内容添加到信息中。示例- Blabla连接已成功创建/删除/修改,或Blabla链接已锁定/解锁,或Blabla节点已触发Blabla同步。
警告-这是一种罕见的情况,但在编写代码时,我们会遇到一些在正常情况下不可能出现的情况,它只会由于任何陈旧的条目或发生任何损坏而出现,通常我们会忽略这种情况,但是如果我们在这里加上这个条件和战争日志会更好。例如-我在一个表中查询,查询条件是列不是主键或唯一的,但被告知它将始终只返回一行,get(0)也是如此,因此在这种情况下,我们应该编写一个条件,如如果resultSet.size〉1,添加一些带有更好消息警告日志。
错误-错误日志应出现在每个不期望的catch块中,并且应正确打印完整的堆栈跟踪(而且不仅仅是错误消息)。在catch块中,人们也会抛出一个新的异常,而不记录现有的异常跟踪,在这种情况下,我们不会获得异常的实际点。因此,在每个catch块中编写具有完整堆栈跟踪的错误日志是非常强制性的。

相关问题