这是在公司内部的一次升级实践,删除了很多隐私的内容,所以可能不是很完整。
在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索。绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?
新的Log4j 2.0版本有了大幅的性能提升、新的插件系统,以及配置设置方面的很多改善。Log4j 1.x 在高并发情况下出现死锁导致cpu使用率异常飙升,而Log4j2.0基于LMAX Disruptor的异步日志在多线程环境下性能会远远优于Log4j 1.x和logback ——官方测试结果。
本次升级是以thrift服务化项目为例子进行的,后续会在其他项目中进行,本次工作内容为:Log4j1.x 升级到 Log4j2(如果不想了解原理,可以直接跳到:3、升级方式)
吞吐量测试
平均耗时
其中:
Loggers mixed sync/async: 同步与异步logger可以混合使用,分别由标签
指定
异步Logger与异步Appender区别:AsyncAppender使用ArrayBlockingQueue来处理message,AsyncLogger使用LMAX Disruptor
- AsyncAppender的做法是:应用线程创建LogEvent将其塞入Queue,消费线程取出LogEvent写磁盘。在这种框架的可扩展性不好,当加倍消费线程时各个线程的吞吐量会减半,所以总吞吐量并不会得到增加。原因是,并发queue是标准java库的一部分,会使用锁来保证数据传递的正确性。
- LMAX Disruptor是一个无锁数据结构,可以在线程间传递消息。详细介绍可访问其网站:https://github.com/LMAX-Exchange/disruptor/wiki/Introduction
更多性能测试信息可参考官方报告:
http://logging.apache.org/log4j/2.x/manual/async.html#Performance
http://logging.apache.org/log4j/2.x/performance.html
示例:
xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="pattern_layout">%d %-5p (%F:%L) - %m%nProperty>
<Property name="LOG_HOME">/var/***/logsProperty>
Properties>
<Appenders>
<Console name="console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${pattern_layout}"/>
Console>
<RollingRandomAccessFile name="file"
fileName="${LOG_HOME}/${sys:app.key}.log"
filePattern="${LOG_HOME}/${sys:app.key}.log.%d{yyyy-MM-dd}">
<PatternLayout pattern="${pattern_layout}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
Policies>
RollingRandomAccessFile>
<RollingRandomAccessFile name="access_kpi"
fileName="${LOG_HOME}/${sys:app.key}_access_kpi.log"
filePattern="${LOG_HOME}/${sys:app.key}_access_kpi.log.%d{yyyy-MM-dd}">
<PatternLayout pattern="${pattern_layout}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
Policies>
RollingRandomAccessFile>
<RollingRandomAccessFile name="jmonitorappender"
fileName="${LOG_HOME}/${sys:app.key}.jmonitor.log"
filePattern="${LOG_HOME}/${sys:app.key}.jmonitor.%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="${pattern_layout}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
Policies>
RollingRandomAccessFile>
<RollingRandomAccessFile name="jmonitorlogstoreappender"
fileName="${LOG_HOME}/${sys:app.key}.jmonitor.logstore.log"
filePattern="${LOG_HOME}/${sys:app.key}.jmonitor.logstore.%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="${pattern_layout}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
Policies>
RollingRandomAccessFile>
<Scribe name="errorLog">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<Property name="hostname">${sys:app.key}Property>
<Property name="scribeHost">127.0.0.1Property>
<Property name="scribePort">4252Property>
<Property name="scribeCategory">cos_errorlogProperty>
<Property name="printExceptionStack">falseProperty>
<Property name="addStackTraceToMessage">falseProperty>
<Property name="timeToWaitBeforeRetry">6000Property>
<Property name="sizeOfInMemoryStoreForward">100Property>
<PatternLayout
pattern="%d %p $${sys:app.host} $${sys:app.ip} errorlog appkey=$${sys:app.key} location=%F:%L rawlog=%replace{%replace{%m}{=}{:}}{\n|\t}{
} rawexception=%replace{%replace{%ex}{=}{:}}{\n|\t}{
}%n"/>
Scribe>
Appenders>
<Loggers>
<Logger name="access_kpi" level="INFO" includeLocation="true" additivity="false">
<AppenderRef ref="access_kpi"/>
Logger>
<Logger name="com.taobao.tair3.client" level="WARN" includeLocation="true" additivity="false">
<AppenderRef ref="file"/>
<AppenderRef ref="errorLog"/>
Logger>
<Logger name="org.springframework" level="WARN"/>
<Logger name="org.apache.zookeeper" level="ERROR"/>
<Logger name="org.springframework.web" level="WARN"/>
<Root level="INFO" includeLocation="true">
<AppenderRef ref="file"/>
<AppenderRef ref="console"/>
<AppenderRef ref="errorLog"/>
Root>
Loggers>
Configuration>
我们先看看Configuration的一些特性:
- Configuration代表Log4j2的配置文件,它和LoggerContext组件一一对应(关于LoggerContext请看下文),它维护Log4j2各个组件之间的关系,其中,一个Configuration对应多个LoggerConfig组件。
- Configuration可以通过四种方式配置:a)配置文件(XML、JSON和YAML);b)创建ConfigurationFactory和Configuration实现;c)通过代码调用Configuration的API构造;d)在Logger内部调用API函数构造。
- Configuration能够在应用程序初始化的过程中进行自动装配,其配置内容按照一定的顺序获取,详见:AutomaticConfiguration。
- 当我们给Configuration设置monitorInterval时,这可以使得log4j2阶段性的读取配置文件,并重新构造Configuration。在这一过程中,log4j2不会丢失日志事件。
<Configuration status="WARN">
...
Configuration>
该片段表明log4j2配置文件的所有内容都在这个标签内,其status属性为“WARN”说明:log4j2内部的日志会将日志级别大于WARN的日志打印到Console。除了该字段,Configuration还包括其他属性,详见:ConfigurationSyntax。
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
Appenders>
所有的Appender将在
<Loggers>
<Logger name="com.foo.Bar" level="trace" additivity="false" includeLocation="true">
<AppenderRef ref="Console"/>
Logger>
<Root level="error">
<AppenderRef ref="Console"/>
Root>
Loggers>
所有的Logger将在
此处有必要说明additivity字段,通过配置该字段,我们可以规定是否将日志事件传递到Logger的父结点处理,其默认值为true(即默认交给parent Logger处理)。
Logger默认不会获取location信息,因此,若我们的Layout或Filter等需要location信息,我们必须给相应的设置“includeLocation=true”
<Filters>
<Marker marker="EVENT" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL">
<KeyValuePair key="User1" value="DEBUG"/>
DynamicThresholdFilter>
Filters>
log4j2还有一个很重要的组件——Filter,详见Filter小节。此处通过
此处,Filter是通过Configuration的直接子元素配置,因此,LogEvents若被该Filter过滤之后则不会传递给Logger处理。
Log4j2提供了异步Logger,通过不同线程实现I/O操作,目的在于为我们的应用程序提高性能。我们先来看一看它主要在哪些方面做改进:
AsyncLoggers虽然带来了极大的性能提升,我们应该经常使用。不过,它也有一些缺点,因此,我们要根据具体的应用场景决定使用同步还是异步的方式,详见:Trade-offs。
以下开始说明***服务化项目如何由:Log4j1.x 升级到 Log4j2
需要确定项目pom文件中依赖的其他的jar中也不再依赖log4j及slf4j-log4j12,具体方式可以通过IDE提供的功能或者直接使用mvn dependency:tree确定依赖关系。
由于引用的jar中很多依然使用的为log4j,因此已经升级过log4j2的项目,每次在新增依赖的时候,一定需要确定一下,引用的jar是否含有对低版本的依赖,并且exclusion掉。
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
<exclusion>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
exclusion>
exclusions>
<properties>
<org.slf4j-version>1.7.12org.slf4j-version>
<log4j2-version>2.3log4j2-version>
properties>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>${org.slf4j-version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-1.2-apiartifactId>
<version>${log4j2-version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>${log4j2-version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>${log4j2-version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>${log4j2-version}version>
dependency>
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.sankuai.meituangroupId>
<artifactId>scribe-log4j2artifactId>
<version>1.0.9version>
dependency>
在JVM启动参数中增加 -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector 开启异步日志。(目前针对scribe的appender为同步appender,如果不开启异步机制会导致线程block)
删除原log4j.xml配置文件,新增log4j2.xml,注意:需要保证log4j2.xml在resource根目录内,否则会导致配置文件加载不到(即log4j2.xml需要在class根目录内)
private static final Logger LOGGER = LoggerFactory.getLogger(Boot.class);
使用slf4j进行log的定义,注意需要保证项目中不再依赖于slf4j1。如果启动时有如下提示,说明依然依赖了多个slf4j
http://logging.apache.org/log4j/2.x/manual/migration.html#Configuring_Log4j_2
http://logging.apache.org/log4j/2.x/guidelines.html
http://logging.apache.org/log4j/2.x/performance.html
http://www.infoq.com/cn/articles/things-of-java-log-performance
http://www.infoq.com/cn/news/2014/08/apache-log4j2
欢迎关注 高广超的简书博客 与 收藏文章 !
欢迎关注 头条号:互联网技术栈 !
个人介绍:
高广超 :多年一线互联网研发与架构设计经验,擅长设计与落地高可用、高性能互联网架构。目前就职于美团网,负责核心业务研发工作。
本文首发在 高广超的简书博客 转载请注明!