Logback_0-Overview,Architecture,Configuration

0 Why import logback?

我们先来设想一下一个日志组件应该能够做哪些事情?

  1. 日志要能输出到console, 日志文件,甚至电子邮件发出去
  2. 要有丰富的输出格式,比如文本 ,html
  3. 对不同class ,package,level的日志可以灵活输出到不同的日志文件,比如com.foo输出到 foo.log, com.foo.bar输出到bar.log
  4. 能对日志分级,比如debug 日志就留在本地或者测试环境使用,生产不需要

一 logback组件

  • logback-core
    • 基础模块;
    • logback-classic 和 logback-access 都对其有依赖
  • logback-classic
    • 原生依赖了slf4j ,方便用户在使用Logback和 JUL ,log4j 等框架之间切换. 这一点很实用: 如今的java 应用, slf4j几乎是标配.如果你使用logback ,那么你不需要另外依赖 slf4j
    • logback-classic 实际是 logback-core的扩展.
  • logback-access
    • 专门设计为access.log 所用,比如jetty server , servlet

在IDEA里可以清楚看到:
在这里插入图片描述

二 Hello world

最简单的logback 使用:

 public static void main(String[] args) {

        // Note that the above example does not reference any logback classes.
        // i.e.,这个demo中没有一个 logback 的API,只有slf4j API
        Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
        logger.debug("Hello world."); // by default, use the  ConsoleAppender

        // Logback can report information about its internal state using a built-in status system.
        // Important events occurring during logback's lifetime can be accessed through a component called StatusManager
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        StatusPrinter.print(context);
    }

但是深究起来还是很有意思的:

  1. 这段代码没有直接使用到任何logback的API, 而是slf4j的API
  2. logback 的StatusManager 管理着logback的一些内部变量(状态),通过StatusPrinter.print()这种方式能够获知

三 logback架构

主要就是logback的三大件 : Logger, Appenders and Layouts, 当然,即使是log4j , 也有这三大件.

3.1 包结构

Logger 在 logback-classic module 中; 而 Appender Layout 在 logback-core module中, logback-core 中并没有 Logger 的概念, 这也侧面说明了 logback-core 更为基础.

3.2 LoggerContext

Logger的创建, Logger树形继承结构的维护都是由 LoggerContext 处理的.

Logback的使用范式中, 第一步是获取logger, 比如:

Logger logger = LoggerFactory.getLogger("com.ht");
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

LoggerContext 也是一个 ILoggerFactory, 也即是说, 前者是后者的实现类:

public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle{}

明确这个基本点十分重要, 后续我们理解logback时, 首先要知道 LoggerContext 才是"抓手".

3.3 Named Hierarchy

术语解释起来比较复杂,举个栗子则一目了然.

Logger logger = LoggerFactory.getLogger("com.ht");
Logger logger2 = LoggerFactory.getLogger("com.ht.logback");

这里的 logger 是 logger2 的 “祖先”. 实质上, Logger的 “hierarchy” 是通过 logger 的名字来判断的. 实践中, Logger名字一般会带上包名, 如果 Logger 名字存在左起匹配原则, 比如 com.ht.logback 含有 com.ht, 那么 com.ht.logback对应的Logger,是 com.ht的子孙.

3.4 Effective Level aka Level Inheritance

Logger Level的继承关系

  • 首先, root logger 是所有Logger的 祖先, root logger 的默认Logger level是 DEBUG
  • 其次, logger2 (com.ht.logback) 是可以继承 logger (com.ht)的 Logger Level的, 当然前提是 logger2 本身并未配置 Logger Level

3.5 Retrieving Loggers

一定要注意: 使用相同的 LoggerName 多次获取Logger, 只能获取同一个Logger!!!, 也就是说,如下的best practice中, Logger 是全局唯一的.

/**
 * 即使多次调用 LoggerFactory.getLogger() ,实际都能获取同一个 Logger;
 * 本质上只是单例模式 + 工厂模式的功劳
 */
Logger logger_1 = LoggerFactory.getLogger(this.getClass());
Logger logger_2 = LoggerFactory.getLogger(this.getClass());
Assert.assertEquals(logger_1, logger_2);

3.6 Appenders

Appenders ,本质上就是,决定了 日志向什么地方输出的一个组件. 那向什么地方呢? 最常见的当然是文件, 但其实也可以写 socket ,甚至是kafka .

Appenders 也有着继承结构,举个栗子:

  • logger (com.ht) 挂着appender_1 ,appender_2;
  • logger2(com.ht.logback) 挂着 appender_3;
  • 如果 Additivity Flag=true, 那么实际logger2 会向 appender_1 ,appender_2,appender_3 三个 appender 输出.

理解这点十分重要:logback 的继承结构是基于LoggerName 的,简单,直接, 有效.

3.6.1 Discriminator

Discriminator是logback中的中的appender路由,i.e.,用于为日志事件(log events)确定具体的 Appender 的一种机制,比如时间、日志级别、线程名、MDC等作为特征。logback内置了`MDCBasedDiscriminator

3.6.2 EvaluatorFilter/MarkerFilter/LevelFilter/ThresholdFilter

logback中Filter机制(扩展点),能根据日志的某种特征决定要不要记录日志

3.7 Layouts

Layouts 本质上只是就是 决定着一句日志输出的格式.

3.8 bird’s eye on logback’s printing

直观感受下, logback 在将一句日志输出前都做了些啥. 这里是时序图.
在这里插入图片描述
总分六步:

  1. Get the filter chain decision
    TurboFilter chain : 一个责任链模式, 用来控制哪些logging request 要处理,哪些被丢弃
  2. Apply the basic selection rule
    比较 logging request 的Level 和 设定的 Logger Level来决定要不要处理 logging request
  3. Create a LoggingEvent object
    MDC:带有上下文信息的Logging Request 的包装类.
  4. Invoking appenders
  5. Formatting the output
  6. Sending out the LoggingEvent

3.9 sublimation(升华一下)

三大件 logger , appender, layout 形成了所谓的正交设计, 即三者互不依赖.
在这里插入图片描述

四 logback configuration

4.1 配置加载顺序

  • Logback tries to find a file called logback-test.xml in the classpath.

  • If no such file is found, logback tries to find a file called logback.groovy in the classpath.

  • If no such file is found, it checks for the file logback.xml in the classpath

  • If no such file is found, service-provider loading facility (SPI, introduced in JDK 1.6) is used to resolve the implementation of com.qos.logback.classic.spi.Configurator interface by looking up the file META-INF\services\ch.qos.logback.classic.spi.Configurator in the class path. Its contents should specify the fully qualified class name of the desired Configurator implementation.

  • If none of the above succeeds, logback configures itself automatically using the BasicConfigurator which will cause logging output to be directed to the console.

原文如上,再清晰不过了,也不需要多余的解释了. 不过这里还有一个小技巧,
Maven项目中,src/test/resources中可以使用logback-test.xml , src/resources放置logback.xml,如此就将测试/生产环境的配置分隔开了.

另外, 上述的类路径, 或者说,项目目录 src/resources/ 中直接放置 logback.xml,不需要再加一级目录.

这里最有意思的是通过 JDK的SPI机制来做配置 . 在一些中间件的项目中,我们可以使用这种方式来灵活代码编写配置内容.

4.2 Automatically configuring logback

假如啥也没配置,就使用BasicConfigurator,这像是一个最小化配置: ConsoleAppenderattached to root_logger

4.3 Automatic configuration with logback-test.xml or logback.xml

如前所说, 当 class path 下有logback-test.xml or logback.xml时,会从中加载配置.
现在来看几个

4.4 logback 内部状态

当logback 解析配置文件出错时,logback会把错误打印到console;
如果用户自己注册了StatusListener,那么就不会有状态自动打印了.
假如logback没出错,但是用户还是希望看到内部状态时,那可以借助API来实现:

// assume SLF4J is bound to logback in the current environment
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback's internal status
StatusPrinter.print(lc);

4.5 Status data

打印logback的状态数据有利于排查问题. 错误可能不仅出现在post-configuration (配置)时, 在硬盘空间不足,或权限不足同样会出现.因此, logback强烈建议用户配置 StatusListener.
比如这样可以配置一个最简单的 StatusListener:

<configuration>
  <-- Recommendation: place status listeners towards the the top of the configuration file -->
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  

  ... the rest of the configuration file  
</configuration>

另外, 为了灵活装配/卸载 StatusListener, Logback提供了-Dlogback.statusListenerClass参数/

4.5.1 OnConsoleStatusListener 的Shorthand

OnConsoleStatusListener 还有一种更为简单的注册方式,即配置<configuration debug="true"> ,比如:

<configuration debug="true"> 
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!‐- encoders are  by default assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder ‐‐>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

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

这种配置和增加 <statusListener/>标签作用是严格一致的.

4.5.2 -Dlogback.statusListenerClass

StatusListener有多个内置实现:

  • OnConsoleStatusListener
  • OnErrorConsoleStatusListener
  • NopStatusListener
    添加这样的-D参数后, 那么在出现错误情况下的 automatic status printing就没有了.
    也就是说, 设置 -Dogback.statusListenerClass=NopStatusListener 实际会在logback出错时错误信息不再输出. 当然实际不会有人这样配置.

话说回头,绝大多数情况我们并不需要理会这个 StatusListener,保留使用Logback的 automatic status printing 就是最佳实践了.

4.5.3 Viewing status messages

通过 LoggerContext.getStatusManager() 可以获取状态对象. logback内部会缓存部分状态信息Status Messages.而且提供了 ViewStatusMessagesServlet这样一个Servlet来展示.

当然这个Servlet需要在 web.xml中配置.

此外,还可以通过 代码,而不是配置文件来设置 StatusListener.

4.6 -Dlogback.configurationFile

logback的配置文件还可以更灵活地放置到不同地方,这就要使用到 -Dlogback.configurationFile 参数了.
但是配置文件只能是 .xml Or .groovy.如果是System.setProperty()尤其要注意, 这个参数必须第一次 LoggerFactory.getLogger()之前使用.

(这倒是个坑…)

public class ServerMain {
    public static void main(String args[]) throws IOException, InterruptedException {
       // must be set before the first call to  LoggerFactory.getLogger();
      System.setProperty(ContextInitializer.CONFIG_FILE_PROPERTY, "/path/to/config.xml");
       ...
    }   
}

使用-D参数设置logback配置文件的做法在开源组件中十分常见.

4.7 Automatically reloading configuration file upon modification

logback 的配置文件还可以在修改后,动态生效.增加配置:

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

有意思的是, 假如动态修改XML后, 内容有语法错误, 那么 Logback会自动降级到之前的配置.

听起来挺美好,可惜有的应用打包会把 Logback.xml 打到 jar 里去(fat jar),极不好修改…

4.8 Enabling packaging data in stack traces

错误堆栈默认是打印不了 代码行的包信息的(窃以为也没太大必要),也可以打开开关.但这是极其消耗性能的

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

4.9 Invoking JoranConfigurator directly

logback使用 JoranConfigurator 这个库来读取配置, 但这个机制也可以手动控制,换言之, 你可以自行控制如何解析配置.

更牛的是, JoranConfigurator 实际是动态更新 Logback的接口.

这个用得比较少,暂不多说.

4.10 Logback的停止

略…
一般不需要特意配置这个.Logback已经自动帮你做好这个事儿了.

五 配置文件

5.1 配置项

可配置项很多,但基本就是三大块,细说起来比较繁琐.

  • appender
  • logger
  • root logger

如果是经常需要本地调试一些无关紧要的代码,以下的配置片段基本够用.笔者本地跑一些demo时经常这样"短平快"地设置.

<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>foo.log</file>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration.Foo" additivity="false">
    <appender-ref ref="FILE" />
  </logger>

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

5.2 Setting the context name

每个Logger 都会关联到LoggerContext,LoggerContext默认是default.
也可以为其改名, 也可以在日志中输出 contextName.

configuration>
  <contextName>myAppName</contextName>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

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

那另外设置contextName 有何作用呢?
假如只是不希望写死 default作为contextName ,那似乎并不十分有用.

5.3 Variable substitution

logback.xml中的配置,可以设置占位符, 然后从环境变量,-D参数, 外部配置文件读取. 举个栗子:

<configuration>
<!--这里是使用的类路径; 实际可以绝对路径,相对路径等多种路径-->
  <property resource="resource1.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${USER_HOME}/myApp.log</file>
     <encoder>
       <pattern>%msg%n</pattern>
     </encoder>
   </appender>

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

resource1.properties内容:

USER_HOME=/home/sebastien

Logback 会首先解析得到 .properties中的配置项,然后替代掉 XML中的占位符.

5.4 Scopes

变量查找的优先级:
Properties are looked up in the the local scope first, in the context scope second, in the system properties scope third, and in the OS environment last.

5.5 Default values for variables

For example, assuming the variable named aName is not defined, "${aName:-golden}" will be interpreted as “golden”.

5.6 Nested variables

变量还可以嵌套, 举个栗子:
现在有个variables2.propertie文件:

USER_HOME=/home/sebastien
fileName=myApp.log
destination=${USER_HOME}/${fileName}

然后,

<configuration>
  <property file="variables2.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${destination}</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

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

注意这里的 ${destination}就是"嵌套"得到的.

5.7 更多的赋值方法

  • name nesting
  • default value nesting
  • HOSTNAME property
  • CONTEXT_NAME property
  • 甚至还可以动态设置属性
  • 根据复杂条件设置属性
  • 从JNDI获取属性

六 reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值