关于升级 jar 包等前提要求
删掉原先的 log4j-1.XX 等 jar 包
从 1.XX 升级到 2.XX 平稳升级需要的 jar 包,其中包括用 sl4j-1.7.25 (1.7.21 暂时也不需要升级) 的版本包,具体可以看自己的工程配置是否需要
新建 log4j2.xml 配置文件
对于 tomcat 项目的版本要求:(以 7.0.43 为分界线),目前 jdk1.8 可以在 tomcat 7.0.32 ,7.0.94 正常启动代码
三种配置方式:
1、关于 log4j2.xml 的默认存放位置(WEB 项目可以使用)
按照官方推荐默认放到 resource 目录下面,名称必须叫 log4j2.xml ,其余文件名会读取失败,无需配置其它文件,如下图:
2、关于使用自定义路径配置文件(推荐 jar 包读取外部配置文件,比如 Eserver ItmsService 等模块)
2.1 在代码的 web.xml 文件里,增加如下配置,其中 param-value 为配置文件的绝对路径,包含 file:// 开头文件
<!-- 配置log4j2 -->
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
<context-param>
<description>日志配置文件的路径</description>
<param-name>log4jConfiguration</param-name>
<param-value>file:///export/home/rms/EServer4WS/WEB-INF/log4j2.xml</param-value>
</context-param>
2.2 修改 web.xml 文件的开头关于 servlet 的版本,因为 tomcat 7.0 之后,lib 包里面的 servlet-api.jar 包的版本是 3.0,所以必须修改 web.xml 的版本声明,否则会无法初始化 log4j2。
(将图一三个标红的地方应该改为 3.0),结果如图二
图一:
图二:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
3、自定义路径并且实现相关 jar 包的预留接口(不需要改动代码和配置,目前只需要替换相关的 jar 包,把新建的 log4j2.xml 改名为 log4j.xml ,并且放在原先同级目录即可)
针对目前代码现状,为了不改动配置和代码,实现了 log4j-1.2-api-2.8.2.jar 预留的相关接口,具体哪个接口需要查看原本工程初始化 log4j 的类用的是哪个接口,比如 org.apache.log4j.xml.DOMConfigurator 类里面的相关接口,如下所示:
private static Logger logger = LoggerFactory.getLogger(DOMConfigurator.class);
// 如下相关通用实现log4j2初始化代码
public static void configureAndWatch(String configFilename)
{
LoggerContext logContext = (LoggerContext) LogManager.getContext(false);
File conFile = new File(configFilename);
logContext.setConfigLocation(conFile.toURI());
logContext.reconfigure();
logger.debug("init log4j2 ok");
}
该方式只需要按照第一步删除旧的 jar 包,上传新的 jar 包,特别是 log4j-1.2-api-2.8.2.jar 包,即可无缝完成升级
关于 log4j2.xml 配置文件中的注意事项
1、关于 log4j2 日志等级组合过滤器的问题
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
表示只输出 info <= level < warn 的 level 日志,也就是只打印 info 级别
2、关于 DefaultRolloverStrategy 默认删除以及个数的问题
注意%d{MM-dd-yyyy}要用年月日格式,不能加上时分秒,并且最后要有%i,这样log4j2才能判断出哪天一共产生几个文件
示例:
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
3、完整的 log4j2.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="con" target="SYSTEM_OUT">
<!--输出日志的格式-->
<Filters>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<!--<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>-->
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="sql" fileName="/export/home/rms/WEB/logs/sql/sql"
filePattern="/export/home/rms/WEB/logs/sql/sql.%d{yyyy-MM-dd}-%i">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="log" fileName="/export/home/rms/WEB/logs/log/log"
filePattern="/export/home/rms/WEB/logs/log/log.%d{yyyy-MM-dd}-%i">
<Filters>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="err" fileName="/export/home/rms/WEB/logs/err/err"
filePattern="/export/home/rms/WEB/logs/err/err.%d{yyyy-MM-dd}-%i">
<Filters>
<ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="error"></logger>
<logger name="org.mybatis" level="error"></logger>
<logger name="org.apache.struts2" level="error"></logger>
<logger name="org.apache.zookeeper" level="error"></logger>
<logger name="com.opensymphony.xwork2" level="error"></logger>
<root level="info">
<appender-ref ref="con"/>
<appender-ref ref="sql"/>
<appender-ref ref="log"/>
<appender-ref ref="err"/>
</root>
</loggers>
</configuration>
衍生多线程配置问题
不同的线程输出日志到不同的文件中
不同的线程输出日志到不同的文件中有关 Log4j2 的内容很多,在此不一一列出,这里只介绍一种常用方法。如果在开发中遇到任何问题,推荐去官方文档中寻找解决方案。
实现 StrLookup
修改 log4j2.xml 配置文件如下,主要是添加 Routes 标签:
实现 StrLookup 中的 lookup 方法,代码如下:
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.StrLookup;
(name = "thread", category = StrLookup.CATEGORY)
public class ThreadLookup implements StrLookup {
public String lookup(String s) {
return Thread.currentThread().getName();
}
public String lookup(LogEvent logEvent, String s) {
return logEvent.getThreadName() == null ? Thread.currentThread().getName()
: logEvent.getThreadName();
}
}
测试方法如下:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestLog2 {
private static final Logger logger = LogManager.getLogger(TestLog2.class);
public static void main(String[] args) {
new Thread(() -> {
logger.info("info");
logger.debug("debug");
logger.error("error");
}).start();
new Thread(() -> {
logger.info("info");
logger.debug("debug");
logger.error("error");
}).start();
}
}
不同线程不同级别的日志输出到不同的文件中
要实现该功能,还要从 RoutingAppender 身上做文章。RoutingAppender 主要用来评估 LogEvents,然后将它们路由到下级 Appender。目标 Appender 可以是先前配置的并且可以由其名称引用的 Appender,或者可以根据需要动态地创建 Appender。RoutingAppender 应该在其引用的任何 Appenders 之后配置,以确保它可以正确关闭。
RoutingAppender 中的 name 属性用来指定该 Appender 的名字,它可以包含多个 Routes 子节点,用来标识选择 Appender 的条件,而 Routes 只有一个属性 “pattern”,该 pattern 用来评估所有注册的 Lookups,并且其结果用于选择路由。在 Routes 下可以有多个 Route,每个 Route 都必须配置一个 key,如果这个 key 匹配 “pattern” 的评估结果,那么这个 Route 就被选中。同时每个 Route 都必须引用一个 Appender,如果这个 Route 包含一个 ref 属性,那么这个 Route 将引用一个在配置中定义的 Appender,如果这个 Route 包含一个 Appender 的定义,那么这个 Appender 将会根据 RoutingAppender 的上下文创建并被重用。
废话说多了,直接上配置才简洁明了。log4j2.xml 配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- status 的含义为是否记录 log4j2 本身的 event 信息,默认是 OFF -->
<Configuration status="OFF">
<Properties>
<!-- 自定义一些常量,之后使用${变量名}引用 -->
<Property name="logFilePath">logs</Property>
<Property name="logFileName">testLog</Property>
</Properties>
<Appenders>
<!-- 很直白,Console 指定了结果输出到控制台 -->
<Console name="ConsolePrint" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %t %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!-- <File>输出结果到指定文件</File> -->
<!-- <RollingFile>同样输出结果到指定文件,但是使用 buffer,速度会快点</RollingFile> -->
<!-- filePattern:表示当日志到达指定的大小或者时间,产生新日志时,旧日志的命名路径 -->
<!-- PatternLayout:和 log4j 一样,指定输出日志的格式,append 表示是否追加内容,值默认为 true -->
<Routing name="RollingFileDebug_${thread:threadName}">
<Routes pattern="$${thread:threadName}">
<Route>
<RollingFile name="RollingFileDebug_${thread:threadName}"
fileName="${logFilePath}/${logFileName}_${thread:threadName}_debug.log"
filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}-${thread:threadName}-debug_%i.log.gz">
<PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<!-- 注意,如果有多个 ThresholdFilter,那么 Filters 标签是必须的 -->
<Filters>
<!-- 首先需要过滤不符合的日志级别,把不需要的首先 DENY 掉,然后在 ACCEPT 需要的日志级别,次序不能颠倒 -->
<!-- INFO 及以上级别拒绝输出 -->
<ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
<!-- 只输出 DEBUG 级别信息 -->
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Policies>
<!-- 时间策略,每隔 24h 产生新的日志文件 -->
<TimeBasedTriggeringPolicy/>
<!-- 大小策略,每到 30MB 时产生新的日志文件 -->
<SizeBasedTriggeringPolicy size="30MB"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
<Routing name="RollingFileInfo_${thread:threadName}">
<Routes pattern="$${thread:threadName}">
<Route>
<RollingFile name="RollingFileInfo_${thread:threadName}"
fileName="${logFilePath}/${logFileName}_${thread:threadName}_info.log"
filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}-${thread:threadName}-info_%i.log.gz">
<Filters>
<!-- onMatch:Action to take when the filter matches. The default value is NEUTRAL -->
<!-- onMismatch: Action to take when the filter does not match. The default value is DENY -->
<!-- 级别在 ERROR 之上的都拒绝输出 -->
<!-- 在组合过滤器中,接受使用 NEUTRAL(中立),被第一个过滤器接受的日志信息,会继续用后面的过滤器进行过滤,只有符合所有过滤器条件的日志信息,才会被最终写入日志文件 -->
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="30MB"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
<Routing name="RollingFileError_${thread:threadName}">
<Routes pattern="$${thread:threadName}">
<Route>
<RollingFile name="RollingFileError_${thread:threadName}"
fileName="${logFilePath}/${logFileName}_${thread:threadName}_error.log"
filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}-${thread:threadName}-error_%i.log.gz">
<Filters>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="30MB"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
<!-- bufferSize 整数,指定可以排队的 events 最大数量,如果使用 BlockingQueue,这个数字必须是 2 的幂次-->
<!-- includeLocation 默认值是 FALSE,如果指定为 TRUE,会降低性能,但是推荐设置为 TRUE,否则不打印位置行信息-->
<Async name="async" bufferSize="262144" includeLocation="true">
<AppenderRef ref="RollingFileDebug_${thread:threadName}"/>
<AppenderRef ref="RollingFileInfo_${thread:threadName}"/>
<AppenderRef ref="RollingFileError_${thread:threadName}"/>
<!-- 只要是级别比 ERROR 高的,包括 ERROR 就输出到控制台 -->
<AppenderRef ref="ConsolePrint" level="ERROR"/>
</Async>
</Appenders>
<Loggers>
<!-- logger 用于定义 log 的 level 以及所采用的 appender,如果无需自定义,可以使用 root 解决,root 标签是 log 的默认输出形式 -->
<!-- 级别顺序(低到高):TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
<Root level="DEBUG" includeLocation="true">
<!-- appender-ref 中的值必须是在前面定义的 appender -->
<AppenderRef ref="async"/>
</Root>
</Loggers>
</Configuration>
Asynchronous 全局配置
根据官方的性能测试我们知道,Loggers all async
的性能最高,但是我们在上边使用的是 Sync
模式(方法一,因为 Appender 默认是 synchronous 的)或 Async Appender
模式(方法二),那么如何更进一步让所有的 Loggers 都是 Asynchronous 的,让我们的配置更完美呢?想要使用 Loggers all async
只需要做两步操作。
因为 Loggers all async
是基于 LMAX Disruptor 实现的,所以我们首先需要添加这个依赖
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
其次是设置系统属性
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
可以在前面提到的 ThreadLookup 类中,添加静态代码块
static {
System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
}
在 src/main/resources
目录下添加 log4j2.component.properties 配置文件,其内容为
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合使用 Synchronous 和 Asynchronous Loggers
依旧需要依赖 com.lmax:disruptor
,但不需要设置系统属性 log4j2.contextSelector
,在配置中可以混合使用同步和异步的 loggers,使用 <AsyncRoot>
或者 <AsyncLogger>
去指定需要异步的 loggers,<AsyncLogger>
元素还可以包含 <Root>
和 <Logger>
用于同步的 loggers。
一个混合了同步和异步的 Loggers 配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- No need to set system property "Log4jContextSelector" to any value
when using <asyncLogger> or <asyncRoot>. -->
<Configuration status="WARN">
<Appenders>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
immediateFlush="false" append="false">
<PatternLayout>
<Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
</PatternLayout>
</RandomAccessFile>
</Appenders>
<Loggers>
<!-- pattern layout actually uses location, so we need to include it -->
<AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</AsyncLogger>
<Root level="info" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</Root>
</Loggers>
</Configuration>
在上面示例的配置中,root logger 就是同步的,但是 com.foo.Bar 的 logger 就是异步的。
使用 Log4j 日志的注意事项
在使用异步日志的时候需要注意一些事项,如下:
- 不要同时使用 AsyncAppender 和 AsyncLogger,也就是在配置中不要在配置 Appender 的时候,使用 Async 标识的同时,又配置 AsyncLogger,这不会报错,但是对于性能提升没有任何好处。
- 不要在开启了全局同步的情况下,仍然使用 AsyncAppender 和 AsyncLogger。这和上一条是同一个意思,也就是说,如果使用异步日志,AsyncAppender、AsyncLogger 和全局日志,不要同时出现。
- 如果不是十分必须,不管是同步异步,都设置 immediateFlush 为 false,这会对性能提升有很大帮助。
4、如果不是确实需要,不要打印 location 信息,比如 HTML 的 location,或者 pattern 模式里的%C or $class, %F or %file, %l or %location, %L or %line, %M or %method, 等,因为 Log4j 需要在打印日志的时候做一次栈的快照才能获取这些信息,这对于性能来说是个极大的损耗。