在日常的面试中,问到日志这块的很少,但是也会问到关于日志的选型和对比,主要考察你平时的总结。我这篇笔记呢主要是从Java中日志的发展历史,到流行的log4j2的常规使用和规范进行一个系统的总结。
Java日志API由以下三个核心组件组成:
logback和log4j2都宣称自己是log4j的后代,一个是出于同一个作者,另一个则是在名字上根正苗红。
撇开血统不谈,比较一下log4j2和logback:
log4j2和logback各有长处,总体来说,如果对性能要求比较高的话,log4j2相对还是较优的选择。
诸如 SLF4J 这样的抽象层,会将你的应用程序从日志框架中解耦。
应用程序可以在运行时选择绑定到一个特定的日志框架(例如 java.util.logging、Log4j 或者 Logback),这通过在应用程序的类路径中添加对应的日志框架来实现。
如果在类路径中配置的日志框架不可用,抽象层就会立刻取消调用日志的相应逻辑。
抽象层可以让我们更加容易地改变项目现有的日志框架,或者集成那些使用了不同日志框架的项目。
PS:
ConfigurationFactory
使用编程的方式进行配置xml
、JSON
、YAML
和properties
等文件类型Loggers节点,常见的有两种:Root和Logger
Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出
Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等
Appenders节点,常见的有三种子节点:Console, File, RollingFile
console节点用来定义输出到控制台的Appender
File节点用来定义输出到指定位置的文件的Appender
RollingFile节点用来定义超过指定大小自动删除旧的创建新的的Appender
Layout日志布局参数很多,这里我只罗列常用的
对齐方式,例如 %20c 右对齐, 最小宽度20,无最大宽度,不足20个字符则在数值前面用空格补足,超过20个字符则保留原信息。
数据类型 转换字符 输出日志
日期 %d{HH:mm:ss.SSS} 11:33:08.440
线程名 %t main
日志级别 %-5level FATAL
日志名称 %logger{36} org.apache.logging.log4j.Log4j2Test
日志信息 %msg fatal level log
换行 %n 日志结束换行
<configuration monitorInterval="5">
<Properties>
<property name="LOG_PATTERN" value="[${sys:SERVICE_NAME}] [%date{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{36} - %msg%n" />
<property name="FILE_PATH" value="${sys:FILE_PATH}/${sys:SERVICE_NAME}" />
<property name="FILE_NAME" value="${sys:SERVICE_NAME}" />
Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
console>
<File name="Filelog" fileName="${FILE_PATH}/log4j2-all.log" append="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
File>
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
appenders>
<loggers>
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
logger>
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
Logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
root>
loggers>
configuration>
RollingFileAppender
是Log4j2中的一种能够实现日志文件滚动更新(rollover)的Appender。rollover的意思是当满足一定条件(如文件达到了指定的大小,达到了指定的时间)后,就重命名原日志文件进行归档,并生成新的日志文件用于log写入。如果还设置了一定时间内允许归档的日志文件的最大数量,并对过旧的日志文件进行删除操作。RollingFile实现日志文件滚动更新,依赖于TriggerPolicy和RolloverStrategy。Policy
是用来控制日志文件何时(When)进行滚动,Strategy
是用来控制日志文件如何(How)进行滚动的。如果配置的是RollingFile
或RollingRandomAccessFile
,则必须配置一个Policy
。
Policy触发策略
<SizeBasedTriggeringPolicy size="100 MB"/>
Cron
表达式的触发策略,灵活<CronTriggeringPolicy schedule="0/5 * * * * ?"/>
interval
integer类型,指定两次封存动作之间的时间间隔。这个配置需要和filePattern
结合使用,filePattern
日期格式精确到哪一位,interval也精确到哪一个单位。如filePattern
中配置的文件重命名规则是%d{yyyy-MM-dd HH-mm-ss}-%i
,单位精确到秒,那么interval的单位也就是秒啦。modulate
boolean类型,说明是否对封存时间进行调制。若modulate=true,则封存时间将以0点为边界进行偏移计算。比如,modulate=true, interval=4hours,那么假设上次封存日志的时间为03:00,则下次封存日志的时间为04:00,之后的封存时间依次为08:00, 12:00 16:00<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Strategy滚动策略
DefaultRolloverStrategy
默认滚动策略,常用参数max
,保存日志文件的最大个数,默认是7,大于此值会删除旧的日志文件
<DefaultRolloverStrategy max="10" />
DeleteAction删除策略
log4j2也提供了删除的策略,方便使用者删除特定的日志文件
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<CronTriggeringPolicy schedule="0 0 0 * * ?"/>
<DefaultRolloverStrategy>
<Delete basePath="${baseDir}" maxDepth="2">
<IfFileName glob="*/app-*.log.gz" />
<IfLastModified age="60d" />
Delete>
DefaultRolloverStrategy>
RollingFile>
Appenders>
“ Lookups provide a way to add values to the Log4j configuration at arbitrary places. They are a particular type of Plugin that implements the StrLookup interface. ”
以上内容复制于log4j2的官方文档lookup - Office Site。其清晰地说明了lookup的主要功能就是提供另外一种方式以添加某些特殊的值到日志中,以最大化松散耦合地提供可配置属性供使用者以约定的格式进行调用。
参考Apache Logging的官方介绍,Log4j2的异步日志是通过LMAX Disruptor technology实现的,通过开启一个单独的线程执行IO操作。
Log4j2可以使用AsyncAppender和AsyncLogger两种配置方式,这两者的主要却别是对生成的LogEvent的处理不一样。其中AsyncAppender采用ArrayBlockingQueue来保存需要异步输出的日志事件;AsyncLogger则使用Disruptor框架来实现高吞吐。
日志输出方式 | |
---|---|
sync | 同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作 |
Async Appender | 异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出。 |
Async Logger | 异步打印日志,采用了高性能并发框架Disruptor,创建一个线程EventProcessor用于处理日志输出。 |
LMAX:
... using queues to pass data between stages of the system was introducing latency, so we focused on optimising this area. The Disruptor is the result of our research and testing. We found that cache misses at the CPU-level, and locks requiring kernel arbitration are both extremely costly, so we created a framework which has "mechanical sympathy" for the hardware it's running on, and that's lock-free.
异步日志配置:
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
<Configuration status="WARN">
<Appenders>
<RandomAccessFile name="RandomAccessFile" fileName="async.log" immediateFlush="false" append="false">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m %ex%nPattern>
PatternLayout>
RandomAccessFile>
Appenders>
<Loggers>
<Root level="info" includeLocation="false">
<AppenderRef ref="RandomAccessFile"/>
Root>
Loggers>
Configuration>
<Configuration status="WARN">
<Appenders>
<RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
immediateFlush="false" append="false">
<PatternLayout>
<Pattern>%d %p %class{1.} [%t] %location %m %ex%nPattern>
PatternLayout>
RandomAccessFile>
Appenders>
<Loggers>
<AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
AsyncLogger>
<Root level="info" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
Root>
Loggers>
Configuration>
https://github.com/zhonghuasheng/JAVA/tree/master/springboot/springboot-log4j2