开源轮子 - Logback 和 Slf4j

发布于:2024-12-22 ⋅ 阅读:(10) ⋅ 点赞:(0)

spring boot内置:Logback

一:Logback强在哪?

  1. 非常自然地实现了 SLF4J,不需要像 Log4j 和 JUL 那样加一个适配层

在这里插入图片描述

  1. 是Spring Boot默认的[一旦某款工具库成为了默认选项,那就说明这款工具已经超过了其他竞品]
  2. 支持自动重新加载配置文件,不需要另外创建扫描线程来监视
  3. 性能大大的提升

logback手册:https://github.com/itwanger/logback-chinese-manual

二:简单使用

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

Maven 会自动导入另外两个依赖:

在这里插入图片描述

package org.example.open_source.log;

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

/**
 * <p>
 * 功能描述:logback demo test
 * </p>
 *
 * @author cui haida
 * @date 2024/04/05/21:02
 */
public class LogbackDemo {
    static Logger logger = LoggerFactory.getLogger(LogbackDemo.class);

    public static void main(String[] args) {
        logger.info("info");
        logger.debug("debug");
        logger.error("error");
        logger.warn("warn");
    }
}

在这里插入图片描述

Logger 和 LoggerFactory 都来自 SLF4J,所以如果项目是从 Log4j + SLF4J 切换到 Logback 的话,此时的代码是零改动的。

在没有配置文件的情况下,一切都是默认的,Logback 的日志信息会输出到控制台。

可以通过 StatusPrinter 来打印 Logback 的内部信息:

LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);

在这里插入图片描述

一般来说,我们会在本地环境中配置 logback-test.xml,在生产环境下配置 logback.xml。

<configuration debug="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder将日志变成字节数组并转到输出流 -->
        <encoder>
            <!-- pattern指定格式,各个参数的意义见下图 -->
            <pattern>%d{HH:mm:ss.SSS} %relative [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

Logback 的配置文件非常灵活

最基本的结构为 <configuration> 元素,包含 0 或多个 <appender> 元素,其后跟 0 或多个 <logger> 元素,其后再跟最多只能存在一个的 <root> 元素。

appender

在这里插入图片描述

root

只支持一个属性——level,值可以为:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF。

appender-ref 用来指定具体的 appender

查看内部状态信息

可以在代码中通过 StatusPrinter 来打印 Logback 内部状态信息

也可以通过在 configuration 上开启 debug 来打印内部状态信息

自动重载配置

之前提到 Logback 很强的一个功能就是支持自动重载配置,那想要启用这个功能也非常简单,只需要在 configuration 元素上添加 scan=true 即可。

<configuration scan="true">
    //...
</configuration>

默认情况下,扫描的时间间隔是一分钟一次。

如果想要调整时间间隔,可以通过 scanPeriod 属性进行调整,单位可以是毫秒(milliseconds)、秒(seconds)、分钟(minutes)或者小时(hours)。

下面这个示例指定的时间间隔是 30 秒:

<configuration scan="true" scanPeriod="30 seconds">
   ...
</configuration>

注意:如果指定了时间间隔,没有指定时间单位,默认的时间单位为毫秒。

当设置 scan=true 后,Logback 会起一个 ReconfigureOnChangeTask 的任务来监视配置文件的变化。

三:把 log4j 转成 logback

如果你的项目以前用的 Log4j,那么可以通过下面这个网址把 log4j.properties 转成 logback-test.xml:

http://logback.qos.ch/translator/

在这里插入图片描述

将之前的那个log4j.peoperties拿到之后,将会转成下面的xml

<?xml version="1.0" encoding="UTF-8"?>

<!--  Built in 2024-04-05 01:26 by logback-translator            -->
<!--                                                             -->
<!--  For sponsoring SLF4J/logback/reload4j projects see         -->
<!--    https://github.com/sponsors/qos-ch                       -->
<!--                                                             -->
<!--  Contact sales(at)qos.ch about licensing this tool or       -->
<!--  custom development.-->
<configuration>
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <Target>System.out</Target>
        <encoder>
            <!-- 问题1:Logback 中没有 %l -->
            <pattern>[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n</pattern>
        </encoder>
    </appender>
    <!-- 问题2,滚动策略? -->
    <appender name="D" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--See http://logback.qos.ch/manual/appenders.html#RollingFileAppender-->
        <!--and http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy-->
        <!--for further documentation-->
        <Append>true</Append>
        <File>debug.log</File>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    <appender name="E" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--See http://logback.qos.ch/manual/appenders.html#RollingFileAppender-->
        <!--and http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy-->
        <!--for further documentation-->
        <Append>true</Append>
        <File>error.log</File>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
    <root level="debug">
        <appender-ref ref="stdout"/>
        <appender-ref ref="D"/>
        <appender-ref ref="E"/>
    </root>
</configuration>

来看下面这个 RollingFileAppender 配置:

<appender name="D" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>debug.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- 按天滚动 -->
        <fileNamePattern>debug.%d{yyyy-MM-dd}.log</fileNamePattern>
        <!-- 保存 30 天的历史记录,最大大小为 30GB -->
        <maxHistory>30</maxHistory>
        <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
        <pattern>%relative [%thread] %level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

基于按天滚动的文件策略,最多保留 30 天,最大大小为 30G。

SizeAndTimeBasedRollingPolicy TimeBasedRollingPolicy 多了一个日志文件大小设定的属性:maxFileSize,其他完全一样。

基于我们对 RollingPolicy 的了解,可以把 logback-test.xml 的内容调整为以下内容:

<configuration>
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <Target>System.out</Target>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="D" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <Append>true</Append>
        <File>debug.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天轮转 -->
            <fileNamePattern>debug.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保存 30 天的历史记录,最大大小为 30GB -->
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="E" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>error.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天轮转 -->
            <fileNamePattern>error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保存 30 天的历史记录,最大大小为 30GB -->
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
    <root level="debug">
        <appender-ref ref="stdout"/>
        <appender-ref ref="D"/>
        <appender-ref ref="E"/>
    </root>
</configuration>

四:日志门面SLF4J

1:什么是SLF4J

SLF4J 是 Simple Logging Facade for Java 的缩写(for≈4),也就是简易的日志门面

以外观模式(Facade pattern,一种设计模式,为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用)实现

支持 java.util.logging、Log4J 和 Logback。

SLF4J 的作者就是 Log4J 和 Logback 的作者

在这里插入图片描述

在这里插入图片描述

2:SLF4J 解决了什么痛点

春秋战国的时候,每个国家都有自己的货币,用别国的货币也不合适,对吧?那在发生贸易的时候就比较麻烦了,货币不统一,就没法直接交易,因为货币可能不等价。

那秦始皇统一六国后,就推出了新的货币政策,全国都用一种货币,那之前的问题就解决掉了。

你看,同样的道理,日志系统有 JUL、JCL,Ceki Gulcu 自己又写了 2 种,Log4j 和 Logback

各有各的优缺点,再加上使用者千千万,萝卜白菜各有所爱,这就导致不同的应用可能会用不同的日志系统。

假设我们正在开发一套系统,打算用 SLF4J 作为门面,Log4j 作为日志系统

我们在项目中使用了 A 框架,而 A 框架的门面是 JCL,日志系统是 JUL,那就相等于要维护两套日志系统,对吧?

在这里插入图片描述

这就难受了!

Ceki Gulcu 想到了这个问题,并且帮我们解决了!来看 SLF4J 官网给出的解决方案。

在这里插入图片描述

  • 使用 jcl-over-slf4j.jar 替换 commons-logging.jar
  • 引入 jul-to-slf4j.jar

3:SLF4J 比 Log4J 强在哪

SLF4J 除了解决掉以上的痛点,帮助我们的应用程序独立于任何特定的日志系统,还有一个非常牛逼的功能,那就是 SLF4J 在打印日志的时候使用了占位符 {}

它有点类似于 String 类的 format() 方法(使用 %s 等填充参数),但更加便捷,这在很大程度上提高了程序的性能。

众所周知,字符串是不可变的,字符串拼接会创建很多不必要的字符串对象,极大的消耗了内存空间。但 Log4J 在打印带参数的日志时,只能使用字符串拼接的方式:

String name = "张三";
int age = 18;
logger.debug(name + ",年纪:" + age + ",hello world");

非常笨重,但加入了 SLF4J 后,这个问题迎刃而解。我们来看一下在 Log4j 项目中加入 SLF4J 的详细的步骤。

第一步,把 log4j 的依赖替换为 slf4j-log4j12(Maven 会自动引入 slf4j-api.jar 和 log4j.jar):

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

第二步,在 resources 目录下创建 log4j.properties 文件

# config rootLogger
log4j.rootLogger = debug,stdout,D,E

# config logger stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

# config logger debug
# if the log level is debug, the log will be output to debug.log
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = debug.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

# config logger error
# if the log level is error, the log will be output to error.log
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

第三步,新建测试类:

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

/**
 * @author cuihaida
 */
public class Log4jSLF4JDemo {
    private static final Logger logger = LoggerFactory.getLogger(Log4jSLF4JDemo.class);

    public static void main(String[] args) {
        logger.debug("{},hello world","张三");
    }
}

看到了吧,使用占位符要比“+”操作符方便的多。并且此时不再需要 isDebugEnabled() 先进行判断,debug() 方法会在字符串拼接之前执行。

如果只是 Log4J 的话,会先进行字符串拼接,再执行 debug() 方法

这也就意味着,如果日志系统的级别不是 DEBUG,就会多执行了字符串拼接的操作,白白浪费了性能。