Springboot 添加Logback日志

日志类库

Java日志库是最能体现Java库在进化中的渊源关系的,在理解时重点理解日志框架本身和日志门面,以及比较好的实践等。
要关注其历史渊源和设计(比如桥接),而具体在使用时查询接口即可,否则会陷入JUL(JavaUtil Log),JCL(Commons Logging),Log4j,SLF4J, Logback,Log4j2傻傻分不清楚的境地。

1.1 日志系统

1.1.1 JUL(java.util.logging)

JDK1.4开始,通过java.util.logging提供的日志功能。虽然是官方自带的log lib,JUL的使用不广泛主要原因是:

  • JUL从JDK1.4才开始加入,当时各种第三方log lib已经被广泛使用了

  • JUL早期存在性能问题,到JDK1.5上有了不错的进步,但是现在和Logbak/Log4j2相比还是有所不如

  • JUL的功能不如Logback/Log4j2等完善,比如Output Handler就没有Logback/Log4j2的丰富,有时候需要自己来继承定制,又比如默认没有从ClassPath里加载配置文件的功能

1.1.2 Log4j

Log4j 是 apache 的一个开源项目,创始人 Ceki Gulcu,Log4j是高度可配置的,并可通过在运行时的外部文件配置。它根据记录的优先级别,并提供机制,以指示记录信息到许多的目的地,诸如:数据库,文件,控制台,UNIX 系统日志等。

Log4j中主要有三个主要组成部分:

  • loggers:负责捕获记录信息

  • appenders:负责发布日志信息

  • layouts:负责格式化不同风格的日志信息

官网地址:http://logging.apache.org/log4j/2.x/

Log4j的短板在于性能,在Logback和Log4j2出来之后,Log4j的使用也减少了。

1.1.3 Logback

Logback是由Log4j创始人Ceki Gulcu设计的又一个开源日志组件,是作为Log4j的继承者来开发的,提供了性能更好的实现,异步logger,Filter等更多的特性。

Logback当前分成三个模块:

  • logback-core:是其他两个模块的基础模块

  • logback-claassic:是log4j的一个改良版本,完整实现SLF4J API可以很方便的更换成其他日志系统,如Log4j 或JDK1.4 logging

  • logbak-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

官网地址:http://logback.qos.ch/

1.1.4 Log4j2

维护Log4j的人为了性能又搞出了Log4j2.

Log4j2和Log4j1.x并不兼容,设计上很大程度上模仿了SLF4J/Logback,性能上也获得很大的提升

Log4j2也做了 Facade/Implementation分离的设计,分成了log4j-api 和 log4j-core.

官网地址:http://logging.apache.org/log4j/2.x/

1.1.5 Log4j vs Logback vs Log4j2

性能上Log4J2要强,但从生态上Logback+SLF4J优先

logback和log4j2都宣称自己是log4j的后代,一个是出于同一个作者,另一个则是在名字上根正苗红。

比较一下Log4j2和logback

  • log4j2比logback更新:log4j2的GA版在2014年底才推出,比logback晚了好几年,这期间log4j2确实吸收了slf4j和logback的一些优点(比如日志模板),同时应用了不少的新技术

  • 由于采用了更先进的锁机制和LMAX Disruptor库,log4j2的性能优于logback,特别是在多线程环境下和使用异步日志的环境下

  • 二者都支持Filter(应该说是log4j2借鉴了logback的Filter),能够实现灵活的日志记录规则(例如仅对一部分用户记录debug级别的日志)

  • 二者都支持对配置文件的动态更新二者都能够适配slf4j,logback与slf4j的适配应该会更好一些,毕竟省掉了一层适配库

  • logback能够自动压缩/删除旧日志

  • logback提供了对日志的HTTP访问功能

  • log4j2实现了“无垃圾”和“低垃圾”模式。简单地说,log4j2在记录日志时,能够重用对象(如String等),尽可能避免实例化新的临时对象,减少因日志记录产生的垃圾对象,减少垃圾回收带来的性能下降

  • log4j2和logback各有长处,总体来说,如果对性能要求比较高的话,log4j2相对还是较优的选择。

性能对比

log4j2与logback性能对比的benchmark,这份benchmark是Apache Logging出的,仅供参考。

  1. 同步写文件日志的benchmark:

  2. 异步写日志的benchmark

当然,这些benchmark都是在日志Pattern中不包含Location信息(如日志代码行号 ,调用者信息,Class名/源码文件名等)时测定的

1.2 日志门面

1.2.1 common-logging

common-logging 是apache的一个开源项目。也称Jakarta Commons Logging(JCL)

common-logging的功能是提供日志功能的API接口,本身并不提供日志的具体实现(除了内部有一个Simple logger的简单实现),而是在运行时动态的绑定日志组件来实现(如log4j、java.util.logging)。

官网地址:https://commons.apache.org/proper/commons-logging/

1.2.2 Sl4j

全称为 Simple Logging Facade for java 即Java简单日志门面

类似于Common-Logging, Slf4j是对不同日志框架提供的一个API封装,可以在部署的时候不修改任何撇脂即可介入一种日志实现方案。但是
Slf4j在编译时静态绑定真正的Log库,使用Slf4j时,如果需要使用某一种日志实现,那么你必须要选择正确的Slf4j的jar包的集合

官网地址: http://www.slf4j.org/

1.2.3 common-logging VS slf4j

slf4j类似与Apache Common-Logging,但是,它在编译时静态绑定真正的日志哭

slf4j一大亮点是:提供了更方便的日志记录方式:
不需要使用Logger.isDebugEnabled()来解决日志因为字符拼接产生的性能问题,Slf4j的方式是使用{}作为字符串替换符,形式如下:

1
logger.debug("id:{}, name:{}", id, name);

添加Logback日志

2.1 配置时考虑点

  1. 支持日志路径,日志level等配置
  2. 日志控制配置通过application.yml下发
  3. 按天生成日志,当天的日志> 50M 回滚
  4. 最多保存10天日志
  5. 生成的日志中Pattern自定义
  6. Pattern中添加用户自定义的MDC字段,比如用户信息、request信息。这种方式可以通过AOP切面控制,在MDC中添加requestID,在spring-logback.xml中配置Pattern
  7. 根据不同的运行环境设置Profile -dev,test,product
  8. 对控制台,Err和全量日志分别配置

2.2 范例

  1. application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    logging:
    level:
    root: debug
    path: C:/data/logs/springboot-logback-demo
    server:
    port: 8080
    spring:
    application:
    name: springboot-logback-demo
    debug: false
  2. spring-logback.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>

    <!-- 日志根目录-->
    <springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="/data/logs/springboot-logback-demo"/>

    <!-- 日志级别 -->
    <springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="DEBUG"/>

    <!-- 标识这个"STDOUT" 将会添加到这个logger -->
    <springProperty scope="context" name="STDOUT" source="log.stdout" defaultValue="STDOUT"/>

    <!-- 日志文件名称-->
    <property name="LOG_PREFIX" value="spring-boot-logback" />

    <!-- 日志文件编码-->
    <property name="LOG_CHARSET" value="UTF-8" />

    <!-- 日志文件路径+日期-->
    <property name="LOG_DIR" value="${LOG_HOME}/%d{yyyyMMdd}" />

    <!--对日志进行格式化-->
    <property name="LOG_MSG" value="- | [%X{requestUUID}] | [%d{yyyyMMdd HH:mm:ss.SSS}] | [%level] | [${HOSTNAME}] | [%thread] | [%logger{36}] | --> %msg|%n "/>

    <!--文件大小,默认10MB-->
    <property name="MAX_FILE_SIZE" value="50MB" />

    <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
    <property name="MAX_HISTORY" value="10"/>

    <!--输出到控制台-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 输出的日志内容格式化-->
    <layout class="ch.qos.logback.classic.PatternLayout">
    <pattern>${LOG_MSG}</pattern>
    </layout>
    </appender>

    <!--输出到文件-->
    <appender name="0" class="ch.qos.logback.core.rolling.RollingFileAppender">
    </appender>

    <!-- 定义 ALL 日志的输出方式:-->
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--日志文件路径,日志文件名称-->
    <File>${LOG_HOME}/all_${LOG_PREFIX}.log</File>

    <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

    <!--日志文件路径,新的 ALL 日志文件名称,“ i ” 是个变量 -->
    <FileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}%i.log</FileNamePattern>

    <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
    <MaxHistory>${MAX_HISTORY}</MaxHistory>

    <!--当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB-->
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>

    </rollingPolicy>

    <!-- 输出的日志内容格式化-->
    <layout class="ch.qos.logback.classic.PatternLayout">
    <pattern>${LOG_MSG}</pattern>
    </layout>
    </appender>

    <!-- 定义 ERROR 日志的输出方式:-->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 下面为配置只输出error级别的日志 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <OnMismatch>DENY</OnMismatch>
    <OnMatch>ACCEPT</OnMatch>
    </filter>
    <!--日志文件路径,日志文件名称-->
    <File>${LOG_HOME}/err_${LOG_PREFIX}.log</File>

    <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

    <!--日志文件路径,新的 ERR 日志文件名称,“ i ” 是个变量 -->
    <FileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}%i.log</FileNamePattern>

    <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
    <MaxHistory>${MAX_HISTORY}</MaxHistory>

    <!--当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB-->
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>

    <!-- 输出的日志内容格式化-->
    <layout class="ch.qos.logback.classic.PatternLayout">
    <Pattern>${LOG_MSG}</Pattern>
    </layout>
    </appender>

    <!-- additivity 设为false,则logger内容不附加至root ,配置以配置包下的所有类的日志的打印,级别是 ERROR-->
    <logger name="org.springframework" level="ERROR" />
    <logger name="org.apache.commons" level="ERROR" />
    <logger name="org.apache.zookeeper" level="ERROR" />
    <logger name="com.alibaba.dubbo.monitor" level="ERROR"/>
    <logger name="com.alibaba.dubbo.remoting" level="ERROR" />

    <!-- ${LOG_ROOT_LEVEL} 日志级别 -->
    <root level="${LOG_ROOT_LEVEL}">

    <!-- 标识这个"${STDOUT}"将会添加到这个logger -->
    <appender-ref ref="${STDOUT}"/>

    <!-- FILE_ALL 日志输出添加到 logger -->
    <appender-ref ref="FILE_ALL"/>

    <!-- FILE_ERROR 日志输出添加到 logger -->
    <appender-ref ref="FILE_ERROR"/>
    </root>

    </configuration>