关于日志

日志级别
fatal > error > warn > info > debug > trace
  1. trace:级别最低
  2. debug:需要调试时候的关键信息
  3. info:普通的打印信息
  4. warn:警告信息
  5. error:错误信息
  6. fatal:灾难级的,因为代码异常导致程序退出执行的事件;系统级别,程序无法打印

当某个目录设置了日志级别,我们只能得到此级别及更高级别的日志

springboot默认日志级别是 info,我们可以在springboot的默认配置文件中修改日志级别

image-20221020110149412

日志格式

image-20221020154852113

日志框架

日志框架包括日志门面、日志实现。日志门面就是接口,日志实现相当于是具体的实现类

一般常用的框架有:
JUL,JCL,JBOSS-LOGING,longback,log4j,log4j2,slf4j等等。

日志门面 (日志的抽象层)日志实现
JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-loggingLog4j JUL(java.util.logging) Log4j2 Logback

左边选一个门面(抽象层)、右边来选一个实现。SpringBoot选用的是SLF4J和logback

关于SLF4j

介绍

slf4j,simple logging facade for java的缩写,翻译为java的简单日志外观。slf4j(simple logging facade for java)不是一个真正的日志实现,而是一个抽象层( abstraction layer),也可以理解为一个接口,它是一种适配器的实现方式,它本身不具有输出日志的功能,输出日志还是由log4j、logback等这样的日志组件来进行输出。

简单来说,它仅仅是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就比如JDBC一样,只是一种规则而已。所以单独的slf4j是不能工作的,必须搭配其他具体的日志实现方案。

2909474-b5127a18b3eda3ec

使用场景

现在开发项目都是使用maven进行构建开发,假设架构师a开发了一个order.jar通用组件,他在程序中使用的是log4j组件进行日志输出;程序员b自己之前一直在开发自己的业务模块,并且他在程序中使用的是logback日志组件,突然有一天程序员b需要在自己的业务系统中使用架构师a的order.jar通用组件,这个时候问题就出现了,由于两套程序使用了不同的日志组件,程序员b除了要维护自己的logback日志组件配置,还需要维护order,jar中的日志组件配置,这个问题是很头疼的。解决方案就是:使用slf4j

SLF4j的使用

使用slf4j,只有一个强制性的依赖,就是slf4j-api-x.x.x.jar,我们在编写代码的时候,只会使用这个jar包里的API,应用程序在运行时去类路径下查找绑定的具体日志框架,并使用该绑定的日志框架进行实际的日志操作,如果在应用程序的类路径下面没有找到合适的绑定的话,slf4j默认使用一个没有任何操作的实现。

image-20221021092345311

slf4j只是一个日志标准,并不是日志系统的具体实现。理解这句话非常重要,slf4j只做两件事情:

  • 提供日志接口
  • 提供获取具体日志对象的方法

比如slf4j-simple、logback都是slf4j的具体实现。

当一个应用面向了很多日志框架,如果我们直接去掉这些依赖包的话,系统肯定会报错的,因为Spring本来底层会调用这些框架的API等。可以采取偷梁换柱的做法:用另一个jar包代替本来的jar包。比如:log4j12;代替后和slf4j完美契合,就可以使用。

引入Maven依赖:

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

总结:

  1. 先将以前的剔除
  2. 用中间包替换原有的日志框架
  3. 导入slf4j其他的实现

slf4j-api作为slf4j的接口类,使用在程序代码中,这个包提供了一个Logger类和LoggerFactory类,Logger类用来打日志,LoggerFactory类用来获取Logger,而slf4j-log4j就是连接slf4j和log4j的桥梁。那么他们是怎么连接的呢?我们来看看slf4j的LoggerFactory类的getLogger函数的源码:

/**
 * Return a logger named corresponding to the class passed as parameter, using
 * the statically bound {@link ILoggerFactory} instance.
 *
 * @param clazz the returned logger will be named after clazz
 * @return logger
 */
public static Logger getLogger(Class clazz) {
  return getLogger(clazz.getName());
}
/**
 * Return a logger named according to the name parameter using the statically
 * bound {@link ILoggerFactory} instance.
 *
 * @param name The name of the logger.
 * @return logger
 */
public static Logger getLogger(String name) {
  ILoggerFactory iLoggerFactory = getILoggerFactory();
  return iLoggerFactory.getLogger(name);
}

  public static ILoggerFactory getILoggerFactory() {
  if (INITIALIZATION_STATE == UNINITIALIZED) {
    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
    performInitialization();
  }
  switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
      return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITIALIZATION:
      return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
      throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
      // support re-entrant behavior.
      // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
      return TEMP_FACTORY;
  }
  throw new IllegalStateException("Unreachable code");
}

LoggerFactory.getLogger()首先获取一个ILoggerFactory接口,然后使用该接口获取具体的Logger。获取ILoggerFactory的时候用到了一个StaticLoggerBinder类,仔细研究我们会发现StaticLoggerBinder这个类并不是slf4j-api这个包中的类,而是slf4j-log4j包中的类,这个类就是一个中间类,它用来将抽象的slf4j变成具体的log4j,也就是说具体要使用什么样的日志实现方案,就得靠这个StaticLoggerBinder类。再看看slf4j-log4j包种的这个StaticLoggerBinder类创建ILoggerFactory长什么样子:

private final ILoggerFactory loggerFactory;

private StaticLoggerBinder() {
  loggerFactory = new Log4jLoggerFactory();
  try {
    Level level = Level.TRACE;
  } catch (NoSuchFieldError nsfe) {
    Util
        .report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
  }
}

public ILoggerFactory getLoggerFactory() {
  return loggerFactory;
}

可以看到slf4j-log4j中的StaticLoggerBinder类创建的ILoggerFactory其实是一个org.slf4j.impl.Log4jLoggerFactory,这个类的getLogger函数是这样的:

public Logger getLogger(String name) {
  Logger slf4jLogger = loggerMap.get(name);
  if (slf4jLogger != null) {
    return slf4jLogger;
  } else {
    org.apache.log4j.Logger log4jLogger;
    if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
      log4jLogger = LogManager.getRootLogger();
    else
      log4jLogger = LogManager.getLogger(name);

    Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
    Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
    return oldInstance == null ? newInstance : oldInstance;
  }
}

就在其中创建了真正的org.apache.log4j.Logger,也就是我们需要的具体的日志实现方案的Logger类。就这样,整个绑定过程就完成了。

在实际开发中,建议使用slf4j,而不是直接使用 log4j, commons logging, logback 或者 java.util.logging等,因为这样可以让程序具有更多的扩展性。

  1. **与客户端解耦:**使用slf4j会使得它独立于任何一个特定的日志实现,这意味着不需要管理多个日志配置或者多个日志类库,以后别人调用你的工具包时也可以不用关心日志组件问题。
  2. **省内存:**slf4j提供了基于占位符{}的日志方法,不会有字符串拼接操作,减少了无用String对象的数量,节省了内存。并且,使用slf4j的日志方法,可以延迟构建日志信息(srting)的开销,程序可以有更高的吞吐性能。

SpringBoot日志配置

默认日志
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

依赖关系图:

2909474-92ed664088723dd9

  1. 我们可以看到SpringBoot底层使用slf4j+logback的方式进行日志记录
  2. 其他日志会转换成slf4j
  3. 可以从依赖中看到明显的替换包

image-20221021105920333

SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可。

基本使用配置
logging:
  #logging.level是map类型, 我们可以指定不同包下使用不同的日志级别
  level:
    com.atguigu: trace
  #以文件形式打印日志logging.file
  file:
    name: boot.log	#不指定日志文件的具体位置, 只指定文件名字时, 日志文件默认会生成在项目目录下
    name: D:/boot.log	#指定日志文件的具体位置时, 日志文件会生成在指定的位置
    path: /spring/log	#在项目所在磁盘根目录下的spring目录下的log目录下生成名为spring.log的日志文件
  #日志输出格式:控制台 or 文件
  pattern: 
  	console: %d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
  	file: %d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
  	#    日志输出格式:
    #    %d 表示日期时间,
    #    %thread 表示线程名,
    #    %-5level 级别从左显示5个字符宽度
    #    %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
    #    %msg 日志消息,
    #    %n 换行符
    #-->
    # %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

指定日志配置文件

在类路径下放上每个日志框架自己的配置文件即可, springboot就不使用他默认的日志配置了

Logging SystemCustomization
Logbacklogback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
Log4j2log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging)logging.properties

当日志配置命名为logback.xml时, 这个配置直接就被日志框架识别了

当日志配置命名为logback-spring.xml时, 这个配置由springboot解析识别, 可以使用更高级的功能, 例如 springProfile, 他与springboot中的Profiles对应, 在springboot默认配置文件中激活指定Profiles, 日志配置也将改变

<springProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误:

no applicable action for [springProfile]

比如在logback-spring.xml中做出以下配置,就可以指定某段配置只在某个环境下生效

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="log.path" value="./logs" />
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[%X{trackId}][%thread] [%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level %logger{30} - %msg%n</pattern>
        </layout>
    </appender>
    <!-- level为 info 日志,时间滚动输出  -->
    <appender name="Log_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/info.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>[%X{trackId}][%thread] [%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level %logger{30} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日志文档保留天数-->
            <maxHistory>5</maxHistory>
            <maxFileSize>100MB</maxFileSize>
            <totalSizeCap>5GB</totalSizeCap>
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>

    <springProfile name="dev">
        <root level="info">
            <appender-ref ref="CONSOLE" />
        </root>
        <!--    mybatis sql单独控制输出级别    -->
        <logger name="com.demo.example.dao" level="debug"/>
    </springProfile>
    <springProfile name="prod">
        <root level="info">
            <appender-ref ref="Log_FILE" />
        </root>
        <!--    mybatis sql单独控制输出级别    -->
        <logger name="com.demo.example.dao" level="debug"/>
    </springProfile>
    <springProfile name="online">
        <root level="info">
            <appender-ref ref="Log_FILE" />
        </root>
        <!--    mybatis sql单独控制输出级别    -->
        <logger name="com.demo.example.dao" level="debug"/>
    </springProfile>
</configuration>
切换日志框架
  1. springboot默认使用spring-boot-starter-logging启动器, 使用这个启动器默认使用Logback 进行日志记录, 如果要使用Log4j2 进行日志记录, 那么可以切换spring-boot-starter-log4j2启动器

  2. 具体切换方法为, 将默认spring-boot-starter-logging启动器排除, 使用spring-boot-starter-log4j2启动器

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--排除默认spring-boot-starter-logging启动器-->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!--使用spring-boot-starter-log4j2启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    
  3. 切换log4j日志框架

    首先排除日志框架的实现jar, 比如偷梁换柱jar, log4j-to-slf4j.jar, 和logback-classic.jar(logback实现jar), 然后引入log4j的实现jar

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
    </dependency>
    
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐