Log4J2学习笔记
Apache Log4j 2是Log4j的重大升级, 引入了Logback包含的大量改进,并修复了Logback架构中某些内在的问题。Log4j2的特性包括:
- API和实现分离:接口更加稳定
- 性能提升:使用基于LMAX Disruptor库的异步日志机制。在多线程应用中,异步日志比Log4j、Logback有更低数量级的延迟时间,以及18倍的吞吐量
- 支持多种API:包括Log4j、SLF4J、Commons Logging 、java.util.logging
- 方便切换到其它日志实现:由于log4j-to-slf4j的存在,你可以随时切换使用别的日志库
- 配置自动重新载入:类似于Logback,当配置文件修改后,Log4j2能够自动重新载入。比Logback更好的时,不会因此丢失日志
- 高级过滤:类似于Logback,Log4j2支持基于上下文数据、Marker、正则式、或者其它日志事件中的组件进行过滤。过滤可以在日志事件传递给Logger之前、传递给Appender之前生效。Loggers可以和过滤器关联
- 支持Property:你可以在配置文件中使用属性,进行动态解析
- Lambda支持:Log4j2支持Java8的Lambda,并且在日志级别不匹配的情况下不会主动解析Lambda,因而能提升性能
- 不产生内存垃圾:可以减轻JVM垃圾回收器的压力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; // 获取日志记录器对象 Logger logger = LogManager.getLogger("HelloWorld"); // 参数替换 logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthday()); // 参数数量没有限制 logger.debug( "Batch {} item {} : {}", batchId, i + 1, names ); // 参数格式化,语法类似于Java的Formatter Logger logger = LogManager.getFormatterLogger("HelloWorld"); logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthday()); // 混合使用日志记录器、格式化日志记录器: Logger logger = LogManager.getLogger("HelloWorld"); logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm", user.getName(), user.getBirthday()); // 使用Lambda实现延迟日志 // 立即执行expensiveOperation if (logger.isTraceEnabled()) { logger.trace("Some long-running operation returned {}", expensiveOperation()); } // 仅仅在必要时才执行expensiveOperation logger.trace("Some long-running operation returned {}", () -> expensiveOperation()); |
日志框架的一个主要目的是,仅仅在需要的时候生成调试和诊断信息。并允许对信息进行过滤,避免加重系统负担或者难以阅读。Log4j2使用标记(Markers)实现满足这种需求:
1 2 3 4 5 6 7 8 9 10 |
Logger logger = LogManager.getLogger(HelloWorld.class.getName()); // 定义一个Marker Marker SQL_MARKER = MarkerManager.getMarker("SQL"); // Marker可以形成树状层次 Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE").setParents(SQL_MARKER); Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY").setParents(SQL_MARKER); // 使用Marker logger.debug(QUERY_MARKER, "SELECT * FROM {}", table); logger.debug(UPDATE_MARKER, "UPDATE {} SET {}", table, formatCols()); |
上面的代码配合MarkerFilters即可实现仅仅对SQL更新操作记录日志,或者记录所有SQL操作日志,或者记录全部应用日志。
使用Marker时,要注意以下规则:
- Marker必须是独特唯一的,起名字时要注意,名字是不是已经被依赖项使用了。当然,你特地要使用同样名字也是可以的
- 父Marker可以被动态的添加、移除。但是这种操作比较昂贵
- 具有多级祖先的Marker解析起来比较消耗资源
Logger类提供了一些方法,方便你跟踪应用程序的执行路径。这些方法会产生从其它调试日志中单独过滤掉的日志事件。你可以使用这些方法来:
- 在开发阶段辅助问题诊断,而不需要单步调试
- 在运维阶段辅助问题诊断,如果单步调试不可用
- 帮助新用户理解应用程序的行为
流程跟踪的主要方法是 entry()、 traceEntry()和 exit()、 traceExit()。它们都产生TRACE级别的日志。前两个方法通常放置在被跟踪方法的开始处,它们使用ENTER标记,此标记是FLOW的子标记。后两个方法通常放置在被跟踪方法的return语句之前,它们使用EXIT标记,此标记是FLOW的子标记。
此外, throwing()方法可以在一个不太可能被处理的异常(例如RuntimeException)抛出时使用,确保必要的诊断信息可用。该方法产生ERROR级别的日志,使用THROWING标记,此标记是EXCEPTION的子标记。 catching()方法可以在捕获一个异常,并不准备重新抛出的情况下使用。该方法产生ERROR级别的日志,使用CATCHING标记,此标记是EXCEPTION的子标记。
示例代码:
1 2 3 4 5 |
logger.traceEntry(new JsonMessage(messages)); logger.traceExit(); try {...} catch (Exception ex) { logger.catching(ex); } |
对Log4j2的配置可以通过四种方式之一完成:
- 配置文件,支持的格式包括XML、JSON、YAML、Properties
- 编程式配置,创建ConfigurationFactory和Configuration实现类
- 编程式配置,调用Configuration接口暴露的API,对默认实现添加组件
- 编程式配置,调用Logger暴露的接口
Log4j2能够在初始化时进行自动配置,它会定位到所有ConfigurationFactory并根据权重值对它们排序。开箱即用的ConfigurationFactory实现有4种,分别处理XML、JSON、YAML、Properties格式的配置文件。具体配置步骤如下:
- 检查系统属性log4j.configurationFile,如果该属性存在,会尝试利用ConfigurationFactory加载匹配扩展名的文件
- 如果没有上述属性,尝试加载类路径下的log4j2-test.propertie
- 如果没有上述文件,尝试加载类路径下的log4j2-test.yaml或者log4j2-test.yml
- 如果没有上述文件,尝试加载类路径下的log4j2-test.json或者log4j2-test.jsn
- 如果没有上述文件,尝试加载类路径下的log4j2-test.xml
- 如果没有上述文件,一次尝试加载log4j2.*版本(没有-test后缀)的配置文件
- 如果找不到任何配置文件,使用DefaultConfiguration。此配置直接打印日志到stdout
此配置行为如下:
- 根日志级别为ERROR
- 将ConsoleAppender附加到根日志记录器
- 将PatternLayout: %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n附加到ConsoleAppender
以XML形式表示默认配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="UTF-8"?>; <Configuration> <Properties> <Property name="name1">value</property> <Property name="name2" value="value2"/> </Properties> <Filter type="type" ... /> <Appenders> <Appender type="type" name="name"> <Filter type="type" ... /> </Appender> ... </Appenders> <Loggers> <Logger name="name1"> <Filter type="type" ... /> </Logger> ... <Root level="level"> <AppenderRef ref="name"/> </Root> </Loggers> </Configuration> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
<?xml version="1.0" encoding="UTF-8"?> <!-- status Log4j2内部事件的日志级别 strict 是否使用严格XML格式 name 配置的名称 packages 此哪些包中加载插件,逗号分隔多个包 monitorInterval 检查配置文件变更的间隔,单位秒,如果发现变更则重新加载配置 --> <Configuration status="debug" strict="true" name="XMLConfigTest" monitorInterval="10" packages="org.apache.logging.log4j.test"> <!-- 定义属性,属性可以在后续使用${} 引用 --> <!-- Log4j2支持很多预定义的上下文属性对象。可以通过 ${prefix:name} 语法引用 bundle:资源束对象,用法示例 ${bundle:BundleName:BundleKey} ctx:线程上下文映射(Thread Context Map,MDC) date:当前时间或者日期 env:系统环境变量 jndi:JNDI上下文 jvmrunargs:JVM参数 sys:系统属性 --> <Properties> <Property name="filename">target/test.log</Property> </Properties> <!-- 过滤器可以出现在四种地方: 1、全局级别,可以在日志事件传递给日志记录器(LoggerConfig)之前接受/拒绝它 2、logger元素内部,针对单个日志记录器 3、appender元素内部,针对单个Appender 4、AppenderRef元素内部,针对日志记录器+Appender的组合 --> <!-- 全局过滤器 --> <Filter type="ThresholdFilter" level="trace"/> <!-- Appender负责向外输出日志内容 --> <Appenders> <!-- 基于控制台输出 --> <Appender type="Console" name="STDOUT"> <!-- 日志输出格式 --> <Layout type="PatternLayout" pattern="%m MDC%X%n"/> <!-- 针对Appender的过滤器 --> <Filters> <!-- 如果日志事件使用标记FLOW,则拒绝输出日志 --> <Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/> <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/> </Filters> </Appender> <Appender type="Console" name="FLOW"> <!-- 日志格式(布局),支持的布局: CSV布局,包括子类CsvParameterLayout、CsvLogEventLayout GELF布局,将JSON压缩为GZIP/ZLIB格式 HTML布局,生成HTML页面 JSON布局,生成一系列JSON字符串 Pattern布局,自定义格式对LogEvent进行格式化 RFC5424布局 Syslog布局,格式化为BSD Syslog记录 XML布局 YAML布局 --> <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/> <Filters> <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/> <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/> </Filters> </Appender> <!-- 基于文件输出 --> <Appender type="File" name="File" fileName="${filename}"> <Layout type="PatternLayout"> <Pattern>%d %p %C{1.} [%t] %m%n</Pattern> </Layout> </Appender> </Appenders> <!-- 日志记录器 --> <Loggers> <!-- level 此日志记录器最低输出级别,大小写不敏感 支持TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF。默认ERROR additivity:false表示日志事件被此记录器处理过后,不会传递给父记录器再次处理 --> <Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false"> <!-- 针对日志记录器的过滤器 --> <Filter type="ThreadContextMapFilter"> <KeyValuePair key="test" value="123"/> </Filter> <!-- 日志记录器关联哪个Appender --> <AppenderRef ref="STDOUT"/> </Logger> <Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false"> <AppenderRef ref="File"/> </Logger> <!-- 根记录器,每个配置都必须有一个根记录器 --> <Root level="trace"> <AppenderRef ref="STDOUT"/> </Root> </Loggers> </Configuration> |
1 2 3 4 5 |
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>2.5.0</version> </dependency> |
和XML配置的结构大体对应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
Configuration: status: warn name: YAMLConfigTest properties: property: name: filename value: target/test-yaml.log thresholdFilter: level: debug appenders: Console: name: STDOUT PatternLayout: Pattern: "%m%n" File: name: File fileName: ${filename} PatternLayout: Pattern: "%d %p %C{1.} [%t] %m%n" Filters: ThresholdFilter: level: error Loggers: logger: - name: org.apache.logging.log4j.test1 level: debug additivity: false ThreadContextMapFilter: KeyValuePair: key: test value: 123 AppenderRef: ref: STDOUT - name: org.apache.logging.log4j.test2 level: debug additivity: false AppenderRef: ref: File Root: level: error AppenderRef: ref: STDOUT |
下面的配置参考Spring Boot的输出格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Configuration: name: development monitorInterval: 5 thresholdFilter: level: trace appenders: Console: name: stdout PatternLayout: # %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5p} %style{%t}{Magenta} %style{%c{1.}.%M}{Cyan}(%F:%L)%n%m%n Pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5p} ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex" Loggers: logger: - name: cc.gmem.study.log4j2 level: debug additivity: false AppenderRef: ref: stdout Root: level: error AppenderRef: ref: stdout |
这是最常用的Layout,本节详细讨论它。
参数名 | 说明 |
charset | 输出字符串为字节数组时使用的编码方式,如果不指定使用平台默认编码 |
pattern | 指定如何转换LogEvent为字符串的模式 |
patternSelector | 类型PatternSelector。根据LogEvent选择一种Pattern,不得和pattern参数同时指定 |
alwaysWriteExceptions | 类型Boolean。如果你不再pattern中指定异常输出,则使用默认异常格式,附加在Pattern尾部 |
%项 | 说明 |
c{precision} | 输出日志记录器的名称,可选的指定一个精度: %c{1}将cc.gmem.study.log4j2.Application格式化为:Application %c{2}将cc.gmem.study.log4j2.Application格式化为:log4j2.Application %c{1.}将cc.gmem.study.log4j2.Application格式化为:c.g.s.l.Application |
C{precision} | 输出发起日志记录请求的那个类的全限定名称 |
d{pattern} | 输出当前日期时间: %d{DEFAULT}:2012-11-02 14:34:02,781 %d{yyyy-MM-dd HH:mm:ss,SSS}:2012-11-02 14:34:02,781 %d{UNIX}:1351866842 %d{UNIX_MILLIS}:1351866842781 |
highlight{pattern}{style} |
添加ANSI颜色高亮。默认针对各种级别的高亮颜色为: FATAL、ERROR、WARN、INFO、DEBUG、TRACE pattern为需要高亮的其它Pattern字段 style取值可以是Default或者Logback。后者亮度较高 |
K{key} | 用于输出MapMessage中的条目 |
l | 输出调用者的位置信息,包括方法名和行号 |
L | 输出调用发起处的行号 |
M | 输出调用发起处的方法名 |
marker | 输出Marker的完整名称,如果由父Marker也输出 |
n | 输出平台相关的换行符 |
N | 输出System.nanoTime() |
pid{[defaultValue]} | 输出PID |
p |
输出日志的级别,示例: %level{WARN=Warning, DEBUG=Debug, ERROR=Error, TRACE=Trace, INFO=Info} |
r | 输出自JVM启动依赖流逝的毫秒数 |
sn | 输出此日志事件的序列号 |
style{pattern}{ANSI style} | 定制输出样式 |
T | 输出当前线程ID |
t | 输出当前线程名称 |
tp | 输出当前线程优先级 |
u{"RANDOM" | "TIME"} | 输出基于时间产生的UUID |
% | 输出% |
1 2 3 4 5 |
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.0</version> </dependency> |
参数 | 说明 |
charset | string=UTF-8,转换字节数组时使用的字符集 |
compact | bool=false,是否紧凑输出,如果true,则不进行缩进 |
eventEol | bool=false,是否在记录后添加换行符 |
complete | bool=false,如果true,则输出JSON眉、脚,并且在记录之间使用逗号分隔 |
properties | bool=false,如果true,则输出线程上下文映射到JSON |
locationInfo | bool=false,如果true,则输出代码位置信息 |
includeStacktrace | bool=true,如果true,则输出异常栈信息 |
stacktraceAsString | bool=false,如果true,则将异常栈格式化为字符串,而非内嵌对象 |
includeNullDelimiter | bool=false,是否在每个记录后添加NIL字符 |
objectMessageAsJsonObject | bool=false,是否将对象类型的信息格式化为JSON对象 |
配置示例(请使用最新版本的Log4j2):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
status = error appender.console.type = Console appender.console.name = console appender.console.layout.type = JsonLayout appender.console.layout.charset = UTF-8 appender.console.layout.compact=false appender.console.layout.eventEol=true appender.console.layout.locationInfo=false appender.console.layout.stacktraceAsString=true rootLogger.level = info rootLogger.appenderRef.console.ref = console |
可以使用KeyValuePair指定额外的输出字段:
1 2 3 4 5 6 7 8 |
<JsonLayout> <!-- 常量值--> <KeyValuePair key="additionalField1" value="constant value"/> <!-- 访问上下文变量 --> <KeyValuePair key="additionalField2" value="$${ctx:key}"/> <!-- 输出当前时间 --> <KeyValuePair key="timestamp" value="$${date:yyyy-MM-dd'T'HH:mm:ss.SSSZ}" /> </JsonLayout> |
在启用安全管理器的情况下,你需要进行如下授权:
1 2 3 4 |
grant { permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; |
Maven依赖配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<!-- 使用Log4J2--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.2</version> </dependency> <!-- 让commons logging使用Log4j2 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.8.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <!-- 让Slf4j使用Log4j2 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.8.2</version> </dependency> |
[…] 本文參考 綠色記憶:log4j學習筆記 […]