SpringBoot日志框架


概述

项目中日志系统是必不可少的的。 目前比较流行的日志框架有log4jlogback等 。可能大家还不知道,这两个框架的作者是同一个人,Logback旨在作为流行的log4j项目的后续版本,从而恢复log4j离开的位置。另外 slf4j(Simple Logging Facade for Java) 则是一个日志门面框架,提供了日志系统中常用的接口,logback log4j 则对slf4j 进行了实现。

为什么使用logback

  • Logback 是log4j框架的作者开发的新一代日志框架,它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J
  • Logback的定制性更加灵活,同时也是spring boot的内置日志框架

开始使用

SpringBoot支持log4j、logback作为日志框架。 如果你使用starters启动器,Spring Boot将使用Logback作为默认日志框架。Spring-boot-starter启动器包含spring-boot-starter-logging启动器并集成了slf4j日志抽象及Logback日志框架。

默认配置

默认情况下Spring Boot将日志输出到控制台,不会写到日志文件。如果要编写除控制台输出之外的日志文件,则需在application.yaml中设置logging.filelogging.path属性

logging:
  file:
    path: 日志文件路径 
    name: 文件名
  level: debug
  pattern:
    console: 日志打印规则
  • path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如path=/var/log

logback-spring.xml详解

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,将xml放至 src/main/resource下面。

也可以使用自定义的名称,比如logback-config.xml,只需要在application.yaml文件中使用logging.config=classpath:logback-config.xml指定即可

在讲解 logback-spring.xml之前我们先来了解三个单词:Logger, AppendersLayouts(记录器、附加器、布局):Logback基于三个主要类:LoggerAppenderLayout。 这三种类型的组件协同工作,使开发人员能够根据消息类型和级别记录消息,并在运行时控制这些消息的格式以及报告的位置。首先给出一个基本的xml配置如下:


  
  
    
    
    
      %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
    
  
  
  
  
  
  
            
    
    
  

元素

logback.xml配置文件的基本结构可以描述为元素,包含零个或多个元素,后跟零个或多个元素,后跟最多一个元素(也可以没有)。下图说明了这种基本结构:

元素

元素只接受一个必需的name属性,一个可选的level属性和一个可选的additivity属性,允许值为truefalselevel属性的值允许一个不区分大小写的字符串值TRACEDEBUGINFOWARNERRORALLOFF元素可以包含零个或多个元素; 这样引用的每个appender都被添加到指定的logger中。logger元素级别具有继承性。

元素

元素配置根记录器。 它支持单个属性,即level属性。 它不允许任何其他属性,因为additivity标志不适用于根记录器。 此外,由于根记录器已被命名为ROOT,因此它也不允许使用name属性。level属性的值可以是不区分大小写的字符串TRACEDEBUGINFOWARNERRORALLOFF之一元素可以包含零个或多个元素; 这样引用的每个appender都被添加到根记录器中。

元素

appender使用元素配置,该元素采用两个必需属性nameclassname属性指定appender的名称,而class属性指定要实例化的appender类的完全限定名称。 元素可以包含零个或一个元素,零个或多个元素以及零个或多个元素,下图说明了常见的结构:

重要:在logback中,输出目标称为appender,addAppender方法将appender添加到给定的记录器logger。给定记录器的每个启用的日志记录请求都将转发到该记录器中的所有appender以及层次结构中较高的appender。换句话说,appender是从记录器层次结构中附加地继承的。例如,如果将控制台appender添加到根记录器,则所有启用的日志记录请求将至少在控制台上打印。如果另外将文件追加器添加到记录器(例如L),则对L和L的子项启用的记录请求将打印在文件和控制台上。通过将记录器的additivity标志设置为false,可以覆盖此默认行为,以便不再添加appender累积。

元素

encoder中最重要就是pattern属性,它负责控制输出日志的格式,这里给出一个我自己写的示例:


  %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n

使用后的输出格式如下图所示 :

  • %d{yyyy-MM-dd HH:mm:ss.SSS}:日期
  • %-5level:日志级别
  • %highlight():颜色,info为蓝色,warn为浅红,error为加粗红,debug为黑色
  • %thread:打印日志的线程
  • %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符
  • %logger:日志输出的类名
  • ** %-40.40():如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符**
  • %cyan:颜色
  • %msg:日志输出内容
  • %n:换行符

元素

filter中最重要的两个过滤器为:**LevelFilter****ThresholdFilter**
**LevelFilter **根据精确的级别匹配过滤事件。 如果事件的级别等于配置的级别,则筛选器接受或拒绝该事件,具体取决于onMatchonMismatch属性的配置。 例如下面配置将只打印INFO级别的日志,其余的全部禁止打印输出:


  
    
      INFO
      ACCEPT
      DENY
    
    
      
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      
    
  
  
    
  

ThresholdFilter 过滤低于指定阈值的事件。 对于等于或高于阈值的事件,ThresholdFilter将在调用其decision()方法时响应NEUTRAL。 但是,将拒绝级别低于阈值的事件,例如下面的配置将拒绝所有低于INFO级别的日志,只输出INFO以及以上级别的日志:


  
    
    
      INFO
    
    
      
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      
    
  
  
    
  

详细的logback-spring.xml示例:

以上介绍了xml中重要的几个元素,下面将我配置的xml贴出来以供参考(实现了基于日期和大小翻滚的策略,以及经INFOERROR日志区分输出,还有规范日志输出格式等):

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

 
    
    
    
        
        
        
            
            
            
            
            
            
            
            %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n
            
            UTF-8
        
    
 
    
    
    
    
    
        
        logs/project_info.log
        
        true
        
            ERROR
            DENY
            ACCEPT
        
        
        
            
            
            
            logs/project_info.%d.%i.log
            
            30
            
            20GB
            
            10MB
        
        
        
            
            %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n
            
            UTF-8
        
    
 
    
    
    
    
    
        
        logs/project_error.log
        
        true
        
        
            ERROR
        
        
        
            
            
            
            logs/project_error.%d.%i.log
            
            30
            
            20GB
            
            10MB
        
        
        
            
            %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n
            
            UTF-8
        
    
 
    
    
    
        
    
 
    
    
    
        
        
    
 
    
    
        
        
    
 
    
    
        
        
    
 

附件内容

1、这里再说下log日志输出代码,一般有人可能在代码中使用如下方式输出:

Object entry = new SomeObject(); 
logger.debug("The entry is " + entry);

2、上面看起来没什么问题,但是会存在构造消息参数的成本,即将entry转换成字符串相加。并且无论是否记录消息,都是如此,即:那怕日志级别为INFO,也会执行括号里面的操作,但是日志不会输出,下面是优化后的写法:

if(logger.isDebugEnabled()) { 
    Object entry = new SomeObject(); 
    logger.debug("The entry is " + entry);
}

3、2的写法,首先对设置的日志级别进行了判断,如果为debug模式,才进行参数的构造,对第一种写法进行了改善。不过还有最好的写法,使用占位符:

Object entry = new SomeObject(); 
logger.debug("The entry is {}.", entry);

如果有多个参数,写法如下:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

4、记录日志的时候我们可能需要在文件中记录下异常的堆栈信息,经过测试,**logger.error(e) **不会打印出堆栈信息,正确的写法是:

logger.error("程序异常, 详细信息:{}", e.getLocalizedMessage() , e);

关注公众号及时阅读最新的技术文章。