优雅灵活记录Java日志 By log4j2

log4j是一个被广泛使用的Java日志记录框架,通过使用该框架,我们可以在自己的项目中根据自身需求灵活配置日志输出格式,从而获得期望格式的日志信息,用于后续分析、错误排查等。log4j2则是原有log4j的升级版本,该版本使用更加简洁方便。

log4j2使用步骤

注:小编我目前使用的 IDE 为 Intellij,自从写Android代码时使用了Android Studio后,就爱上了 Intellij 这一系列的产品了。

  1. 下载 log4j2 的JAR包:从http://logging.apache.org/log4j/2.x/download.html 官网下载最新版Apache Log4j 2 binary 压缩包,解压后会得到许多JAR包,只需要将其中的 log4j-core-2.7.jar 和 log4j-api-2.7.jar 添加到项目中即可;
  2. 在项目的 src 文件夹下添加 log4j2.xml 文件,该文件为 log4j2 的配置文件,默认文件内容为:
<?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. 在项目中使用 log4j2:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LogTest {
    // 获取 Logger 实例
    static Logger logger = LogManager.getLogger();
    public static void main(String[] args) {

        logger.trace("I am a trace message");
        logger.warn("I am a warn message");
        logger.error("I am a error message");
        logger.info("I am a info message");
        logger.fatal("I am a fatal message");
    }
}

效果如下图所示:

日志效果图-1

咦?上面明明定义了五条日志信息,怎么控制台就显示了两条呢?细致的读者maybe会有如此困惑。小小剧透一波:此玄机存在于 log4j 的配置文件中,若想一探究竟,就继续随我一起仔细探究吧!

log4j2 日志级别

log4j2 中默认的日志级别为:trace < debug < info < warn < error < fatal
当设置了日志输出级别时,当前级别及以上级别的日志会输出,而当前级别以下级别的日志则不会输出,系统默认输出级别为 error,也就是说默认设置下,只有 error 和 fatal这两级日志会输出,这也就解释了为什么上面只输出了两条日志信息

  • trace:即追踪,程序每推进一步均可输出一条trace日志,主要用于展现程序运行轨迹
  • debug:调试
  • info:输出你感兴趣的信息,一般用于基本的、高层次的诊断信息。在长时间运行的代码段开始运行及结束运行时应该产生消息,以便知道现在系统在干什么。但是这样的信息不宜太过频繁
  • warn:提示信息
  • error:错误信息,显示一个错误,或一个通用的错误情况,但还不至于会将系统挂起。这种程度的错误一般会触发邮件的发送,将消息发送到alert list中,运维人员可以在文档中记录这个bug并提交
  • fatal:会导致程序崩溃的重大错误,用在极端的情形中,即必须马上获得注意的情况

log4j2 日志组件

下图是Apache官网贴出的log4j的类图,通过该类图可以清晰地看出log4j的类层次结构。

log4j2类层次结构图

由类图可以看出:log4j2中主要包含如下概念/组成部分:Loggerontext、Configuration、Filter、Logger、LoggerConfig、Appender、Layout等,后文会详细解释这些概念。

log4j2 由以下三个核心组件组成:

  • Loggers:Logger负责捕捉事件并将其发送给合适的Appender;
  • Appenders:负责对Logger传来的日志事件进行处理,如过滤、格式化等,然后将处理后的日志事件记录到指定位置;
  • Layouts:负责对日志事件中的数据进行转换和格式化;Layouts决定了数据在一条日志记录中的最终形式。

当Logger记录一个事件时,它将事件转发给适当的Appender。然后Appender使用Layout来对日志记录进行格式化,并将其发送给控制台、文件或其它目标位置。另外,Filters可以让你进一步指定一个Appender是否可以应用在一条特定的日志记录上。在日志配置中,Filters并不是必需的,但可以让你更灵活地控制日志消息的流动。

log4j2核心概念

Loggerontext
LoggerContext在Logger系统中扮演着锚点的角色,一般来说,一个应用只会存在于一个LoggerContext中,但是也可以根据使用场景的差异和项目的具体需求,为一个项目配置多个不同的LoggerContext。在同一个LoggerContext下,log系统是可以互通的。

Configuration
Configuration顾名思义,即是当前LoggerContext中的所有Logger的配置环境,每一个LoggerContext均会拥有一个动态可配置的Configuration,该Configuration 包含了当前LoggerContext环境中所有的Loggers、Appenders、Filters、Layouts等配置信息。注意:在reConfiguration期间,新旧Configuration均会同时存在,然而一旦重配过程结束,旧的Configuration就会失效,配置信息会重定向至新的Configuration中。

Logger
Logger是一个通过给LoggerManager.getLogger()方法传递特定的Logger名称而获得的一个AbstractLogger实现类对象,该对象自身并不具体进行任何操作,而是通过关联一个LoggerConfig完成具体的Logger配置,并通过实现AbstractLogger的方法进行具体的操作。注意:通过同一个名称获得的Logger对象会指向同一个Logger引用。

LoggerConfig
LoggerConfig包含了一个Logger对象的具体配置信息,如该Logger关联的Appenders、Filters、过滤级别等。

Filter
过滤器,定义LogEvent的过滤规则。log4j中定义了三种过滤状态:Accept(接受)、Deny(拒绝)和Nertual(中立)。
Accept:该LogEvent将被执行而无需调用其它过滤器;
Deny:忽略当前LogEvent,并将该LogEvent的控制权移交给过滤器调用者;
Nertual:中立,也就是说,当前LogEvent应该传递给其它过滤器,如果没有别的过滤器可以传递了,那么就由现在这个过滤器来处理。

Appender
Appender意为输出目的地,负责将日志消息转发给期望的输出。它负责接收日志事件,使用Layout格式化事件,然后将其发送给对应的目标。对于一个日志事件,我们可以使用多个Appenders来将事件发送到不同的目标位置。例如,我们可以在控制台上显示一个简单的日志事件的同时,将其通过邮件的方式发送给指定的接收者。目前,Appender可以是console、文件、远程socket服务器、Apache Flume、JMS以及远程 UNIX 系统日志守护进程等。

注意:
在某一个Logger中被启用的log请求将被转发到该Logger相关联的的所有Appenders上,并且还会被转发到LoggerConfig的父级的Appenders上。由此可知,如果设置不当的话,会产生一系列的遗传反应。如:Logger A 继承自 Logger B,并且两者均是root的子节点,若LoggerConfig B 中定义了一个文件作为Appender的话,那么使用LoggerConfig A和LoggerConfig B的logger 的log请求都会在该文件中打印,文件中便会显示很多重复的日志信息,这会给日志分析带来不必要的麻烦。还好这是可以被避免的,通过设置 Appender 的 additivity属性为false即可。(默认情况下,additivity="true")

Layout:
Layout用于定义LogEvent的输出格式。

log4j2配置方式

log4j2 有如下四种配置方式,可以根据你的意愿、喜好等自行选择:

  1. 通过配置文件进行配置,配置文件可以是 XML、 JSON、 YAML 和 properties这四种类型中的任意一种;
  2. 在代码中创建 ConfigurationFactory 和 Configuration 累的实现类;
  3. 在代码中调用Configuration 接口的 API 在默认配置类上添加组件;
  4. 在代码中调用内部类 Logger 的方法实现自定义。

通过配置文件配置log4j

目前小编还只使用了文件的配置方式,所以本文着重于通过配置文件配置log4j,以后有机会再好好研究一波代码配置的方式,然后与看官分享 _

文件配置优先级

对应于四类配置文件:XML、 JSON、 YAML 和 properties,Log4j 分别有与之相应的 ConfigurationFactory 实现类,不同的实现类可以加载不同扩展名的配置文件中的配置信息。当 Log4j 启动时会按照一定的优先级顺序在classpath(一般是项目的src文件夹下)查找项目中的配置文件,若找到一个配置文件,就调用与当前文件扩展名对应的ConfigurationFactory实现类加载该配置文件中的配置信息,否则继续查找下一优先级的配置文件,若未找到任何配置文件,就使用默认配置DefaultConfiguration。
不同配置文件的优先级如下图所示:

配置文件优先级

配置文件详解

如下是个比较完整详细的 XML 形式的配置文件,该文件通过注释的方式提供简要解释以便于理解。

<?xml version="1.0" encoding="UTF-8"?>
<!-- configuration 有多个属性,常用的有:name(配置名)
status(是否记录log4j2本身的event信息,默认是OFF,
可选值有: "trace", "debug", "info", "warn", "error" and "fatal")
dest(错误流输出位置,标准错误流、文件或远程邮件发送)
strict(是否使用strict xml形式,configuration支持concise(简约)和strict(严格)两种模式的xml。
简约模式下,使得文件编写起来更简洁) -->
<configuration status="OFF">
    <!-- 定义如下几个引用名,用于后面配置文件中使用 -->
    <Properties>
        <property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss z} %-5level %class{36}%L%M - %msg%xEx%n
</property><!-- 日志输出格式 -->
        <property name="log-path">${web:rootDir}/logs</property><!-- ${web:rootDir}是项目根目录 -->
        <property name="every_file_size">5M</property><!-- 日志切割的最小单位 -->
        <property name="output_log_level">debug</property><!-- 日志输出级别 -->
    </Properties>

    <!-- 先定义所有的appender(日志输出位置) -->
    <appenders>

        <!-- 输出到控制台 name: 当前appender名 target: 目的位置-->
        <Console name="Console" target="SYSTEM_OUT">
            <!-- ThresholdFilter: 用于定义过滤机制 -->
            <!-- level: 日志过滤级别 -->
            <!-- onMatch="ACCEPT":保留level 及以上级别的日志 -->
            <!-- onMismatch="DENY":丢掉level 以下级别的日志-->
            <!-- onMatch 和 onMissmatch 可选值为:ACCEPT DENY NEUTRAL-->
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />

            <!--这个都知道是输出日志的格式 -->
            <PatternLayout pattern="${log_pattern}" />
        </Console>

        <!-- debug级别日志文件 -->
        <!-- RollingFile:日志滚动输出 -->
        <!--每次大小超过size,则这size大小的日志会自动进行压缩,作为存档 -->
        <RollingFile name="app_debug" fileName="${log-path}/debug/debug.log" 
filePattern="${log-path}/debug/debug-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL" />
                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="${log_pattern}" />
            <!-- SizeBasedTriggeringPolicy: 基于日志大小切分日志 -->
            <SizeBasedTriggeringPolicy size="${every_file_size}"/>
        </RollingFile>

        <!-- info级别日志文件 -->
        <RollingFile name="app_info" fileName="${log-path}/info/info.log" 
filePattern="${log-path}/info/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL" />
                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <PatternLayout pattern="${log_pattern}" />
            <SizeBasedTriggeringPolicy size="${every_file_size}"/>
        </RollingFile>

        <!-- error级别日志文件 -->
        <RollingFile name="app_error" fileName="${log-path}/error/error.log" 
filePattern="${log-path}/error/error-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <PatternLayout pattern="${log_pattern}" />
            <SizeBasedTriggeringPolicy size="${every_file_size}"/>
        </RollingFile>

    </appenders>

    <!-- 定义logger,只有定义了logger并引入之前定义的appender,appender才会生效 -->
    <loggers>
        <!--建立一个默认的root的logger,需要在root的level中指定输出的级别, -->
        <root level="${output_log_level}">
            <appender-ref ref="Console" />
            <appender-ref ref="app_debug"/>
            <appender-ref ref="app_info"/>
            <appender-ref ref="app_error"/>
        </root>
    </loggers>

</configuration>

log4j2 配置节点分析

configuration

configuration决定了当前上下文的loggers配置信息,是配置文件根节点,主要包含如下属性值:

  • name:当前配置的名称
  • dest:输出位置,标准错误流、文件或远程邮件发送
  • status:是否记录 Log4j 自身的event信息,默认为 off,可选值有: "trace", "debug", "info", "warn", "error" and "fatal"
  • strict:是否使用strict xml形式,configuration支持concise(简约)和strict(严格)两种模式的xml。简约模式下,使得文件编写起来更简洁

Appenders

Appenders用于定义当前LoggerContext中所需要使用到的Appender。目前log4j2提供了多种不同类型的appender可供配置。如下介绍几种常用的,如果想了解更全面的信息,可移步官网

Appender类型

ConsoleAppender:
ConsoleAppender是最常用的Appenders之一,它只是将日志消息显示到控制台上。log4j2的默认Appender即为ConsoleAppender。

ConsoleAppender参数如下:

  • filter:用于决定是否需要使用该Appender来处理当前日志事件,通过使用CompositeFilter可以组合使用多个Filter;
  • layout:用于决定如何对日志记录进行格式化,默认情况下使用“%m%n”,它会在每一行显示一条日志记录;
  • follow:用于决定Appender是否需要了解输出(system.out或者system.err)的变化,默认情况是不需要跟踪这种变化;
  • name:用于设置Appender的名字;
  • ignoreExceptions:默认为true,用于决定是否需要记录在日志事件处理过程中出现的异常;
  • target:用于指定输出目标位置,默认情况下使用SYSTEM_OUT,但也可以修改成SYSTEM_ERR

FileAppender:
FileAppenders将日志记录写入到文件中,它负责打开、关闭文件,向文件中追加日志记录,并对文件进行加锁,以免数据被破坏或者覆盖。
FileAppender参数如下:

  • append:是否追加写入,默认为true,表示将日志事件追加在文件末尾,若设置为false,则每次都会先清空文件,然后将日志事件写入文件中;
  • bufferedIO:默认为true,表示使用缓存,也就是说每次进行日志输出时会先将日志事件输出到缓存中,缓存满或者设置了immediateFlush 时,才将日志事件写入磁盘文件。注意:llocking属性不能与该属性同时使用;
  • bufferSize:缓存大小,默认为8192字节;
  • createOnDemand:当需要时才创建日志文件,即只有当有日志事件需要被输出到该文件中时才创建文件;
  • filter:过滤器,含义与ConsoleAppender中相似;
  • fileName:日志文件名;
  • immediateFlush:表示每次写完日志后均进行flush,这可以保证每次日志均被写入磁盘中了;
  • layout:格式化;
  • locking:是否对该日志文件加锁,一般用于多主机场景中,默认为false,该属性对性能影响较大,需要谨慎使用;
  • name:Appender名称;
  • ignoreExceptions:用法同ConsoleAppender。

RollingFileAppender

滚动输出日志到文件中,RollingFileAppender 根据TriggeringPolicy 和RolloverPolicy滚动将日志事件输出到fileName指定的文件中。TriggeringPolicy用于决定是否触发RolloverPolicy所定义的日志滚动操作,若未设置RolloverPolicy ,则会使用默认设置 DefaultRolloverStrategy。

RollingFileAppender参数如下:

  • append、bufferedIO、bufferSize、createOnDemand、filter、fileName、immediateFlush、layout、name、ignoreExceptions等属性与FileAppender类似,就不一一概述了;
  • filePattern:存档日志文件的文件名模式,模式格式依赖于所采用的RolloverPolicy ;
  • policy:TriggeringPolicy,触发策略;触发策略有如下几种:Composite Triggering Policy(组合策略)、CronTriggeringPolicy(定时触发)、OnStartupTriggeringPolicy(JVM虚拟机启动时触发)、SizeBasedTriggeringPolicy(基于日志文件大小触发)、TimeBasedTriggeringPolicy(基于时间触发)
  • strategy:RolloverStrategy,决定存档文件名称、数量和位置的策略。

其它Appender:
SyslogAppenders将日志记录发送给本地或者远程系统的日志服务;SMTPAppender会将日志内容以邮件的形式发送出去;FailoverAppender设置在处理日志的过程中,如果一个或者多个Appender失败,自动切换到其他Appender上等,更多信息详见官网

Layout

log4j2中使用较多的Layout为PatternLayout,格式为:

<PatternLayout pattern = "%d{yyyy-MM-dd HH:mm:ss z} %-5level %class{36}%L%M - %msg%xEx%n" />

%d{HH:mm:ss:SSS}:日志输出时间,精确到毫秒
%t:当前线程名称
%-5level:日志级别,-5表示左对齐并固定输出5个字符,如果不足右边补零
%logger:logger名称
%msg:日志文本
%n:换行
%F:文件所在的类文件名
%L:行号
%M:所在方法名
%l:语句所在的行数,包括类名,方法名,文件名,行数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,937评论 1 13
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,957评论 0 6
  • from:https://www.cnblogs.com/ITtangtang/p/3926665.html一、L...
    enshunyan阅读 3,274评论 0 0
  • 一、Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layo...
    默默守护阅读 1,897评论 2 8
  • log4j 1.1 简介 Log4j是一个由Java编写可靠、灵活的日志框架,是Apache旗下的一个开源项目;现...
    贾博岩阅读 7,887评论 1 32