slf4j+log4j2配置项目日志管理系统

日志解决方案这么多,为何我们选择slf4j+log4j2来作为日志系统呢?首先我们来将常见的日志作对比。

1 日志解决方案对比

1.1 log4j

Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、数据库等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

Log4j有7种不同的log级别,按照等级从低到高依次为:TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF。如果配置为OFF级别,表示关闭log。

Log4j支持两种格式的配置文件:properties和xml。包含三个主要的组件:Logger、appender、Layout。

1.2 log4j2

Spring Boot1.4以及之后的版本已经不支持log4j,log4j也很久没有更新了,现在已经有很多其他的日志框架对Log4j进行了改良,比如说SLF4J、Logback等。而且Log4j2在各个方面都与Logback非常相似,那么为什么我们还需要Log4j2呢?

  • 插件式结构。Log4j 2支持插件式结构。我们可以根据自己的需要自行扩展Log4j2我们可以实现自己的appender、logger、filter。
  • 配置文件优化。在配置文件中可以引用属性,还可以直接替代或传递到组件。而且支持json格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。
  • Java 5的并发性。Log4j2利用Java 5中的并发特性支持,尽可能地执行最低层次的加锁。解决了在log4j 1.x中存留的死锁的问题。
  • 异步logger。Log4j2是基于LMAX Disruptor库的。在多线程的场景下,和已有的日志框架相比,异步的logger拥有10倍左右的效率提升。
1.3 logback

Logback,一个“可靠、通用、快速而又灵活的Java日志框架”。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。

1) logback-core: Joran, Status, context, pattern parsing
2)logback-classic: developer logging
3) logback-access: The log generated when a user accesses a web-page on a web server. Integrates seamlessly with Jetty and Tomcat.

Logback相对于log4j的优势

1)logback比log4j要快大约10倍,而且消耗更少的内存。
2)logback-classic模块直接实现了SLF4J的接口,所以我们迁移到logback几乎是零开销的。
3)logback不仅支持xml格式的配置文件,还支持groovy格式的配置文件。相比之下,Groovy风格的配置文件更加直观,简洁。
4) logback-classic能够检测到配置文件的更新,并且自动重新加载配置文件。
5)logback能够优雅的从I/O异常中恢复,从而我们不用重新启动应用程序来恢复logger。
6)logback能够根据配置文件中设置的上限值,自动删除旧的日志文件。
7)logback能够自动压缩日志文件。
8)logback能够在配置文件中加入条件判断(if-then-else)。可以避免不同的开发环境(dev、test、uat…)的配置文件的重复。
9)logback带来更多的filter。
10)logback的stack trace中会包含详细的包信息。
11)logback-access和Jetty、Tomcat集成提供了功能强大的HTTP-access日志。
配置文件:需要在项目的src目录下建立一个logback.xml。
注:(1)logback首先会试着查找logback.groovy文件;
(2)当没有找到时,继续试着查找logback-test.xml文件;
(3)当没有找到时,继续试着查找logback.xml文件;
(4)如果仍然没有找到,则使用默认配置(打印到控制台)。

logback的一些配置介绍:

  • 控制台输出
    在Spring Boot中默认配置了ERROR、WARN和INFO级别的日志输出到控制台。我们可以通过两种方式切换至DEBUG级别:
    在运行命令后加入–debug标志,如:$ java -jar test.jar --debug
    在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。

  • 多彩输出
    如果你的终端支持ANSI,设置彩色输出会让日志更具可读性。通过在application.properties中设置spring.output.ansi.enabled参数来支持。
    NEVER:禁用ANSI-colored输出(默认项)
    DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项)
    ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐使用

  • 文件输出
    Spring Boot默认配置只会输出到控制台,并不会记录到文件中,但是我们通常生产环境使用时都需要以文件方式记录。
    若要增加文件输出,需要在application.properties中配置logging.file或logging.path属性。
    logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
    logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=../logs
    日志文件会在10Mb大小的时候被截断,产生新的日志文件,默认级别为:ERROR、WARN、INFO

  • 级别控制
    在Spring Boot中只需要在application.properties中进行配置完成日志记录的级别控制。
    配置格式:logging.level.=LEVEL
    logging.level:日志级别控制前缀,为包名或Logger名
    LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
    举例:
    logging.level.com.test=DEBUG:com.test包下所有class以DEBUG级别输出
    logging.level.root=WARN:root日志以WARN级别输出

  • 自定义输出格式
    在Spring Boot中可以通过在application.properties配置如下参数控制输出格式:
    logging.pattern.console:定义输出到控制台的样式(不支持JDK Logger)
    logging.pattern.file:定义输出到文件的样式(不支持JDK Logger)

2 日志门面slf4j

slf4j,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,而是通过Facade Pattern提供一些Java logging API,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。作者创建SLF4J的目的是为了替代Jakarta Commons-Logging。

实际上,SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用那个具体的日志系统。类似于Apache Common-Logging,SLF4J是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在编译时静态绑定真正的Log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

3 方案选择

log4j已经很久没更新了,它的改良版log4j2与Logback都有相应的优化。而slf4j则是门面日志,它不是具体的日志解决方案,为此,常常与具体的实现方案使用,好处就是允许最终用户在部署其应用时使用其所希望的日志系统。那么Logback与log4j2具体选择哪一个呢?以下是我参考的一篇关于log4j2与logback性能测试的文章截图:

image.png

为此我选择了slf4j + log4j2来实现项目的日志系统。

4 具体实现

4.1 去除旧的日志依赖

在<dependencies></dependencies>中加入以下代码,以下主要是排除旧的log4j日志依赖和与门面日志关联的slf4j+log4j12依赖。

<exclusions>
  <exclusion>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
   </exclusion>

   <exclusion>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
   </exclusion>
<exclusions>

若以前的配置文件在web.xml中加载了的话需要注释掉:

image.png

4.2 添加新的日志解决方案依赖

pom.xml中添加我们的新日志解决方案依赖,若之前项目未加入slf4j的依赖的话需要我们加入相应的slf4j的核心依赖包

 <!--日志系统依赖start-->

        <!-- slf4j核心包-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

        <!--slf4j对应log4j2的中间件,即桥接,告诉slf4j使用log4j2-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.10.0</version>
        </dependency>

        <!--log4j2核心包-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.10.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.10.0</version>
        </dependency>


        <!--日志系统依赖end-->

5 配置log4j2

5.1 配置文件格式及读取优先级:

Log4j2 配置文件后缀要求为".xml"、".json"或者".jsn"。

系统选择配置文件的优先级为(从先到后):

1)classpath下的名为log4j2-test.json 或者log4j2-test.jsn的文件.
2)classpath下的名为log4j2-test.xml的文件.
3)classpath下名为log4j2.json 或者log4j2.jsn的文件.
4)classpath下名为log4j2.xml的文件.
5)如果classpath下没有相关配置文件,则使用默认日志系统.

配置log4j2.xml

在项目中的classpath下新建log4j2的配置文件log4j2.xml

<?xml version="1.0" encoding="utf-8" ?>
<configuration status="WARN">

    <Properties> <!-- 配置日志文件输出目录,此配置将日志输出到工程目录下的log4j2_logs文件夹 -->
        <Property name="LOG_HOME">
            log4j2_logs
        </Property>
    </Properties>

    <Appenders>
    <!--这个输出控制台的配置,即System.out -->
    <Console name="console_out_appender" target="SYSTEM_OUT">
        <!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) . -->
        <ThresholdFilter level="INFO" onMatch="ACCEPT"
                         onMismatch="DENY" />
        <!-- 输出日志的格式 -->
        <PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n" />
    </Console>
    <!-- 这个输出控制台的配置,这里输出error级别的信息到System.err,在eclipse控制台上看到的是红色文字 -->
    <Console name="console_err_appender" target="SYSTEM_ERR">
        <ThresholdFilter level="ERROR" onMatch="ACCEPT"
                         onMismatch="DENY" />
        <PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n" />
    </Console>


    <!-- TRACE级别日志 ; 设置日志格式并配置日志压缩格式,压缩文件独立放在一个文件夹内, 日期格式不能为冒号,否则无法生成,因为文件名不允许有冒号,此appender只输出trace级别的数据到trace.log -->
    <RollingFile name="trace_appender" immediateFlush="true"
                 fileName="${LOG_HOME}/trace.log" filePattern="${LOG_HOME}/trace/trace - %d{yyyy-MM-dd HH:mm:ss}.log.gz">
        <PatternLayout>
            <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
        </PatternLayout>
        <Policies>
            <!-- 每个日志文件最大2MB -->
            <SizeBasedTriggeringPolicy size="2MB" />
        </Policies>
        <Filters>
            <!-- 此Filter意思是,只输出TRACE级别的数据 DENY,日志将立即被抛弃不再经过其他过滤器; NEUTRAL,有序列表里的下个过滤器过接着处理日志;
                ACCEPT,日志会被立即处理,不再经过剩余过滤器。 -->
            <ThresholdFilter level="debug" onMatch="DENY"
                             onMismatch="NEUTRAL" />
            <ThresholdFilter level="trace" onMatch="ACCEPT"
                             onMismatch="DENY" />
        </Filters>
    </RollingFile>

    <!-- DEBUG级别日志 设置日志格式并配置日志压缩格式,压缩文件独立放在一个文件夹内, 日期格式不能为冒号,否则无法生成,因为文件名不允许有冒号,此appender只输出debug级别的数据到debug.log; -->
    <RollingFile name="debug_appender" immediateFlush="true"
                 fileName="${LOG_HOME}/debug.log" filePattern="${LOG_HOME}/debug/debug - %d{yyyy-MM-dd HH:mm:ss}.log.gz">
        <PatternLayout>
            <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
        </PatternLayout>
        <Policies><!-- 每个日志文件最大2MB ; -->
            <SizeBasedTriggeringPolicy size="2MB" />

            <!-- 如果启用此配置,则日志会按文件名生成新压缩文件, 即如果filePattern配置的日期格式为 %d{yyyy-MM-dd HH}
                ,则每小时生成一个压缩文件, 如果filePattern配置的日期格式为 %d{yyyy-MM-dd} ,则天生成一个压缩文件 -->
            <TimeBasedTriggeringPolicy interval="1"
                                       modulate="true" />

        </Policies>
        <Filters><!-- 此Filter意思是,只输出debug级别的数据 -->
            <ThresholdFilter level="info" onMatch="DENY"
                             onMismatch="NEUTRAL" />
            <ThresholdFilter level="debug" onMatch="ACCEPT"
                             onMismatch="DENY" />
        </Filters>
    </RollingFile>

    <!-- INFO级别日志 -->
    <RollingFile name="info_appender" immediateFlush="true"
                 fileName="${LOG_HOME}/info.log" filePattern="${LOG_HOME}/info/info - %d{yyyy-MM-dd HH:mm:ss}.log.gz">
        <PatternLayout>
            <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
        </PatternLayout>
        <Policies>
            <SizeBasedTriggeringPolicy size="2MB" />
        </Policies>
        <Filters>
            <ThresholdFilter level="warn" onMatch="DENY"
                             onMismatch="NEUTRAL" />
            <ThresholdFilter level="info" onMatch="ACCEPT"
                             onMismatch="DENY" />
        </Filters>
    </RollingFile>

    <!-- WARN级别日志 -->
    <RollingFile name="warn_appender" immediateFlush="true"
                 fileName="${LOG_HOME}/warn.log" filePattern="${LOG_HOME}/warn/warn - %d{yyyy-MM-dd HH:mm:ss}.log.gz">
        <PatternLayout>
            <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
        </PatternLayout>
        <Policies>
            <SizeBasedTriggeringPolicy size="2MB" />
        </Policies>
        <Filters>
            <ThresholdFilter level="error" onMatch="DENY"
                             onMismatch="NEUTRAL" />
            <ThresholdFilter level="warn" onMatch="ACCEPT"
                             onMismatch="DENY" />
        </Filters>
    </RollingFile>

    <!-- ERROR级别日志 -->
    <RollingFile name="error_appender" immediateFlush="true"
                 fileName="${LOG_HOME}/error.log" filePattern="${LOG_HOME}/error/error - %d{yyyy-MM-dd HH:mm:ss}.log.gz">
        <PatternLayout>
            <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
        </PatternLayout>
        <Policies>
            <SizeBasedTriggeringPolicy size="2MB" />
        </Policies>
        <Filters>
            <ThresholdFilter level="error" onMatch="ACCEPT"
                             onMismatch="DENY" />
        </Filters>
    </RollingFile>
</Appenders>

    <loggers>
        <!--使appender生效-->
        <!-- 配置日志的根节点 -->
        <!-- 定义logger,只有定义了logger并引入了appender,appender才会生效 -->
        <root level="trace">
            <appender-ref ref="console_out_appender" />
            <appender-ref ref="console_err_appender" />
            <appender-ref ref="trace_appender" />
            <appender-ref ref="debug_appender" />
            <appender-ref ref="info_appender" />
            <appender-ref ref="warn_appender" />
            <appender-ref ref="error_appender" />
        </root>

    </loggers>
</configuration>

6 测试日志输出

在我们需要使用日志的类中加入以下代码,导入slf4j相应的包:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

添加如下代码

private final static Logger logger =  LoggerFactory.getLogger(log4j2Test.class);

这是我使用测试类,若要在别的类使用,使用方法是一致的,只需要做上两步的操作即可:


image.png

运行结果图如下:


image.png
image.png

注意

在项目中,可能有的人导包并不是直接导入slf4j的包,而是指定的日志解决方案包,如下

import org.apache.log4j.Logger

这种情况下,我们是直接使用了指定的日志解决方案,并没用发挥我们日志们面的一个作用,这样的话我们下次换日志解决方案的时候,需要修改所有类中导入此包的地方,整个过程会比较麻烦。

为此,我们应该导入的是日志门面相应的包,这样我们项目更新日志解决方案的时候,只须修改依赖与配置文件即可,而不需要修改其它的代码。

参考

https://www.jianshu.com/p/191273d04d2d
https://segmentfault.com/a/1190000015568449

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