Log4J2官方文档学习实践

819 阅读9分钟

摘要:Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

Log4J2有各种Appender,通过不同的方式去输出Log。log4j2.xml

例如一些XML标签的应用 :

  • 输出到控制台
  • 写道临时文件中
  • 滚动写入,自动归档 (文件到了100MB ,就会归档

支持Cassandra

写到Cassandra数据库

支持Console

控制台

支持Failover

往一个地方写失败了就写入到另一个地方去

支持Flume

大数据日志采集系统

支持JDBC

写入关系型数据库

支持JMS

写入外部消息系统

支持JPA

JPA标准写入数据库

支持HTTP

访问某一个HTTP接口

支持Kafka

写入消息中间件

支持Mapped File

内存映射文件

Async

Appender支持异步的Append,也就是Async模式。

他的工作原理就是接收一些引用定位到其他的appender,使用一个独立的线程去写日志,也就是完全异步化。

但是这里需要注意:写到其他的appender抛出的异常是直接不可见的,是隐藏起来的。

当然一般来说是配好了所有的Appender最后去做的一个配置。默认情况下Async会使用一个ArrayBlockingQueue。当我们的应用使用多线程去跑程序时必须对Async进行关注,他可能会导致锁的竞争,会导致性能的下降。

当我们需要在异步环境中提高打日志的性能,一般来说建议用lock-free Async Loggers,也就是无锁化异步Logger。倒也无需太关注AsyncAppender。

官方文档见:Log4j – Log4j 2 Appendershttps://logging.apache.org/log4j/2.x/manual/appenders.html#AsyncAppender

首先我们关注一下配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Async name="Async">
      <AppenderRef ref="MyFile"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers>
</Configuration>

如代码块所见:Root Logger里面只引用了一个Async,Async里面再去引用别人,这里是MyFile。

通过这种方式可以让我们把日志先放入ArrayBlockingQueue,再去往其他的Append里面写入。

当然里面也有一些配置:

AppenderRef

AsyncAppend引用的一些其他的Append

blocking

如果设置为true,队列满了就会阻塞住,这个其实就不太好了

shutdownTimeout

关闭时的超时时间

BlockingQueueFactory

你可以自己定义一个Queue的工厂

不过这些也不用细究

文档里也说了正常来说也不建议使用,这个BlockingQueueFactory提供了几种queue的实现,比如

  • ArrayBlockingQueue:默认的实现
  • DisruptorBlockingQueue
  • JCToolsBlockingQueue
  • LinkedTransferQueue

但其实比较推荐的并不是用Async,而是异步的Logger

FailoverAppender

官方文档见:​​​​​​Log4j – Log4j 2 Appendershttps://logging.apache.org/log4j/2.x/manual/appenders.html#FailoverAppender

当我们使用FailoverAppender时,就可以先定义一个primary Appender,再定义一个secondary appenders,这个东西说实在还是挺有用的。

因为他会优先往primary里面打印,如果失败了再往secondary里面去写。那么如果我们后续有需求要把日志输入到ES中去,是不是就得用这个了?

到了那时我们primary就是打印进ES中,但是因为可能有一些不可预知的异常,比如网络通信导致写入失败了,那么肯定就得落盘到磁盘文件中去。

Parameter Name

Type

Description

filter

Filter

一个过滤器,用于确定事件是否应由此 Appender 处理。

使用 CompositeFilter 可以使用多个过滤器。

primary

String

要使用的主要 Appender 的名称。

failovers

String[]

要使用的辅助 Appender 的名称。

name

String

The name of the Appender.

retryIntervalSeconds

[重试间隔]

integer

默认60,如果primary失败了那么不会进行重试.

ignoreExceptions

boolean

默认值为 true,会导致遇到的异常时被记录到内部,然后被忽略。

当设置为 false 时,异常将被传播给调用者。

target

String

Either "SYSTEM_OUT" or "SYSTEM_ERR". The default is "SYSTEM_ERR".

官方配置示例:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz"
                 ignoreExceptions="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <TimeBasedTriggeringPolicy />
    </RollingFile>
    <Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
      <PatternLayout pattern="%m%n"/>
    </Console>
    <Failover name="Failover" primary="RollingFile">
      <Failovers>
        <AppenderRef ref="Console"/>
      </Failovers>
    </Failover>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Failover"/>
    </Root>
  </Loggers>
</Configuration>

我们可以看到在示例中直接引用了Failover,Failover定义了滚动文件。

如果失败了就可以配置控制台输出。

KafkaAppender

这个其实我们可以关注一下,把日志往Kafka里面去打。对应着我们可以开一个后台的服务,比如统一日志中心。日志中心里面就包含了Kafka以及对应的消费组件。消费组件负责把Kafka里的日志写入到ES中去,其实这也是一个不错的思路。然后我们在基于ES做一个对应的日志查询系统就可以了。

这个配置相对简单,就不一一赘述了。你可以配置一个Kafka的Topic,指定一个Key进行partition分区,确保同一个服务有序的分发到同一个分区中去。

配置syncSend默认为true,这个是为了同步的去发送以保证写Kafka出错就能反馈回来。

还有就是properties,我们需要设置的就是bootstrap.servers这样的一个地址

官方示例:

<?xml version="1.0" encoding="UTF-8"?>
  ...
  <Appenders>
    <Kafka name="Kafka" topic="log-test">
      <PatternLayout pattern="%date %message"/>
        <Property name="bootstrap.servers">localhost:9092</Property>
    </Kafka>
  </Appenders>

NoSQLAppender

当然我们也可以把日志直接写到ES这种NoSQL数据库里,没必要走什么Kafka了。他会用一个比较轻量级的接口,目前接口对MongoDB和CouchDB都有实现。如果说我们需要自定义provider其实也非常简单。这个其实还是比较值的学习的。

这里的配置最重要的就是定义一个自己的provider(NoSqlProvider),我们对ES的定义直接写在这个配置就可以了。

文档上直接说了推荐我们阅读一下MongoDB和CounchDB的源码作为一个指导【To create your own custom provider, read the JavaDoc for the NoSQLProvider, NoSQLConnection, and NoSQLObject classes】,学习一下日志是如何直接写入NoSQL数据库中去的。

当然,如果这样做相对来说要麻烦一些,如果用Kafka来中转就会简单很多

{
    "level": "WARN",
    "loggerName": "com.example.application.MyClass",
    "message": "Something happened that you might want to know about.",
    "source": {
        "className": "com.example.application.MyClass",
        "methodName": "exampleMethod",
        "fileName": "MyClass.java",
        "lineNumber": 81
    },
    "marker": {
        "name": "SomeMarker",
        "parent" {
            "name": "SomeParentMarker"
        }
    },
    "threadName": "Thread-1",
    "millis": 1368844166761,
    "date": "2013-05-18T02:29:26.761Z",
    "thrown": {
        "type": "java.sql.SQLException",
        "message": "Could not insert record. Connection lost.",
        "stackTrace": [
                { "className": "org.example.sql.driver.PreparedStatement$1", "methodName": "responder", "fileName": "PreparedStatement.java", "lineNumber": 1049 },
                { "className": "org.example.sql.driver.PreparedStatement", "methodName": "executeUpdate", "fileName": "PreparedStatement.java", "lineNumber": 738 },
                { "className": "com.example.application.MyClass", "methodName": "exampleMethod", "fileName": "MyClass.java", "lineNumber": 81 },
                { "className": "com.example.application.MainClass", "methodName": "main", "fileName": "MainClass.java", "lineNumber": 52 }
        ],
        "cause": {
            "type": "java.io.IOException",
            "message": "Connection lost.",
            "stackTrace": [
                { "className": "java.nio.channels.SocketChannel", "methodName": "write", "fileName": null, "lineNumber": -1 },
                { "className": "org.example.sql.driver.PreparedStatement$1", "methodName": "responder", "fileName": "PreparedStatement.java", "lineNumber": 1032 },
                { "className": "org.example.sql.driver.PreparedStatement", "methodName": "executeUpdate", "fileName": "PreparedStatement.java", "lineNumber": 738 },
                { "className": "com.example.application.MyClass", "methodName": "exampleMethod", "fileName": "MyClass.java", "lineNumber": 81 },
                { "className": "com.example.application.MainClass", "methodName": "main", "fileName": "MainClass.java", "lineNumber": 52 }
            ]
        }
    },
    "contextMap": {
        "ID": "86c3a497-4e67-4eed-9d6a-2e5797324d7b",
        "username": "JohnDoe"
    },
    "contextStack": [
        "topItem",
        "anotherItem",
        "bottomItem"
    ]
}

RollingFileAppender

这个还是挺常用的,RollingFile其实也算是输出流的一种,往磁盘文件里面输出嘛。

如果触发了TriggeringPolicy就会将文件进行归档。如果触发了RolloverPolicy就会把文件进行清理。所以说RollingFileAppender会使用RollingFileManager去执行磁盘文件的IO以及执行rollover开辟新文件删除旧文件。当RolloverFileAppender不能被共享的时候,RollingFileManager是可以被共享的。这个其实是说两个应用部署在一个tomcat的时候是可以共享的,当然这个你可以不用管他。😀

RolloverFileAppender可以设置两种policy:TriggeringPolicy和RolloverPolicy。TriggeringPolicy会决定在写文件的时候何时Rollove滚出来一个新的日志文件。当我们没有设置Rollove的话默认会使用DefaultRolloverSrategy。

从log4j-2.5以后我们可以在DefaultRollover里面去配置一个自定义的日志文件的删除行为。从2.8开始会默认用DirectWriteRolloverStrategy。

Parameter Name

Type

Description

append

boolean

是往文件里追加而不是重写

bufferedIO

boolean

设置为true就会开启一个buffer。buffer如果被写满了就往磁盘里进行溢写

bufferSize

int

When bufferedIO is true, this is the buffer size, the default is 8192 bytes.

createOnDemand

boolean

如果有需要的话就会自己去创建文件

filter

Filter

A Filter to determine if the event should be handled by this Appender. More than one Filter may be used by using a CompositeFilter.

fileName

String

The name of the file to write to. If the file, or any of its parent directories, do not exist, they will be created.

filePattern

String

The pattern of the file name of the archived log file. The format of the pattern is dependent on the RolloverPolicy that is used. The DefaultRolloverPolicy will accept both a date/time pattern compatible with SimpleDateFormat and/or a %i which represents an integer counter. The pattern also supports interpolation at runtime so any of the Lookups (such as the DateLookup) can be included in the pattern.

immediateFlush

boolean

如果为true那么每次写文件都会被flush,每次都会写到磁盘文件中去。只有在使用同步Loggers中才生效,并且只有对于一批数据才会执行flush

layout【布局】

Layout

The Layout to use to format the LogEvent. If no layout is supplied the default pattern layout of "%m%n" will be used.

name

String

The name of the Appender.

policy

TriggeringPolicy

The policy to use to determine if a rollover should occur.

strategy

RolloverStrategy

The strategy to use to determine the name and location of the archive file.

ignoreExceptions

boolean

The default is true, causing exceptions encountered while appending events to be internally logged and then ignored. When set to false exceptions will be propagated to the caller, instead. You must set this to false when wrapping this Appender in a FailoverAppender.

filePermissions

String

File attribute permissions in POSIX format to apply whenever the file is created.

Underlying files system shall support POSIX file attribute view.

Examples: rw------- or rw-rw-rw- etc...

fileOwner

String

File owner to define whenever the file is created.

Changing file's owner may be restricted for security reason and Operation not permitted IOException thrown. Only processes with an effective user ID equal to the user ID of the file or with appropriate privileges may change the ownership of a file if _POSIX_CHOWN_RESTRICTED is in effect for path.

Underlying files system shall support file owner attribute view.

fileGroup

String

File group to define whenever the file is created.

Underlying files system shall support POSIX file attribute view.

<?xml version="1.0" encoding="UTF-8"?>
<!-- monitorInterval:log4j2会自动检测配置文件的变化,如果变化了就重新加载,自动检测的时间间隔 -->
<configuration monitorInterval="30">
    <!-- 我们可以配置很多种不同的appender,有的是在控制台打印,有的是打印文件,甚至可能是打印不同的文件 -->
    <appenders>
        <!-- 打印到控制台的appender -->
        <console name="ConsoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d{HH:mm:ss.SSS}] [%p] - %l - %m%n"/>
        </console>

        <!-- 临时打印到文件的日志信息,特别适合测试环境的日志打印,每次系统重启都会清空文件 -->
        <File name="TestFileAppender" fileName="/usr/local/demo-log4j2/test.log" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!-- rolling,一个日志文件存满了以后,可以让他自动归档到指定目录下,也可以保留几个文件,超过文件数量就清理掉 -->
        <RollingFile name="InfoRollingFileAppender"
                     fileName="/usr/local/demo-log4j2/logs/info.log"
                     filePattern="/usr/local/demo-log4j2/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>

        <RollingFile name="WarnRollingFileAppender"
                     fileName="/usr/local/demo-log4j2/logs/warn.log"
                     filePattern="/usr/local/demo-log4j2/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="7" />
        </RollingFile>

        <RollingFile name="ErrorRollingFileAppender"
                     fileName="/usr/local/demo-log4j2/logs/error.log"
                     filePattern="/usr/local/demo-log4j2/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
    </appenders>

    <!-- 定义logger日志记录组件 -->
    <loggers>
        <root level="all">
            <appender-ref ref="ConsoleAppender"/>
            <appender-ref ref="InfoRollingFileAppender"/>
            <appender-ref ref="WarnRollingFileAppender"/>
            <appender-ref ref="ErrorRollingFileAppender"/>
        </root>
    </loggers>
</configuration>

Log4j2的layouts布局

Log4j – Log4j 2 Layoutshttps://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout

Pattern Layout是我们比较常用的一种布局方式。

例如如下示例

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

C{precision} 或者 class{precision} 就是打印class的类

d{pattern} 或者 date{pattern} 就是 打印时间

Pattern

Example

%d{DEFAULT}

2012-11-02 14:34:02,123

%d{DEFAULT_MICROS}

2012-11-02 14:34:02,123456

其他的大家可以参考文档去做,在这里就不细究了。

Log4j2的filter过滤

filter其实本质上就是定义appender过滤出自己感兴趣的log,比如符合warn这个级别的日志就接受,不符合就拒绝。

<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>

filter的种类也是繁多,比如**BurstFilter。**它可以控制你打log的频率,甚至可以用它来进行限流😂

但其实我们一般用ThresholdFilter就足够了。