log4j2的异步使用及添加自定义参数方式
作者:住街对面的查理
log4j2异步使用及添加自定义参数
关于log4j2的性能和原理就不赘述了,这篇主要讲使用,配置文件解读,和添加自定义参数,偏应用的一篇文章。
相比与其他的日志系统,log4j2丢数据这种情况少;disruptor技术,在多线程环境下,性能高于logback等10倍以上;利用jdk1.5并发的特性,减少了死锁的发生;
目前看来,log4j2的性能最突出。
添加依赖(这里省略了版本号)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions><!-- 去掉springboot默认配置 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <!-- 引入log4j2依赖 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
然后再各个项目中添加文件log4j2.xml,在要使用的类上添加@slf4j 注解(lombok的),即可使用log对象。
log4j 2.0与以往的1.x有一个明显的不同,其配置文件只能采用.xml, .json或者 .jsn。在默认情况下,系统选择configuration文件的优先级如下:(classpath为src文件夹)
- classpath下名为 log4j-test.json 或者log4j-test.jsn文件
- classpath下名为 log4j2-test.xml
- classpath下名为 log4j.json 或者log4j.jsn文件
- classpath下名为 log4j2.xml
level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
下面写一个生产可用的log4j2.xml的模板
<?xml version="1.0" encoding="UTF-8"?> <!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!-- monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN"> <Properties> <!--常用变量配置 供下文中使用--> <property name="APP_NAME">项目名称</property> <property name="LOGGER_LEVEL">INFO</property> <!--日志路径 对应服务器路径--> <property name="LOGGER_PATH">/data/logs</property> <Property name="LOG_HOME">${LOGGER_PATH}/${APP_NAME}</Property> <!--文件大小--> <Property name="FILE_SIZE">10M</Property> <!--日志格式--> <Property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</Property> <!--日志文件命名格式--> <Property name="rolling_file_name">-%d{yyyy-MM-dd}.%i.zip</Property> <!--日志留存最大文件数--> <Property name="rollover_strategy_max">30</Property> <Property name="LOG_HOME_PROJECT">${LOG_HOME}/${APP_NAME}-project</Property> <Property name="LOG_HOME_PROJECT_ERROR">${LOG_HOME}/${APP_NAME}-project-error</Property> <Property name="LOG_HOME_SQL">${LOG_HOME}/${APP_NAME}-sql</Property> </Properties> <appenders> <!--控制台打印 及格式--> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="${log_pattern}" /> </Console> <!--定义rolling() 供下文使用--> <RollingRandomAccessFile name="projectRolling" fileName="${LOG_HOME_PROJECT}.log" filePattern="${LOG_HOME_PROJECT}${rolling_file_name}" immediateFlush="false" append="true"> <PatternLayout> <Pattern>${log_pattern}</Pattern> <Charset>UTF-8</Charset> </PatternLayout> <Policies> <!--滚动规则 时间或者文件大小 滚动后将按照filePattern命名--> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="24"/> <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/> </Policies> <DefaultRolloverStrategy max="${rollover_strategy_max}" /> </RollingRandomAccessFile> <RollingRandomAccessFile name="projectErrorRolling" fileName="${LOG_HOME_PROJECT_ERROR}.log" filePattern="${LOG_HOME_PROJECT_ERROR}${rolling_file_name}" immediateFlush="false" append="true"> <Filters> <!-- 只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)可以使用多个ThresholdFilter 达到精准过滤某个级别的日志--> <ThresholdFilter level="${LOGGER_LEVEL}" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <PatternLayout> <Pattern>${log_pattern}</Pattern> <Charset>UTF-8</Charset> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/> </Policies> <DefaultRolloverStrategy max="${rollover_strategy_max}" /> </RollingRandomAccessFile> </RollingRandomAccessFile> <RollingRandomAccessFile name="sqlRolling" fileName="${LOG_HOME_SQL}.log" filePattern="${LOG_HOME_SQL}${rolling_file_name}" immediateFlush="false" append="true"> <PatternLayout> <Pattern>${log_pattern}</Pattern> <Charset>UTF-8</Charset> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="${FILE_SIZE}"/> </Policies> <DefaultRolloverStrategy max="${rollover_strategy_max}" /> </RollingRandomAccessFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <loggers> <!--异步日志 区别于普通使用的logger root 搭配--> <!--name 为包名 对应配置日志输出等级level --> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <!--将org.springframework包下的日志打印到Console控制台,projectRolling文件,projectErrorRolling(error级别单独一个文件)--> <AsyncLogger name="org.springframework" level="${LOGGER_LEVEL}" additivity="false"> <appender-ref ref="Console"/> <appender-ref ref="projectRolling"/> <appender-ref ref="projectErrorRolling"/> </AsyncLogger> <AsyncLogger name="com.alibaba.dubbo" level="${LOGGER_LEVEL}" additivity="false"> <appender-ref ref="Console"/> <appender-ref ref="projectRolling"/> <appender-ref ref="projectErrorRolling"/> </AsyncLogger> <AsyncLogger name="druid.sql" level="${LOGGER_LEVEL}" additivity="false"> <appender-ref ref="Console"/> <appender-ref ref="sqlRolling"/> </AsyncLogger> <AsyncLogger name="org.mybatis" level="${LOGGER_LEVEL}" additivity="false"> <appender-ref ref="Console"/> <appender-ref ref="sqlRolling"/> </AsyncLogger> <AsyncLogger name="com.项目包名" level="${LOGGER_LEVEL}" additivity="false"> <appender-ref ref="Console"/> <appender-ref ref="projectRolling"/> <appender-ref ref="projectErrorRolling"/> </AsyncLogger> <AsyncRoot level="${LOGGER_LEVEL}"> <appender-ref ref="Console"/> <appender-ref ref="projectRolling" /> <appender-ref ref="projectErrorRolling" /> </AsyncRoot> </loggers> </configuration>
如上配置会产生3个日志文件
- 项目名称-project.log
- 项目名称-project-error.log
- 项目名称-sql.log
补充知识
onMatch和onMismatch都有三个属性值,分别为Accept、DENY和NEUTRAL
分别介绍这两个配置项的三个属性值:
onMatch=“ACCEPT”
表示匹配该级别及以上onMatch=“DENY”
表示不匹配该级别及以上onMatch=“NEUTRAL”
表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上onMismatch=“ACCEPT”
表示匹配该级别以下onMismatch=“NEUTRAL”
表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的onMismatch=“DENY”
表示不匹配该级别以下的
自定义日志格式
%d{HH:mm:ss.SSS}
表示输出到毫秒的时间%logger{36}
简单理解为类名%thread
输出当前线程名称%-5level
输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0%logger
输出logger名称,因为Root Logger没有名称,所以没有输出%msg
日志文本%n
换行%X{xxx} xxx
为自定义参数
如何在日志中添加自己想传的参数?
定义拦截器(web服务拦controller,dubbo服务拦api),每次请求过来,拦住,然后将自定义参数传入。至于自定义参数怎么存,就是另一个问题了。
eg: traceId 跟踪号 对应log4j2.xml中的 %X{traceId}
下面是关键代码:
public class ContextFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String traceId = UUID.randomUUID().toString().replaceAll("-", ""); // org.slf4j.MDC MDC.put(CommonConsts.TRACE_ID_LOG, traceId);// 用来给日志文件使用 // org.apache.logging.log4j.ThreadContext ThreadContext.put(CommonConsts.TRACE_ID_LOG, traceId); //经测试,这两行都可行。 filterChain.doFilter(servletRequest, servletResponse); }
最后的日志打印效果如下:
2019-05-29 12:04:30.122 [http-nio-8080-exec-2] [2333333] INFO com.core.web.filter.ContextFilter - 接口调用时间:245毫秒
log4j 输入自定义参数
使用log4j、log4j2输入日志时,有时想追加打印自定义参数(比如客户端环境:手机型号、浏览器数据,request数据、用户数据等),以便于快速定位问题所在。
亦或在多线程环境中,快速定位哪些日志是由同一用户输出,便于其他工具进行日志分析。
log4j提供了ThreadContext 线程上下文类,用于存储自定义数据,以便在输入日志时,包含指定数据。
测试代码如下
package com.howtodoinjava.log4j2.examples; import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; public class Log4j2HelloWorldExample { private static final Logger LOGGER = LogManager.getLogger(Log4j2HelloWorldExample.class.getName()); public static void main(String[] args) { //Add context information ThreadContext.put("id", UUID.randomUUID().toString()); ThreadContext.put("ipAddress", "192.168.21.9"); LOGGER.debug("Debug Message Logged !!"); LOGGER.info("Info Message Logged !!"); LOGGER.debug("Another Debug Message !!"); //Clear the map ThreadContext.clearMap(); LOGGER.debug("Thread Context Cleaned up !!"); LOGGER.debug("Log message with no context information !!"); } }
而后在 log4j.xml 中指定上述参数,
单独使用%X以包含地图的全部内容。
使用%X{key}包括指定的键。
使用%x包括堆栈的全部内容。
在实际应用时,一般是在过滤器、拦截器进行上述操作,方法之前,将数据绑定到线程中,方法完成后,清理线程数据。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。