主流日志框架

2020-03-25  本文已影响0人  CJ21

一、日志门面

1.1 简单日志门面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提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

image.png

SLF4J本身并不输出日志,最大的特色是:它可以通过适配的方式挂接不同的日志系统,属于一个日志接口。
如果项目适配到log4j就使用log4j日志库进行输出;如果适配到logback就使用logback日志库进行输出;如果适配到log4j2就使用log4j2日志库进行输出。

这样最大的好处,就是在开发时面向SLF4J接口开发,项目后期希望从log4j换成log4j2的时候,只需要在项目pom.xml中进行桥接适配即可,不用修改具体需要打印日志的代码!

1.2 日志层级关系

下图描述了日志各个层级之间的关系。

蓝色的是接口层,墨绿的是适配层,蓝色的是实现层

如应用采用log4j作为日志框架时,其调用关系为

  1. 首先系统使用slf4j-api作为日志介入的接口;
  2. slf4j-log4j12是链接slf4j-api和log4j中间的适配器:它实现了slf4j-api中StaticLoggerBinder接口,从而使得在编译时绑定的是slf4j-log4j12的getSingleton()方法。
  3. log4j是具体的日志系统:通过slf4j-log4j12初始化Log4j,达到最终日志的输出。



如下图是主流日志框架使用到的部分包:

image.png

二、主流日志框架

2.1 Log4j

Log4j 是一种非常流行的日志框架,由Ceki Gülcü首创,之后将其开源贡献给 Apache 软件基金会。
Log4j 有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局)。这里可简单理解为日志类别、日志要输出的地方和日志以何种形式输出。
综合使用这三个组件可以轻松地记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。

2.1.1 依赖

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

注:在Log4j2依赖中也会有Log4j的依赖,所以可以在Log4j2日志框架中使用Log4j。

2.1.2 配置

配置文件
可以配置为log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <!-- 日志输出到控制台 -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <!-- 日志输出格式 -->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%p][%d{yyyy-MM-dd HH:mm:ss SSS}][%c]-[%m]%n"/>
        </layout>

        <!--过滤器设置输出的级别-->
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <!-- 设置日志输出的最小级别 -->
            <param name="levelMin" value="INFO"/>
            <!-- 设置日志输出的最大级别 -->
            <param name="levelMax" value="ERROR"/>
        </filter>
    </appender>


    <!-- 输出日志到文件 -->
    <appender name="fileAppender" class="org.apache.log4j.FileAppender">
        <!-- 输出文件全路径名-->
        <param name="File" value="fileAppender.log"/>
        <!--是否在已存在的文件追加写:默认时true,若为false则每次启动都会删除并重新新建文件-->
        <param name="Append" value="false"/>
        <param name="Threshold" value="INFO"/>
        <!--是否启用缓存,默认false-->
        <param name="BufferedIO" value="false"/>
        <!--缓存大小,依赖上一个参数(bufferedIO), 默认缓存大小8K  -->
        <param name="BufferSize" value="512"/>
        <!-- 日志输出格式 -->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%p][%d{yyyy-MM-dd HH:mm:ss SSS}][%c]-[%m]%n"/>
        </layout>
    </appender>


    <!-- 输出日志到文件,当文件大小达到一定阈值时,自动备份 -->
    <!-- FileAppender子类 -->
    <appender name="rollingAppender" class="org.apache.log4j.RollingFileAppender">
        <!-- 日志文件全路径名 -->
        <param name="File" value="RollingFileAppender.log"/>
        <!--是否在已存在的文件追加写:默认时true,若为false则每次启动都会删除并重新新建文件-->
        <param name="Append" value="true"/>
        <!-- 保存备份日志的最大个数,默认值是:1  -->
        <param name="MaxBackupIndex" value="10"/>
        <!-- 设置当日志文件达到此阈值的时候自动回滚,单位可以是KB,MB,GB,默认单位是KB,默认值是:10MB -->
        <param name="MaxFileSize" value="10KB"/>
        <!-- 设置日志输出的样式 -->`
        <layout class="org.apache.log4j.PatternLayout">
            <!-- 日志输出格式 -->
            <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5p] [method:%l]%n%m%n%n"/>
        </layout>
    </appender>


    <!-- 日志输出到文件,可以配置多久产生一个新的日志信息文件 -->
    <appender name="dailyRollingAppender" class="org.apache.log4j.DailyRollingFileAppender">
        <!-- 文件文件全路径名 -->
        <param name="File" value="dailyRollingAppender.log"/>
        <param name="Append" value="true"/>
        <!-- 设置日志备份频率,默认:为每天一个日志文件 -->
        <param name="DatePattern" value="'.'yyyy-MM-dd'.log'"/>

        <!--每分钟一个备份-->
        <!--<param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm'.log'" />-->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%p][%d{HH:mm:ss SSS}][%c]-[%m]%n"/>
        </layout>
    </appender>


    <!--
    1. 指定logger的设置,additivity是否遵循缺省的继承机制
    2. 当additivity="false"时,root中的配置就失灵了,不遵循缺省的继承机制
    3. 代码中使用Logger.getLogger("logTest")获得此输出器,且不会使用根输出器
    -->
    <logger name="logTest" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="dailyRollingAppender"/>
    </logger>


    <!-- 根logger的设置,若代码中未找到指定的logger,则会根据继承机制,使用根logger-->
    <root>
        <appender-ref ref="console"/>
        <appender-ref ref="fileAppender"/>
        <appender-ref ref="rollingAppender"/>
        <appender-ref ref="dailyRollingAppender"/>
    </root>

</log4j:configuration>

也可以在resources文件夹下创建log4j.properties配置文件

log4j.rootLogger=INFO,M,C,E
log4j.additivity.monitorLogger=false
 
# INFO级别文件输出配置
log4j.appender.M=org.apache.log4j.DailyRollingFileAppender
log4j.appender.M.File=/logs/info.log
log4j.appender.M.ImmediateFlush=false
log4j.appender.M.BufferedIO=true
log4j.appender.M.BufferSize=16384
log4j.appender.M.Append=true
log4j.appender.M.Threshold=INFO
log4j.appender.M.DatePattern='.'yyyy-MM-dd
log4j.appender.M.layout=org.apache.log4j.PatternLayout
log4j.appender.M.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p %l %m %n
 
# ERROR级别文件输出配置
log4j.appender.E=org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File=/logs/error.log
log4j.appender.E.ImmediateFlush=true
log4j.appender.E.Append=true
log4j.appender.E.Threshold=ERROR
log4j.appender.E.DatePattern='.'yyyy-MM-dd
log4j.appender.E.layout=org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p %l %m %n
 
# 控制台输出配置
log4j.appender.C=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.target=System.out
log4j.appender.C.Threshold=INFO
log4j.appender.C.layout=org.apache.log4j.PatternLayout
log4j.appender.C.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m %n

2.1.3 log4j使用

  1. 创建一个配置文件,可以是log4j.xml或者log4j.properties,将其放入项目根目录下。也可以直接在配置文件中进性配置。
  2. 在代码中使用
Logger logger = LoggerFactory.getLogger(LogPrintUtil.class);
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");

2.2 log4j2

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

  1. 插件式结构。Log4j 2支持插件式结构。我们可以根据自己的需要自行扩展Log4j 2. 我们可以实现自己的appender、logger、filter。
  2. 配置文件优化。在配置文件中可以引用属性,还可以直接替代或传递到组件。而且支持json格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。
  3. Java 5的并发性。Log4j 2利用Java 5中的并发特性支持,尽可能地执行最低层次的加锁。解决了在log4j 1.x中存留的死锁的问题。
  4. 异步logger。Log4j 2是基于LMAX Disruptor库的。在多线程的场景下,和已有的日志框架相比,异步的logger拥有10倍左右的效率提升。
  5. 异常处理:在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
  6. 性能提升, log4j2相较于log4j 1和logback都具有很明显的性能提升,后面会有官方测试的数据。
  7. 自动重载配置:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用——那对监控来说,是非常敏感的。
  8. 无垃圾机制:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。

官方建议一般程序员查看的日志改成异步方式,一些运营日志改成同步。日志异步输出的好处在于,使用单独的进程来执行日志打印的功能,可以提高日志执行效率,减少日志功能对正常业务的影响。异步日志在程序的classpath需要加载disruptor-3.0.0.jar或者更高的版本。

 <!-- log4j2异步日志需要加载disruptor-3.0.0.jar或者更高的版本 -->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.6</version>
        </dependency>

异步日志分为两种:
a.全异步模式
这种异步日志方式,不需要修改修改原理的配置文件,Logger仍然使用<root> and <logger>
只需要在主程序代码开头,加一句系统属性的代码:

System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");  

b.异步和非异步混合输出模式
在配置文件中Logger使用<asyncRoot> or <asyncLogger>

<loggers>  
     <AsyncLogger name="AsyncLogger" level="trace" includeLocation="true">  
        <appender-ref ref="Console" />  
        <appender-ref ref="debugLog" />  
        <appender-ref ref="errorLog" />  
    </AsyncLogger>  
 
    <asyncRoot level="trace" includeLocation="true">  
        <appender-ref ref="Console" />  
    </asyncRoot>   
</loggers>

2.2.1 log4j2依赖

<!--添加log4j2相关jar包-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.12.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>
<!-- log4j2异步日志需要加载disruptor-3.0.0.jar或者更高的版本,该依赖已经在log4j-core中 -->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>
<!--解决 Caused by: java.lang.AbstractMethodError: javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)V -->
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.11.0</version>
</dependency>

2.2.2 配置文件

log4j2.xml

<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出 -->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
<Configuration status="OFF" monitorInterval="300">
    <properties>
        <property name="LOG_HOME">./log4j2/</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
            <ThresholdFilter level="TRACE" onMatch="ACCEPT"
                onMismatch="DENY" />
            <!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) 优先级,即DEBUG,INFO,WARN,ERROR, 
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="ACCEPT"/> -->
            <PatternLayout
                pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
        <!-- 消息在粗粒度级别上突出强调应用程序的运行过程 -->
        <RollingRandomAccessFile name="InfoFile"
            fileName="${LOG_HOME}/info.log"
            filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <Filters>
                <ThresholdFilter level="warn" onMatch="DENY"
                    onMismatch="NEUTRAL" />
                <ThresholdFilter level="trace" onMatch="ACCEPT"
                    onMismatch="DENY" />
            </Filters>
            <PatternLayout
                pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingRandomAccessFile>
        <!-- 输出错误信息日志 -->
        <RollingRandomAccessFile name="ErrorFile"
            fileName="${LOG_HOME}/error.log"
            filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
            <Filters>
                <ThresholdFilter level="fatal" onMatch="DENY"
                    onMismatch="NEUTRAL" />
                <ThresholdFilter level="warn" onMatch="ACCEPT"
                    onMismatch="DENY" />
            </Filters>
            <PatternLayout
                pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingRandomAccessFile>
        <!-- 输出每个严重的错误事件将会导致应用程序的退出的日志. -->
        <RollingRandomAccessFile name="FatalFile"
            fileName="${LOG_HOME}/fatal.log"
            filePattern="${LOG_HOME}/$${date:yyyy-MM}/fatal-%d{yyyy-MM-dd}-%i.log">
            <Filters>
                <ThresholdFilter level="fatal" onMatch="ACCEPT"
                    onMismatch="DENY" />
            </Filters>
            <PatternLayout
                pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingRandomAccessFile>
        <!-- 这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
        <RollingFile name="RollingFile"
            fileName="${LOG_HOME}/web.log"
            filePattern="${LOG_HOME}/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout
                pattern="[%d{yyyy-MM-dd 'at' HH:mm:ss z}] [%-5p] %l - %m%n" />
            <SizeBasedTriggeringPolicy size="2MB" />
        </RollingFile>
    </Appenders>
 
    <Loggers>
        <Root level="TRACE"> <!-- 这里的TRACE控制总下面的各类日志的日志级别,凡是高于此级别的都会在各自的级别文件中生成 -->
            <AppenderRef ref="RollingFile" />
            <AppenderRef ref="Console" />
            <AppenderRef ref="InfoFile" />
            <AppenderRef ref="ErrorFile" />
            <AppenderRef ref="FatalFile" />
        </Root>
    </Loggers>
</Configuration>

也可以配置为log4j2.properties

status = warn

appender.console.type = Console
appender.console.name = LogToConsole
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n

#appender.file.type = File
#appender.file.name = LogToFile
#appender.file.fileName=logs/app.log
#appender.file.layout.type=PatternLayout
#appender.file.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n

# Rotate log file
appender.rolling.type = RollingFile
appender.rolling.name = LogToRollingFile
appender.rolling.fileName = logs/app.log
appender.rolling.filePattern = logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=10MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 10

# Log to console and rolling file
logger.app.name = com.mkyong
logger.app.level = debug
logger.app.additivity = false
logger.app.appenderRef.rolling.ref = LogToRollingFile
logger.app.appenderRef.console.ref = LogToConsole

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = LogToConsole

2.2.3 log4j2使用

  1. 创建一个配置文件log4j2.xml或log4j2.properties,将其放入项目根目录下。也可以直接在配置文件中进性配置。
  2. 在代码中使用
Logger logger = LoggerFactory.getLogger(LogPrintUtil.class);
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");

2.3 logback

Logback 也是用 java 编写一款非常热门的日志开源框架,由 log4j 创始人写的,性能比 log4j 要好。

logback 主要分为3个模块:

选择logback的理由:

  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日志。

2.3.1 依赖

<!--这个依赖直接包含了 logback-core 和 slf4j-api的依赖,以及log4j依赖-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.11</version>
</dependency>

<!-- 支持在xml中写判断标签,logback-core和logback-classic中包含了该依赖 -->
<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>2.7.8</version>
</dependency>

2.3.2 配置

Logback框架会按顺序读取在项目根目录下的logback.groovy,logback-test.xml和logback.xml配置文件。
可以通过在resource文件下添加logback.xml或logback-spring.xml文件

<?xml version="1.0" encoding="utf-8" ?>
<!--该日志将日志级别不同的log信息保存到不同的文件中 -->
<configuration>
    <!-- 工程名字 -->
    <contextName>Flink CDC</contextName>


    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <!-- 日志输出编码 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>


    <!-- 日志输出级别 -->
    <root level="ERROR">
        <appender-ref ref="console"/>
    </root>
</configuration>
<?xml version="1.0" encoding="utf-8" ?>
<!--该日志将日志级别不同的log信息保存到不同的文件中 -->
<configuration>

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <springProperty scope="context" name="springAppName" source="spring.application.name"/>

    <!-- 日志在工程中的输出位置 -->
    <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
    <property name="SERVICE_NAME" value="Smartcook"/>

    <!-- 工程名字 -->
    <contextName>${SERVICE_NAME}</contextName>

    <!-- 控制台的日志输出样式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="logback--%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <!-- 日志输出编码 -->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 日志输出为文件 -->
    <!--<appender name="FILE" class="ch.qos.logback.core.FileAppender">-->
    <!--&lt;!&ndash; $使用变量FILE_PATH的格式,类似Linux中使用的格式:${FILE_PATH} &ndash;&gt;-->
    <!--<file>logs/file.log</file>-->
    <!--<encoder>-->
    <!--&lt;!&ndash; 指定输出格式 &ndash;&gt;-->
    <!--<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{0} -%msg%n</pattern>-->
    <!--</encoder>-->
    <!--</appender>-->

    <!-- 日志输出为文件,且每天回滚为新文件,文件保留30天 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">           
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">             
            <fileNamePattern>logs/logFile.%d{yyyy-MM-dd}.log</fileNamePattern>            
            <maxHistory>30</maxHistory> 
        </rollingPolicy>

        <!--日志文件最大的大小 -->
        <triggeringPolicy
                class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
          
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                %logger{50} - %msg %n
            </pattern>
        </encoder>
    </appender>


    <!--为logstash输出的Appender -->
    <!--<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>192.168.32.128:5044</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <jsonFactoryDecorator class="net.logstash.logback.decorate.CharacterEscapesJsonFactoryDecorator">
                <escape>
                    <targetCharacterCode>10</targetCharacterCode>
                    <escapeSequence>\u2028</escapeSequence>
                </escape>
            </jsonFactoryDecorator>
            <providers>
                <pattern>
                    <pattern>
                        {
                        "timestamp":"%date{ISO8601}",
                        "user":"test",
                        "message":"[%d{yyyy-MM-dd HH:mm:ss.SSS}][%p][%t][%L{80}|%L]%m"}%n
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
        <keepAliveDuration>5 minutes</keepAliveDuration>
    </appender>-->

    <!--<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">-->
        <!--<destination>192.168.32.128:5044</destination>-->
        <!--<encoder class="net.logstash.logback.encoder.LogstashEncoder" />-->
        <!--<keepAliveDuration>5 minutes</keepAliveDuration>-->
    <!--</appender>-->

    <!-- 为logstash输出的JSON格式的Appender -->
    <appender name="logstash"
              class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>192.168.32.128:5044</destination>
        <!-- 日志输出编码 -->
        <encoder
                class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "severity": "%level",
                        "service": "${springAppName:-}",
                        "trace": "%X{X-B3-TraceId:-}",
                        "span": "%X{X-B3-SpanId:-}",
                        "exportable": "%X{X-Span-Export:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "rest": "%message"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

    <!-- 发送给redis -->
    <appender name="LOGSTASH" class="com.cwbase.logback.RedisAppender">
        <source>mySource</source>
        <sourcePath>mySourcePath</sourcePath>
        <type>appName</type>
        <tags>dev</tags>
        <host>116.62.148.11</host>
        <port>6380</port>
        <key>logstash</key>
    </appender>

    <!-- 异步发送给redis -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="LOGSTASH" />
    </appender>
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="logstash"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ASYNC" />
    </root>
</configuration>

2.3.3 使用

  1. Logback框架会先读取项目根目录下的logback.groovy文件,没读到则读取logback-test.xml文件,没读到则读取logback.xml配置文件,如果还是没有读到,则使用默认配置(打印到控制台)。
    因此可以在根目录下创建如上所述的三个文件。
  2. 在代码中使用
Logger logger = LoggerFactory.getLogger(LogPrintUtil.class);
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");

除了配置文件控制日志打印级别,也可以在运行命令后加入–debug标志
java -jar test.jar --debug



注:
logback是Spring Boot默认的日志系统,如果是springboot项目,那么也可以采用一下两种方式对Springboot日志框架Logback进行配置。

  1. 可以直接在springboot.yml文件中添加配置。
#修改日志级别和日志输出位置。
logging:
  level:
    # root日志以INFO级别输出
    root: info
    # 此包下所有class以DEBUG级别输出
    com.example.log_demo.log1: DEBUG
  file: logs/${spring.application.name}.log
  path: ./logs

需要注意几点:

  1. 这里若不配置具体的包的日志级别,日志文件信息将为空 ;

  2. 若只配置logging.path,那么将会在E:\logs文件夹生成一个日志文件为spring.log ;

  3. 若只配置logging.file,那将会在项目的当前路径下生成一个demo.log日志文件 ;

  4. logging.path和logging.file同时配置时,不会进行叠加;

  5. logging.path和logging.file的value都可以是相对路径或者绝对路径。

  6. 创建 logback-spring.xml 文件放到项目根目录下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml" />
    <logger name="org.springframework.web" level="INFO"/>
    <logger name="org.springboot.sample" level="TRACE" />
 
    <springProfile name="dev">
        <logger name="org.springboot.sample" level="DEBUG" />
    </springProfile>
 
    <springProfile name="staging">
        <logger name="org.springboot.sample" level="INFO" />
    </springProfile>
</configuration>

logback-spring.xml文件相比较logback.xml文件,可以使用<springProfile>这个标签

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
    <!-- scan:当scan为true时,配置文件如果发生变化,会被重新加载,默认为true -->
    <!-- scanPeriod:监测配置文件是否修改的时间间隔,单位默认ms,默认间隔为1分钟 -->
    <!-- debug:当debug为true时,会打印logback内部日志,默认为false -->

    <!-- 工程名字 -->
    <contextName>${SERVICE_NAME}</contextName>

    <!-- property用于定义变量,通过${}来调用已定义的变量 -->
    <!-- 日志在工程中的输出位置 -->
    <property name="log.path" value="C:/Users/Administrator/Desktop/log"/>
    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <!-- 定义日志输出样式 -->
    <property name="CONSOLE_LOG_PATTERN" value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 此处设置了INFO级别,其他位置的配置不会生效 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <!-- 日志输出样式和编码 -->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!-- 时间滚动输出日志,level为DEBUG -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/log_debug.log</file>           
          
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <!-- 日志滚动策略,按日期和大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">             
            <!-- 每天日志归档路径及格式 -->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>            
            <!-- 文件查过100M回滚为新文件 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 日志保留天数 -->
            <maxHistory>30</maxHistory> 
        </rollingPolicy>

        <!-- 日志记录的级别 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出日志,level为INFO -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/log_info.log</file>           
          
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <!-- 日志滚动策略,按日期和大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">             
            <!-- 每天日志归档路径及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>            
            <!-- 文件查过100M回滚为新文件 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 日志保留天数 -->
            <maxHistory>30</maxHistory> 
        </rollingPolicy>

        <!-- 日志记录的级别 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出日志,level为WARN -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/log_warn.log</file>           
          
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <!-- 日志滚动策略,按日期和大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">             
            <!-- 每天日志归档路径及格式 -->
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>            
            <!-- 文件查过100M回滚为新文件 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 日志保留天数 -->
            <maxHistory>30</maxHistory> 
        </rollingPolicy>

        <!-- 日志记录的级别 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出日志,level为ERROR -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/log_error.log</file>           
          
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <!-- 日志滚动策略,按日期和大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">             
            <!-- 每天日志归档路径及格式 -->
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>            
            <!-- 文件查过100M回滚为新文件 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 日志保留天数 -->
            <maxHistory>30</maxHistory> 
        </rollingPolicy>

        <!-- 日志记录的级别 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        使用mybatis时,sql语句是debug下才打印,而此处只配置了INFO级别,所以想查看sql语句,有以下两种操作:
        第一种把<root level="INFO">改为<root level="DEBUG">就会打印sql,不过会造成日志记录过大
        第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常的INFO级别;
     -->

    <!-- 对应spring.profiles.active属性,如果是dev环境,则生效 -->
    <springProfile name="dev">
        <!--
            logger用来设置某个包或某个类的日志打印级别。
            name:来指定包或者类
            level:用来设置打印级别,如果未设置,logger会继承上级的级别
            可以输出项目中的debug日志,包括mybatis的sql日志
        -->
        <logger name="com.cj" level="DEBUG"></logger>

        <!-- root节点用来指定最基础的日志输出级别,只有一个level属性;level用来设置打印级别,默认是DEBUG,可以包含零个或多个appender元素 -->
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="DEBUG_FILE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>

    <!-- 对应spring.profiles.active属性,如果是pro环境,则生效 -->
    <springProfile name="pro">

        <!-- root节点用来指定最基础的日志输出级别,只有一个level属性;level用来设置打印级别,默认是DEBUG,可以包含零个或多个appender元素 -->
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="DEBUG_FILE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>

</configuration>

2.4 java.util.logging.Logger

java.util.logging.Logger(JUL),JDK自带的日志系统,从JDK1.4就有了。关键元素包括:

整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于LogManager,也包括配置文件的读取,LogManager与logger是1对多关系,整个JVM运行时只有一个LogManager,且所有的logger均在LogManager中。
logger与handler是多对多关系,logger在进行日志输出的时候会调用所有的hanlder进行日志的处理。
handler与formatter是一对一关系,一个handler有一个formatter进行日志的格式化处理。
logger与level是一对一关系,hanlder与level也是一对一关系 。
JDK默认的logging配置文件为:$JAVA_HOME/jre/lib/logging.properties,可以使用系统属性java.util.logging.config.file指定相应的配置文件对默认的配置文件进行覆盖。

使用:

        java.util.logging.Logger logger = java.util.logging.Logger.getLogger(this.getClass().getName());
        logger.fine("fine");
        logger.info("info");
        logger.warning("warning");

2.5 Apache Commons Logging

Apache Commons Logging,之前叫 Jakarta Commons Logging(JCL)。
提供的是一个日志(Log)接口(interface),同时兼顾轻量级和不依赖于具体的日志实现工具。它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。用户被假定已熟悉某种日志实现工具的更高级别的细节。JCL提供的接口,对其它一些日志工具,包括Log4J, Avalon LogKit, and JDK 1.4+等,进行了简单的包装,此接口更接近于Log4J和LogKit的实现。
common-logging是apache提供的一个通用的日志接口。用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的logging, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。当然,common-logging内部有一个Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j来使用。使用它的好处就是,代码依赖是common-logging而非log4j, 避免了和具体的日志方案直接耦合,在有必要时,可以更改日志实现的第三方库。

使用common-logging的常见代码:

import org.apache.commons.logging.Log;  
import org.apache.commons.logging.LogFactory;  
 
public class A {  
    private static Log logger = LogFactory.getLog(this.getClass());  
}  

slf4j 与 common-logging 比较 :

注:配置文件项解释

Appender:日志输出器,配置日志的输出级别、输出位置等,包括以下几类:

日志级别
一般日志级别包括:ALL,DEBUG, INFO, WARN, ERROR,FATAL,OFF;
Log4J推荐使用:DEBUG, INFO, WARN, ERROR。

输出级别的种类:

配置日志信息的格式

log4j.appender.[appenderName].layout = [fully.qualified.name.of.layout.class]
log4j.appender.[appenderName].layout.option1 = value1
…
log4j.appender.[appenderName].layout.option = valueN

日志信息格式中几个符号所代表的含义

-X号: X信息输出时左对齐;  
%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,  
%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921  
%r: 输出自应用启动到输出该log信息耗费的毫秒数  
%c: 输出日志信息所属的类目,通常就是所在类的全名  
%t: 输出产生该日志事件的线程名  
%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main (TestLog4.java:10)  
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。  
%%: 输出一个"%"字符  
%F: 输出日志消息产生时所在的文件名称  
%L: 输出代码中的行号  
%m: 输出代码中指定的消息,产生的日志具体信息  
%n: 输出一个回车换行符,Windows平台为"/r/n",Unix平台为"/n"输出日志信息换行  
可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:  
1)%20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。  
2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。  
3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。  
4)%20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边较远输出的字符截掉。

三、日志转换

如果要更换一个项目的日志框架,如使用Logback框架替换Log4j框架,那么需要完成如下事项

  1. 首先保证项目中的日志框架代码都是面向SLF4J接口开发的;如hadoop代码中直接使用了log4j依赖的类,那么就不能替换为其他日志框架了;
  2. 排除所有的log4j相关的适配层和实现层的jar包,如slf4j-log4j12和log4j包。
  3. 引入logback包,只需要引入logback-classic一个jar包即可。



注:Springboot框架中的Spring-boot-starter-logging会自动引入各个框架(jul、jcl、log4j)的slf4j的适配层,最终用logback实现,所以如果其他框架有引入不同的日志框架,需要进行排除,否则会有冲突。

image.png

四、总结

slf4j跟commons-logging类似,是各种日志实现的通用入口,log4j、log4j2、logback、slf4j-simple和java.util.logging是比较常见的日志实现系统,目前应用比较广泛的是Log4j和logback,而logback作为后起之秀,以替代log4j为目的,整体性能比log4j较佳,log4j的升级版log4j2也是有诸多亮点,用户可以根据项目需求和个人习惯,选择合适的日志实现。

上一篇 下一篇

猜你喜欢

热点阅读