Home
img of docs

解析在应用程序中记录日志可能对性能产生的影响,包括I/O开销、日志格式化和同步/异步记录等因素。

chou403

/ Log

/ c:

/ u:

/ 6 min read


介绍

很多时候,日志的记录都经常被大家忽略,因为对于性能要求不高的场景中,记录日志确实是可以忽略的。

但是在很多核心业务链路中,1-2ms的影响都很大,所以就会考虑对写日志环境进行性能优化。

解决日志慢的问题,主要有两种方案,一个是异步,一个是降级。

因为日志写入操作通常是一个相对耗时的IO操作,如果每次日志记录都同步写入磁盘,可能会导致主线程阻塞,影响应用程序的性能。通过使用异步日志,主线程可以继续执行其他任务,而日志写入操作则由后台线程负责处理,提高了应用程序的响应性和吞吐量。

目前常用的日志框架,如Log4j2,Logback等都是支持异步日志的。都提供了AsyncAppender来实现异步的日志写入。

以下是一些优化日志记录以最小化性能影响的策略:

1. 异步日志记录

异步日志记录可以显著减少对应用程序性能的影响。它允许日志记录操作在单独的线程中进行,从而不阻塞应用程序的主要业务逻辑。

  • Logback 中的异步日志记录:
   <configuration>
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="FILE"/>
    </appender>
</configuration>
  • Log4j2 中的异步日志记录:
   <Configuration>
    <Appenders>
        <Async name="AsyncAppender">
            <AppenderRef ref="File"/>
        </Async>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="AsyncAppender"/>
        </Root>
    </Loggers>
</Configuration>

2. 日志级别

仅记录必要级别的日志。避免在生产环境中记录 DEBUGTRACE 级别的日志,因为这些日志级别会生成大量日志数据。

  • 将日志级别设置为 INFO 或更高:
   <configuration>
    <root level="info">
        <appender-ref ref="FILE"/>
    </root>
</configuration>

3. 日志采样

对高频率的日志进行采样,只记录部分日志条目,以减少日志记录的总量。

  • Log4j2 中的日志采样:
   <Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

4. 批量日志记录

将日志数据批量写入磁盘,而不是每次日志记录都进行写操作,这样可以减少 I/O 操作的频率,提高性能。

  • Log4j2 中的批量日志记录:
   <Configuration>
    <Appenders>
        <File name="File" fileName="logs/app.log">
            <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
        </File>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="File"/>
        </Root>
    </Loggers>
</Configuration>

5. 优化日志格式

使用简洁的日志格式,避免复杂的日志消息拼接和格式化操作。

  • 优化后的日志格式:
   <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %m%n"/>

6. 减少同步块

尽量避免在日志记录中使用同步块,因为这可能会导致线程竞争和性能瓶颈。

7. 日志轮转

设置日志轮转策略,防止日志文件过大影响性能。

  • Logback 中的日志轮转:
   <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/app.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>%d [%t] %-5level: %msg%n</pattern>
    </encoder>
</appender>

8. 日志存储系统优化

使用高效的日志存储系统(如 Elasticsearch,Logstash,Kibana)并进行适当的配置优化,以确保日志存储和查询的高性能。

9. 避免敏感操作的日志

在性能敏感的代码中,避免过多的日志记录操作。例如,在高频调用的函数中减少日志记录。

总结

通过异步日志记录,设置适当的日志级别,批量日志记录,优化日志格式,减少同步块,日志轮转等技术,可以有效地降低日志记录对系统性能的影响。在实际应用中,结合多种方法,根据具体需求和系统性能表现进行调整和优化,以实现最佳的日志记录性能。

扩展知识

异步日志的限制

在很多时候,我们会在日志中打印一个链路追踪的信息,如trace_id,但是因为这里是异步打印的,ThreadLocal中存储的trace_id就无法获取了,就会导致日志中无法记录trace_id了。

所以需要特殊解决一下,方法也比较简单,就是自己定义一个Appender的实现类,在这各类里面进行设置这个trace_id:

   MDC.put("traceId",  threadPoolTaskData.toString());

或者使用 https://github.com/ofpay/logback-mdc-ttl 来解决。