前言
日志配置很重要,如果配置不当,可能会造成服务器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>
重要配置说明
异步输出日志
要使所有的记录器成为异步的,下面是最简单的配置,并提供最佳性能。
- classpath上添加disruptor.jar包
在Log4j-2.9 之后的版本,需要classpath上有 disruptor-3.3.4.jar包 或更高版本的。
在Log4j-2.9 之前的版本,需要 disruptor-3.0.0.jar 或更高版本。
- 设置环境变量
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
不打印代码位置信息
- 异步输出日志开启后,默认情况下,代码行号不会被异步记录器传递给I/O线程,即不打印代码位置信息。
如果你的某个布局或自定义过滤器需要位置信息,你需要在所有相关的记录器(包括根记录器)的配置中,设置
"includeLocation=true"。
但是这里,非常不建议开启,因为开启后会严重降低性能。
- 同步日志输出时,不打印代码位置信息,需要在所有相关的记录器(包括根记录器)的配置中设置 "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
参数名 | 类型 | 描述 |
---|---|---|
append | boolean | 当为true(默认值)时,记录将被追加到文件的末尾。当设置为false时,文件将在写入新记录前被清除。 |
immediateFlush | boolean | 当设置为true(默认值)时,每次写入后都会有一次刷新。这将保证数据被写入磁盘,但可能影响性能。每次写入后的刷新,只有在将此应用程序与同步记录器一起使用时才有用。异步记录器和应用程序将在一批事件结束时自动刷新,即使 immediateFlush 被设置为 false 也会自动刷新。这样既保证了数据被写入到磁盘,也使效率更高。 |
fileName | String | 要写入的文件的名称。如果该文件或其任何父目录不存在,它们将被创建。 |
filePattern | String | 归档日志文件的文件名模式。该模式的格式取决于所使用的RolloverStrategy。DefaultRolloverStrategy将接受与SimpleDateFormat兼容的日期/时间模式和/或代表一个整数计数器的%i。 |
SizeBased Triggering Policy
SizeBasedTriggeringPolicy会在文件达到指定大小时引起分割。大小可以以字节为单位指定,后缀为KB、MB、GB或TB,例如20MB。大小也可以包含一个小数,如1.5MB。大小是用Java根语言评估的,所以小数单位必须使用句号。当与基于时间的触发策略相结合时,文件模式必须包含%i,否则目标文件将在每次滚动时被覆盖,因为基于大小的触发策略不会导致文件名中的时间戳值改变。 当不使用基于时间的触发策略时,基于大小的触发策略将导致时间戳值的改变。
TimeBased Triggering Policy
一旦日期/时间模式不再适用于当前正在写入的日志文件,TimeBasedTriggeringPolicy就会对日志文件进行分割。
参数名 | 类型 | 描述 |
---|---|---|
interval | integer | 根据日期模式中最具体的时间单位,日志分割应该多长时间发生一次。例如,在日期模式中,小时是最具体的时间单位,interval为4时,那么每4小时就会发生一次分割。此参数默认值是1。 |
modulate | boolean | 表示是否应调整时间间隔,使下一次日志分割发生在时间间隔边界。例如,如果最具体的时间单位是小时,假设当前时间为凌晨3点,interval是4,那么第一次日志分割将发生在凌晨4点,然后接下来的分割将发生在上午8点、中午12点、下午4点,等等。此参数默认值是false。 |
maxRandomDelay | integer | 表示随机延迟分割的最大秒数。默认情况下,这是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的时间单位就是秒。
Default Rollover Strategy
默认的分割策略同时接受日期/时间模式和来自RollingFileAppender本身指定的filePattern属性的整数。
如果日期/时间模式存在,它将被替换成当前的日期和时间值。如果该模式包含一个整数,它将在每次滚动时被递增。如果模式中同时包含日期/时间和整数,整数将被递增,直到日期/时间模式的结果改变。
如果文件模式以".gz"、".zip"、".bz2"、".deflate"、".pack200 "或".xz "结尾,生成的日志将使用与后缀相匹配的压缩方案进行压缩。
其中bzip2、Deflate、Pack200 和 XZ 格式需要 Apache Commons Compress,XZ需要XZ for Java。
参数名 | 类型 | 描述 |
---|---|---|
fileIndex | String | 如果设置为 "max"(默认),索引较高的文件将比索引较小的文件更新。如果设置为 "min",文件重命名和计数器将遵循上述的固定窗口策略。从2.8版开始,如果fileIndex属性被设置为 "nomax",那么最小和最大值将被忽略,文件编号将以1递增,每次分割将有一个递增的值,没有最大文件数限制。 |
min | integer | 计数器的最小值。此参数默认值为1。 |
max | integer | 计数器的最大值。一旦达到这个值,旧的日志将在随后的日志分割中被删除。此参数默认值是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参数,你可以测试你的配置,而不会意外地删除错误的文件。
参数名 | 类型 | 描述 |
---|---|---|
basePath | String | 必填。开始扫描要删除的文件的基本路径。 |
maxDepth | integer | 要访问的目录的最大层数。值为0意味着只访问起始文件(基本路径本身),除非被安全管理器拒绝。值为Integer.MAX_VALUE表示应该访问所有级别。默认值是1,意味着只访问指定基本目录中的文件。 |
followLinks | integer | 是否跟踪文件软链接。默认为false。 |
testMode | integer | 如果为true,文件不会被删除,而是在INFO级别向状态记录器打印一条信息。用它来做一次试运行,测试配置是否像预期的那样工作。默认为false。 |
pathConditions | integer | 如果没有指定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>
上面配置中的 IfAccumulatedFileSize
和 IfLastModified
,没有 IfAll
、IfAny
、IfNot
条件修饰,没有配置默认就是 IfAll
,表示 and 的逻辑。
用户可以创建自定义条件或使用内置条件。
- IfFileName - 接受其路径(相对于基本路径)与正则表达式或glob匹配的文件。
- IfLastModified - 接受与指定时间一样长或更长的文件,配置格式需要按照Duration配置,否则不生效。
- IfAccumulatedFileCount - 接受在文件树行走过程中超过某个计数阈值的路径。
- IfAccumulatedFileSize - 接受在文件树行走过程中超过累积文件大小阈值后的路径。
- IfAll - 如果所有的嵌套条件都满足,则接受该路径(逻辑和)。嵌套条件可以按任何顺序进行评估。
- IfAny - 如果其中一个嵌套条件满足,则接受该路径(逻辑OR)。嵌套条件可以以任何顺序进行评估。
- IfNot - 如果嵌套条件不接受它,则接受一个路径(逻辑NOT)。
日志区分级别输出
日志按不同级别分别输出到不同的日志文件,方便定位问题和细粒度日志文件删除。
ThresholdFilter
如果LogEvent中的级别与配置的级别相同或更具体,该过滤器返回onMatch结果,否则返回onMismatch值。例如,如果ThresholdFilter被配置为ERROR级别,而LogEvent包含DEBUG级别,那么将返回onMismatch值,因为ERROR事件比DEBUG更具体。
参数名 | 类型 | 描述 |
---|---|---|
level | String | 一个有效的级别名称来匹配。 |
onMatch | String | 当过滤器匹配时要采取的行动。可以是ACCEPT、DENY或NEUTRAL。默认值是NEUTRAL。 |
onMismatch | String | 当过滤器不匹配时要采取的行动。可以是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 发布!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。