Logback日志框架(一):基础与周边配置

FFish 2021年03月12日 170次浏览

Logback由来

Logback意在替代log4j。

Logback is intended as a successor to the popular log4j project.

Log4j:

import org.apache.log4j.Logger
import org.apache.log4j.LoggerFactory

Logback:

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

与log4j相比,logback的优点非常多,总之用就对了。并且因为logback完美实现了log4j,所以从log4j迁移到logback非常容易,只要换个jar包就好了。

在Spring boot中使用logback

项目中使用了任意Spring boot的starter,就可以直接使用logback了。例如spring-boot-starter-web的依赖关系如下:

spring-boot-starter-web -> spring-boot-starter-logging -> spring-jcl

org.slf4j.LoggerFactory创建org.slf4j.Logger

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class DemoApplication 
{
    private static final Logger logger = LoggerFactory.getLogger(DemoApplication.class);
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
         
        logger.info("ffish.net demo log with inputs {}, {}, {}", 1, 2, "abc");
    }
}

执行输出

2021-03-15 12:23:14.295  INFO 2011 --- [           main] net.ffish.demo.DemoApplication        : ffish.net demo log with inputs: 1 2 abc

如果你急需一份差不多的配置

如果你急需,下面是一份差不多的配置文件,将它保存在logback.xml中,放在application.properties平级目录,修改一下日志文件的存储路径(value="/var/log"这里),即可使用。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="60 seconds" debug="true">
    <property name="LOG_LOCATION" value="/var/log" />

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_LOCATION}/mylog.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_LOCATION}/archived/mylog-%d{yyyy-MM-dd}.%i.log
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

接下来我会展开介绍一些主要的语法,如果你英文阅读不错,建议读一读官网说明,更具体。

Logback配置文件读取顺序

Logback可识别的配置文件列表如下:

  1. 最高优先级:classpath中的logback-test.xml
  2. 第二优先级:classpath中的logback.groovy
  3. 第三优先级:classpath中的logback.xml
  4. 第四优先级:实现了com.qos.logback.classic.spi.Configurator接口的配置类。
  5. 第五优先级:logback的默认最小配置BasicConfigurator,log会直接输出到控制台。

配置文件的结构

一个logback.xml应该包含:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!-- 0个、1个或多个“appender”元素 -->
    <appender>
    </appender>
    <!-- 0个、1个或多个“logger”元素 -->
    <logger>
    </logger>
    <!-- 最多1个“root”元素 -->
    <root>
    </root>
</configuration>

最基础的配置

下面这份是最基础的配置,将log输出到控制台,先不用理解其中的含义,做一个demo程序,将配置文件粘贴到logback.xml试试。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

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

打印logstack的内部状态

ch.qos.logback.core.util.StatusPrinter.print(ch.qos.logback.classic.LoggerContext context)方法,可将logback的内部状态打印出来。

public static void main(String[] args) {
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
    StatusPrinter.print(lc);
}

你会看到输出为

10:24:22.704 [main] INFO  net.ffish.demo.DemoApplication - Started DemoApplication in 1.426 seconds (JVM running for 1.762)
10:24:21,520 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
10:24:21,521 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
10:24:21,521 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [CONSOLE]
10:24:21,521 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
10:24:21,522 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
10:24:21,522 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [CONSOLE] to Logger[ROOT]
10:24:21,522 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
10:24:21,522 |-INFO in org.springframework.boot.logging.logback.SpringBootJoranConfigurator@44536de4 - Registering current configuration as safe fallback point

如果不想在代码中通过StatusPrinter.print()来打印logback内部状态,也可以通过配置文件来实现:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">
   ...
</configuration>

上面这个debug="true",表示打印logback内部状态。我看到有些人错误地认为这是设置日志级别为“debug”,实际上没有那个功效:)

官网强烈建议打印logback的内部状态,有助于诊断日志系统的问题。

Enabling output of status data usually goes a long way in the diagnosis of issues with logback. As such, it is highly recommanded and should be considered as a resource of first resort.

配置文件变更自动生效

<configuration>元素如下属性配置,可自动扫描配置文件的修改,并生效新的配置。

<configuration scan="true" scanPeriod="30 seconds">
   ...
</configuration>
  • 如果不设置,默认的scanPeriod是60秒
  • scanPeriod支持的单位有millisecondssecondsminuteshours
  • scanPeriod的默认单位是milliseconds,如果你没有指定的话
  • 如果新的配置文件有xml语法错误,扫描程序会自动回滚到前一个无错的配置

在日志中添加包信息

Logback可以在日志的每一行添加包信息,包含jar包名称和版本号,报错的文件和行号,如下:

14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
  at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
  at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
  at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
  at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
  at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
  at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]

设置方法如下:

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

然而,打印包信息非常耗费计算资源,尤其是抛异常较多的程序。

关闭logback

只创建不关闭是程序员的大忌,如何优雅地关闭logback?

import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
...
// assume SLF4J is bound to logback-classic in the current environment
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();

或者利用Shutdown hook机制。

<confiuration debug="true">
    <shutdownHook />
    ...
</configuration>

当然你也可以为<shutdownHook>添加自定义class,否则系统会调用DefaultShutdownHook