我们一个Spring Boot项目,使用了org.slf4j.Logger.info记录日志。类似代码如下:

@Slf4j
public class CTest {
    public void test() {
    	。。。
        log.info("Hello World!");
    }
}

结果运行的时候,系统除了将日志记录到日志文件,还将日志打印到控制台。由于日志太多太密集,屏幕不停地滚动,令人目不暇接,加上乱码,简直不知所谓。

为啥日志还会自动输出到控制台呢?我一向孤陋寡闻,好奇去问AI。AI告诉我,系统使用org.slf4j.Logger.info记录日志,而SLF4J 只是一个日志门面(Facade),具体的日志记录行为是由绑定的具体日志实现框架(如 Logback、Log4j、java.util.logging 等)决定的。就相当于,org.slf4j.Logger.info只定义了日志的接口,具体实现要看用了什么日志工具包。如果没有指定,Spring Boot会默认采用Logback。我看了看pom.xml,没有发现有引用什么日志包,因此可以确定,我们系统用的应该是Logback。我记得Spring Boot的核心是控制反转(IoC),AOP和容器,然后AI说,除此之外,还有约定大于配置,自动配置,起步依赖等等。起步依赖就是为程序启动自动提供了许多准备,自动配置应该就包含了上面说的,默认采用Logback作为日志框架的做法。

一、禁止日志输出到控制台

回到日志内容默认输出到控制台,这是Logback的做派。如果要禁止,需要在配置文件或代码中指定。这个配置文件,不是指application.yml这种Spring Boot项目级的配置文件,它们只能简单地设置日志的输出级别和输出路径,而禁止输出到控制台,只能在专有的logback-spring.xml进行配置。

1、application.yml只能简单地设置日志的输出级别和输出路径

application.yml

logging:
  level:
    root: info      # 设置全局日志级别
    com.yourpackage: debug  # 针对特定包的日志级别
  file:
    path: ./log/seller.log  # 日志输出路径
  pattern:
    file: '%d{yyyy-MM-dd HH:mm:ss} - %msg%n'  # 日志文件的格式
    console: '%d{yyyy-MM-dd HH:mm:ss} - %msg%n'  # 控制台输出的日志格式

2、日志配置文件logback-spring.xml设置

logback-spring.xml需要手动创建,位置也是在resources根目录下,SpringBoot会自动识别。文件内容如下:

resources/logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义文件日志输出 -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>./log/seller.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 配置 root 只使用文件输出 -->
    <root level="info">
        <appender-ref ref="FILE" />
    </root>
</configuration>

3、也可以通过配置类进行设置

当然也可以通过创建配置类来设置:

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.util.Iterator;

@Configuration
public class LoggingConfig {

    @Bean
    public void configureLogging() {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger rootLogger = loggerContext.getLogger("ROOT");

        // 移除 ConsoleAppender
        removeConsoleAppender(rootLogger);

        // 添加 FileAppender
        addFileAppender(loggerContext, rootLogger);
    }

    private void removeConsoleAppender(Logger rootLogger) {
        Iterator<Appender<ILoggingEvent>> iterator = rootLogger.iteratorForAppenders();
        while (iterator.hasNext()) {
            Appender<ILoggingEvent> appender = iterator.next();
            if (appender instanceof ConsoleAppender) {
                rootLogger.detachAppender(appender);
                appender.stop();
            }
        }
    }

    private void addFileAppender(LoggerContext context, Logger rootLogger) {
        // 创建 FileAppender
        FileAppender<ILoggingEvent> fileAppender = new FileAppender<>();
        fileAppender.setContext(context);
        fileAppender.setFile("./log/seller.log");

        // 设置日志输出格式
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(context);
        encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss} - %msg%n");
        encoder.start();

        fileAppender.setEncoder(encoder);
        fileAppender.start();

        // 添加到 rootLogger
        rootLogger.addAppender(fileAppender);
    }
}

二、设置日志输出级别

1、设置

Logback的日志输出有所谓级别:Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7
其中FATAL最高,DEBUG最低。级别越高,记录到日志的机会就越小。如果设为DEBUG,可能日志内容会很多。
这是我另外一个项目中,resources/logback-spring.xml的部分内容

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false">
	。。。
    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
    <root level="warn">
        <appender-ref ref="console"/>
        <appender-ref ref="debug"/>
        <appender-ref ref="error"/>
    </root>
</configuration>

每个 appender 负责将特定的日志输出到不同的目的地(例如控制台、文件等)。如果一个级别没有 appender,那么该级别的日志只会被传递给其他引用的 appender。

在 Logback 配置中,<appender-ref ref="debug"/> 的作用是将日志记录引用到名为 debug 的 appender。但是,它并没有直接指定输出目标(如控制台或文件),而是依赖于 debug appender 的具体定义。

1)输出到控制台

<configuration>
    <!-- 定义一个名为 "console" 的 ConsoleAppender -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 设置日志格式 -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 根日志器配置 -->
    <root level="info">
        <!-- 引用 console appender -->
        <appender-ref ref="console"/>
    </root>
</configuration>

2)输出到日志文件

<configuration>
    <!-- 定义一个名为 "file" 的 FileAppender -->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!-- 设置日志文件路径 -->
        <file>/path/to/logfile.log</file>
        <!-- 设置日志格式 -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 根日志器配置 -->
    <root level="info">
        <!-- 引用 file appender -->
        <appender-ref ref="file"/>
    </root>
</configuration>

注意 appender 的名称只是一个名词,自定义的,可以命名为任意名字,比如 <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> ,console并不意味着它是输出到控制台,输出到控制台,关键是属性class="ch.qos.logback.core.ConsoleAppender"

2、日志一例

    <!-- Log file error output -->
    <appender name="warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/warn.log</file>  <!-- 使用新的日志路径 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/%d{yyyy-MM}/warn.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <!-- 简化格式: 只输出时间和日志消息 -->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss}] %-5level - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>   

解释:
创建一个名为 warn 的 RollingFileAppender,专门用于记录 WARN 级别及以上的日志。
日志文件存储在 ${log.path} 指定的路径下,初始文件名为 warn.log。
日志文件会被压缩为 gzip 格式
使用 SizeAndTimeBasedRollingPolicy 实现日志轮转:
按日期和大小分割日志文件。
每个日志文件最大为 50MB。
最多保留 30 天的日志文件。
日志格式简化为 [时间戳] 日志级别 - 日志消息。
只有 WARN 级别及以上的日志会被记录到该 appender 中。

这种配置非常适合用于单独记录警告和错误日志,便于后续分析和排查问题。

3、根日志级别

1)只有等于或高于根日志级别的日志才会被处理

在 Logback 中,如果一个 appender 没有显式设置日志级别或过滤器,它会继承 根日志级别 的配置。
如下例,根日志级别被设置为 INFO,这意味着只有 INFO 级别及以上的日志(包括 WARN 和 ERROR)会被处理。

<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <!-- 指定控制台输出的字符集为 UTF-8,防止乱码 -->
        <pattern>[%d{yyyy-MM-dd HH:mm:ss}] %-5level - %msg%n</pattern>
    </encoder>
</appender>

<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
<root level="info">
    <appender-ref ref="console"/>
</root>

对于console这个appender,它没有指定level,那么console就默认采用根日志级别,即console appender 也会遵循这一规则,只输出 INFO 级别及以上的日志。

2)根日志级别可绑定多个appender

这意味着,等于或高于根日志级别的日志,会同时有多种处理方式。比如

    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="debug"/>
        <appender-ref ref="warn"/>
        <appender-ref ref="error"/>
    </root>

假如里面的4个appender,有的只输出到控制台,有的只输出到日志,那么info及以上级别的日志,既输出到控制台,又输出到日志。但如果appender显示指明了级别,那么只有该级别的日志才会使用这个appender。比如

    <appender name="warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
		。。。
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

则只有warn级别才能使用这个appender,尽管它在根日志级别绑定的appender列表里。

三、日志输出到控制台乱码问题

我发现,日志内容输出到控制台,其中的汉字会是乱码。IDE、运行jar包的命令行、包括日志配置文件都设了UTF-8,但仍然是乱码。但如果用System.out.println()直接输出到控制台,又正常。这个问题暂时还没有解决。

记录一下。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐