前言

日志配置很重要,如果配置不当,可能会造成服务器CPU占用高,磁盘占满等问题,为了让大家少走弯路,本篇分享出生产环境上稳定的高性能日志配置。

如果这篇文章帮助到了你,欢迎评论、点赞、转发。

日志正确配置

  • 异步输出日志
  • 不打印代码位置信息
  • 日志要有分割策略和删除策略
  • 日志区分级别输出

log4j2完整配置示例,适用版本2.x

<?xml version="1.0" encoding="UTF-8"?>
<!-- Don't forget to set system property
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
     to make all loggers asynchronous. -->
<Configuration status="WARN">
    <!--变量配置:此处的变量都是自定义的 -->
    <Properties>
        <!--module name:服务名 -->
        <property name="MODULE_NAME" value="yourselfLogName"/>
        <!--log.pattern:日志输出的前缀格式  -->
        <property name="LOG_PATTERN"
                  value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%traceId] [%logger{60}:%L] - %msg%n"/>
        <!--TIME_INTERVAL:日志分割的时间间隔,时间单位是根据filePattern来定的 -->
        <property name="TIME_INTERVAL" value="1"/>
        <!--maxFileSize:单个日志文件的最大大小 -->
        <property name="MAX_FILE_SIZE" value="200 MB"/>
        <!-- TOTAL_SIZE_CAP:不同日志级别的日志文件总磁盘占用的阈值 -->
        <property name="TOTAL_SIZE_INFO" value="30 GB"/>
        <property name="TOTAL_SIZE_WARN" value="5 GB"/>
        <property name="TOTAL_SIZE_ERROR" value="5 GB"/>
        <!--maxHistory:日志的最大留存时间 -->
        <property name="MAX_HISTORY" value="P15D"/>
        <!--maxHistory:日志分割最大随机延迟的秒数 -->
        <property name="MAX_RANDOMDELAY" value="300"/>
        <!-- level:日志级别 -->
        <property name="LEVEL_INFO" value="info"/>
        <property name="LEVEL_WARN" value="warn"/>
        <property name="LEVEL_ERROR" value="error"/>
        <!-- log path:日志输出的路径,可以配置相对路径、绝对路径、路径软连接 -->
        <property name="LOG_PATH" value="yourselfLogPath"/>
    </Properties>

    <Appenders>
        <!-- Console -->
        <console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
        </console>

        <!-- INFO_FILE -->
        <RollingRandomAccessFile name="INFO_FILE" fileName="${LOG_PATH}/${MODULE_NAME}-${LEVEL_INFO}.log"
                                 immediateFlush="false"
                                 append="true"
                                 filePattern="${LOG_PATH}/${MODULE_NAME}-${LEVEL_INFO}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
            <!-- 日志分割策略 -->
            <Policies>
                <TimeBasedTriggeringPolicy interval="${TIME_INTERVAL}" maxRandomDelay="${MAX_RANDOMDELAY}"/>
                <SizeBasedTriggeringPolicy size="${MAX_FILE_SIZE}"/>
            </Policies>
            <!-- 日志删除策略 -->
            <DefaultRolloverStrategy fileIndex="nomax">
                <Delete basePath="${LOG_PATH}" maxDepth="1" followLinks="true">
                    <IfFileName glob="${MODULE_NAME}-${LEVEL_INFO}*.log">
                        <IfAny>
                            <IfAccumulatedFileSize exceeds="${TOTAL_SIZE_INFO}"/>
                            <IfLastModified age="${MAX_HISTORY}"/>
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
            <!-- 日志级别 -->
            <Filters>
                <ThresholdFilter level="${LEVEL_WARN}" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="${LEVEL_INFO}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingRandomAccessFile>
        
        <!-- WARN_FILE -->
        <RollingRandomAccessFile name="WARN_FILE" fileName="${LOG_PATH}/${MODULE_NAME}-${LEVEL_WARN}.log"
                                 immediateFlush="false"
                                 append="true"
                                 filePattern="${LOG_PATH}/${MODULE_NAME}-${LEVEL_WARN}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
            <!-- 日志分割策略 -->
            <Policies>
                <TimeBasedTriggeringPolicy interval="${TIME_INTERVAL}" maxRandomDelay="${MAX_RANDOMDELAY}"/>
                <SizeBasedTriggeringPolicy size="${MAX_FILE_SIZE}"/>
            </Policies>
            <!-- 日志删除策略 -->
            <DefaultRolloverStrategy fileIndex="nomax">
                <Delete basePath="${LOG_PATH}" maxDepth="1" followLinks="true">
                    <IfFileName glob="${MODULE_NAME}-${LEVEL_WARN}*.log">
                        <IfAny>
                            <IfAccumulatedFileSize exceeds="${TOTAL_SIZE_WARN}"/>
                            <IfLastModified age="${MAX_HISTORY}"/>
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
            <!-- 日志级别 -->
            <Filters>
                <ThresholdFilter level="${LEVEL_ERROR}" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="${LEVEL_WARN}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingRandomAccessFile>
        
        <!-- ERROR_FILE -->
        <RollingRandomAccessFile name="ERROR_FILE" fileName="${LOG_PATH}/${MODULE_NAME}-${LEVEL_ERROR}.log"
                                 immediateFlush="false"
                                 append="true"
                                 filePattern="${LOG_PATH}/${MODULE_NAME}-${LEVEL_ERROR}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
            <!-- 日志分割策略 -->
            <Policies>
                <TimeBasedTriggeringPolicy interval="${TIME_INTERVAL}" maxRandomDelay="${MAX_RANDOMDELAY}"/>
                <SizeBasedTriggeringPolicy size="${MAX_FILE_SIZE}"/>
            </Policies>
            <!-- 日志删除策略 -->
            <DefaultRolloverStrategy fileIndex="nomax">
                <Delete basePath="${LOG_PATH}" maxDepth="1" followLinks="true">
                    <IfFileName glob="${MODULE_NAME}-${LEVEL_ERROR}*.log">
                        <IfAny>
                            <IfAccumulatedFileSize exceeds="${TOTAL_SIZE_ERROR}"/>
                            <IfLastModified age="${MAX_HISTORY}"/>
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
            <!-- 日志级别 -->
            <Filters>
                <ThresholdFilter level="${LEVEL_ERROR}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingRandomAccessFile>

    </Appenders>

    <Loggers>
        <!-- logger 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定 -->
        <!-- common framework level -->
        <AsyncLogger name="org.apache" level="INFO"/>
        <AsyncLogger name="org.apache.http" level="INFO"/>

        <!-- root -->
        <Root level="INFO" includeLocation="false">
            <AppenderRef ref="CONSOLE"/>
            <AppenderRef ref="INFO_FILE"/>
            <AppenderRef ref="WARN_FILE"/>
            <AppenderRef ref="ERROR_FILE"/>
        </Root>
    </Loggers>

</Configuration>

重要配置说明

异步输出日志

要使所有的记录器成为异步的,下面是最简单的配置,并提供最佳性能。

  1. classpath上添加disruptor.jar包
在Log4j-2.9 之后的版本,需要classpath上有 disruptor-3.3.4.jar包 或更高版本的。
在Log4j-2.9 之前的版本,需要 disruptor-3.0.0.jar 或更高版本。
  1. 设置环境变量 log4j2.contextSelector
log4j2.contextSelector = org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
或者
log4j2.contextSelector = org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector

官方文档详细描述:Log4j – Log4j 2 Lock-free Asynchronous Loggers for Low-Latency Logging (apache.org)

对于Springboot的项目,只需要增加 log4j2.component.properties 文件,文件里面配置内容如下:

log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

image.png

不打印代码位置信息

  1. 异步输出日志开启后,默认情况下,代码行号不会被异步记录器传递给I/O线程,即不打印代码位置信息。
如果你的某个布局或自定义过滤器需要位置信息,你需要在所有相关的记录器(包括根记录器)的配置中,设置
"includeLocation=true"。
但是这里,非常不建议开启,因为开启后会严重降低性能。
  1. 同步日志输出时,不打印代码位置信息,需要在所有相关的记录器(包括根记录器)的配置中设置 "includeLocation=false"

分割策略和删除策略

分割策略

日志分割策略,大部分情况下会按照指定文件大小、指定时间间隔进行日志分割。

比如:日志按每200MB和每天的间隔去切割,示例配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" >
    <Appenders>
        <RollingRandomAccessFile name="RollingRandomAccessFile" 
                                 immediateFlush="false"
                                 append="true"
                                 fileName="logs/app.log"
                                 filePattern="logs/app-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout>
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%traceId] [%logger{60}:%L] - %msg%n</Pattern>
            </PatternLayout>
            
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" maxRandomDelay="300"/>
                <SizeBasedTriggeringPolicy size="200 MB"/>
            </Policies>
            
            <DefaultRolloverStrategy max="20"/>
        </RollingRandomAccessFile>
    </Appenders>
    
    <Loggers>
        <Root level="info">
            <AppenderRef ref="RollingRandomAccessFile"/>
        </Root>
    </Loggers>
    
</Configuration>

RollingRandomAccessFileAppender

参数名类型描述
appendboolean当为true(默认值)时,记录将被追加到文件的末尾。当设置为false时,文件将在写入新记录前被清除。
immediateFlushboolean当设置为true(默认值)时,每次写入后都会有一次刷新。这将保证数据被写入磁盘,但可能影响性能。每次写入后的刷新,只有在将此应用程序与同步记录器一起使用时才有用。异步记录器和应用程序将在一批事件结束时自动刷新,即使 immediateFlush 被设置为 false 也会自动刷新。这样既保证了数据被写入到磁盘,也使效率更高。
fileNameString要写入的文件的名称。如果该文件或其任何父目录不存在,它们将被创建。
filePatternString归档日志文件的文件名模式。该模式的格式取决于所使用的RolloverStrategy。DefaultRolloverStrategy将接受与SimpleDateFormat兼容的日期/时间模式和/或代表一个整数计数器的%i。

Triggering Policies

SizeBased Triggering Policy

SizeBasedTriggeringPolicy会在文件达到指定大小时引起分割。大小可以以字节为单位指定,后缀为KB、MB、GB或TB,例如20MB。大小也可以包含一个小数,如1.5MB。大小是用Java根语言评估的,所以小数单位必须使用句号。当与基于时间的触发策略相结合时,文件模式必须包含%i,否则目标文件将在每次滚动时被覆盖,因为基于大小的触发策略不会导致文件名中的时间戳值改变。 当不使用基于时间的触发策略时,基于大小的触发策略将导致时间戳值的改变。

TimeBased Triggering Policy

一旦日期/时间模式不再适用于当前正在写入的日志文件,TimeBasedTriggeringPolicy就会对日志文件进行分割。

参数名类型描述
intervalinteger根据日期模式中最具体的时间单位,日志分割应该多长时间发生一次。例如,在日期模式中,小时是最具体的时间单位,interval为4时,那么每4小时就会发生一次分割。此参数默认值是1。
modulateboolean表示是否应调整时间间隔,使下一次日志分割发生在时间间隔边界。例如,如果最具体的时间单位是小时,假设当前时间为凌晨3点,interval是4,那么第一次日志分割将发生在凌晨4点,然后接下来的分割将发生在上午8点、中午12点、下午4点,等等。此参数默认值是false。
maxRandomDelayinteger表示随机延迟分割的最大秒数。默认情况下,这是0,表示没有延迟。这个设置在服务器上很有用,在服务器上,如果多个应用程序被配置为同时分割日志文件,那么随机延迟分割可以使日志分割的负载分散到不同时间。

这里着重说明一下interval的时间单位,它是根据filePattern里日期模式配置中最具体的时间单位而定的

举例说明:

filePattern="logs/app-%d{yyyy-MM-dd}-%i.log" 时,因为日期模式是yyyy-MM-dd,具体到天这个级别,所以interval的时间单位就是天。

filePattern="logs/app-%d{yyyy-MM-dd HH}-%i.log" 时,因为日期模式是yyyy-MM-dd HH,具体到小时这个级别,所以interval的时间单位就是小时。

filePattern="logs/app-%d{yyyy-MM-dd HH:mm}-%i.log" 时,因为日期模式是yyyy-MM-dd HH:mm,具体到分钟这个级别,所以interval的时间单位就是分钟。

filePattern="logs/app-%d{yyyy-MM-dd HH:mm:ss}-%i.log" 时,因为日期模式是yyyy-MM-dd HH:mm:ss,具体到秒这个级别,所以interval的时间单位就是秒。

Rollover Strategies

Default Rollover Strategy

默认的分割策略同时接受日期/时间模式和来自RollingFileAppender本身指定的filePattern属性的整数。

如果日期/时间模式存在,它将被替换成当前的日期和时间值。如果该模式包含一个整数,它将在每次滚动时被递增。如果模式中同时包含日期/时间和整数,整数将被递增,直到日期/时间模式的结果改变。

如果文件模式以".gz"、".zip"、".bz2"、".deflate"、".pack200 "或".xz "结尾,生成的日志将使用与后缀相匹配的压缩方案进行压缩。

其中bzip2、Deflate、Pack200 和 XZ 格式需要 Apache Commons Compress,XZ需要XZ for Java。

参数名类型描述
fileIndexString如果设置为 "max"(默认),索引较高的文件将比索引较小的文件更新。如果设置为 "min",文件重命名和计数器将遵循上述的固定窗口策略。从2.8版开始,如果fileIndex属性被设置为 "nomax",那么最小和最大值将被忽略,文件编号将以1递增,每次分割将有一个递增的值,没有最大文件数限制。
mininteger计数器的最小值。此参数默认值为1。
maxinteger计数器的最大值。一旦达到这个值,旧的日志将在随后的日志分割中被删除。此参数默认值是7。
删除策略

删除策略,大部分情况下会按照日志占用磁盘的空间大小、日志最长留存时间进行自动删除。

常见的配置有保留最近15天的日志且日志磁盘占用不超过200GB,超过200GB 或者 15天之前的日志,只要满足任何一个条件,就会触发日志删除。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" >
    <Appenders>
        <RollingRandomAccessFile name="RollingRandomAccessFile"
                                 immediateFlush="false"
                                 append="true"
                                 fileName="logs/app.log"
                                 filePattern="logs/app-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout>
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%traceId] [%logger{60}:%L] - %msg%n</Pattern>
            </PatternLayout>

            <Policies>
                <TimeBasedTriggeringPolicy interval="1" maxRandomDelay="300"/>
                <SizeBasedTriggeringPolicy size="200 MB"/>
            </Policies>

            <DefaultRolloverStrategy fileIndex="nomax">
                <Delete basePath="logs" maxDepth="1" followLinks="true">
                    <IfFileName glob="app-%d{yyyy-MM-dd}*.log">
                        <IfAny>
                            <IfAccumulatedFileSize exceeds="200 GB"/>
                            <IfLastModified age="P15D"/>
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="RollingRandomAccessFile"/>
        </Root>
    </Loggers>

</Configuration>

Log4j-2.5引入了一个Delete动作,与DefaultRolloverStrategy max属性相比,该动作使用户能够更多的控制在滚动时间删除哪些文件。删除动作让用户可以配置一个或多个条件,选择相对于基本目录要删除的文件。

请注意,有可能删除任何文件,而不仅仅是滚动的日志文件,所以使用这个动作时要小心! 通过testMode参数,你可以测试你的配置,而不会意外地删除错误的文件。

参数名类型描述
basePathString必填。开始扫描要删除的文件的基本路径。
maxDepthinteger要访问的目录的最大层数。值为0意味着只访问起始文件(基本路径本身),除非被安全管理器拒绝。值为Integer.MAX_VALUE表示应该访问所有级别。默认值是1,意味着只访问指定基本目录中的文件。
followLinksinteger是否跟踪文件软链接。默认为false。
testModeinteger如果为true,文件不会被删除,而是在INFO级别向状态记录器打印一条信息。用它来做一次试运行,测试配置是否像预期的那样工作。默认为false。
pathConditionsinteger如果没有指定ScriptCondition时,此配置是必需的。此参数接受一个或多个PathCondition元素。如果指定了一个以上的条件,在删除之前,它们都需要接受一个路径。条件可以是嵌套的,在这种情况下,只有当外部条件接受了路径时,内部条件才会被评估。如果条件不是嵌套的,它们可以按任何顺序进行评估。条件也可以通过使用IfAll、IfAny和IfNot复合条件与逻辑运算符AND、OR和NOT相结合。

这里重点说明一下pathConditions(以下示例不要在生产中使用):

 <DefaultRolloverStrategy fileIndex="nomax">
    <Delete basePath="logs" maxDepth="1" followLinks="true">
        <IfFileName glob="app-%d{yyyy-MM-dd}*.log">
            <IfAccumulatedFileSize exceeds="200 GB"/>
            <IfLastModified age="P15D"/>
        </IfFileName>
    </Delete>
</DefaultRolloverStrategy>

上面配置中的 IfAccumulatedFileSizeIfLastModified ,没有 IfAllIfAnyIfNot 条件修饰,没有配置默认就是 IfAll,表示 and 的逻辑。

用户可以创建自定义条件或使用内置条件。

  • IfFileName - 接受其路径(相对于基本路径)与正则表达式或glob匹配的文件。
  • IfLastModified - 接受与指定时间一样长或更长的文件,配置格式需要按照Duration配置,否则不生效。
  • IfAccumulatedFileCount - 接受在文件树行走过程中超过某个计数阈值的路径。
  • IfAccumulatedFileSize - 接受在文件树行走过程中超过累积文件大小阈值后的路径。
  • IfAll - 如果所有的嵌套条件都满足,则接受该路径(逻辑和)。嵌套条件可以按任何顺序进行评估。
  • IfAny - 如果其中一个嵌套条件满足,则接受该路径(逻辑OR)。嵌套条件可以以任何顺序进行评估。
  • IfNot - 如果嵌套条件不接受它,则接受一个路径(逻辑NOT)。

日志区分级别输出

日志按不同级别分别输出到不同的日志文件,方便定位问题和细粒度日志文件删除。

ThresholdFilter

如果LogEvent中的级别与配置的级别相同或更具体,该过滤器返回onMatch结果,否则返回onMismatch值。例如,如果ThresholdFilter被配置为ERROR级别,而LogEvent包含DEBUG级别,那么将返回onMismatch值,因为ERROR事件比DEBUG更具体。

参数名类型描述
levelString一个有效的级别名称来匹配。
onMatchString当过滤器匹配时要采取的行动。可以是ACCEPT、DENY或NEUTRAL。默认值是NEUTRAL。
onMismatchString当过滤器不匹配时要采取的行动。可以是ACCEPT、DENY或NEUTRAL。默认值是DENY。

配置示例,只打印info日志到文件中:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Don't forget to set system property
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
     to make all loggers asynchronous. -->
<Configuration status="WARN">
    <Appenders>
        <!-- INFO_FILE -->
        <RollingRandomAccessFile name="INFO_FILE" fileName="logs/app.log"
                                 immediateFlush="false"
                                 append="true"
                                 filePattern="logs/app-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%traceId] [%logger{60}:%L] - %msg%n" charset="UTF-8"/>
            <!-- 日志分割策略 -->
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" maxRandomDelay="300"/>
                <SizeBasedTriggeringPolicy size="200 MB"/>
            </Policies>
            <!-- 日志删除策略 -->
            <DefaultRolloverStrategy max=“20” />
            <!-- 日志级别 -->
            <Filters>
                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingRandomAccessFile>
    </Appenders>

    <Loggers>
        <!-- root -->
        <Root level="INFO" includeLocation="false">
            <AppenderRef ref="INFO_FILE"/>
        </Root>
    </Loggers>

</Configuration>

如果这篇文章帮助到了你,欢迎评论、点赞、转发。

本文由博客一文多发平台 OpenWrite 发布!

codeba
1 声望0 粉丝