参杂着原文翻译和自己的话。。感谢有道翻译。

全文注释:

  • log请求=logging request=日志请求,指代logger.info、logger.debug之类的调用。
  • 祖先的意思根据语境变化,有时候也包含父母,大多数时候只指爷爷辈以上。

Logback's architecture

logback被分成三个模块:logback-core, logback-classic和logback-access,其中core模块是其它两个模块的基础。classic模块是core模块的扩展,它对应着log4j的一个显著改进版本。logback-classic实现了SLF4J API,这让你可以在各种日志系统(JUL、log4j等)和logback间无压力地切换。第三个名为access的模块与Servlet容器集成,以提供HTTP访问日志功能。文档的剩余部分,主要讲述logback-classic相关的内容。

Logger, Appenders and Layouts

Logback建立在三种主要类的基础上:LoggerAppender以及Layout。

Logger类属于logback-classic模块,另外两个接口属于logback-core模块,作为一个通用模块,在logback-core模块中并没有logger的概念。

Logger context

任何日志API优于普通System.out.println的第一个也是最重要的优势在于:日志API能够禁用特定的log语句,同时允许其他log语句不受阻碍地打印。这暗示着log语句按照开发人员制定的标准分成若干类。在logback-classic中,这种分类是logger的固有部分。所有logger都共同隶属于一个LoggerContext,后者负责制造logger以及将它们排列在树状层次结构中。

logger都是有名字的实体。它们的名字是大小写敏感,并且遵循分层命名规则。例如说,名为“com.mycompany”的logger被称作名为“com.mycompany.xxx”的logger的父母(直接关系!)。类似地,“com”被称为“com.mycompany.xxx”的祖先(隔代关系)。

root logger位于logger的树状层次结构的顶端(根部)。它的特殊之处在于它是任何logger树状层次结构的一部分。像其它Logger一样,可以通过名字拿到它:

        Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
rootLogger.debug("大家好!我是root logger");

Effective Level(有效等级)又名Level Inheritance

logger可以被赋予等级,可以分配的等级有:TRACE, DEBUG, INFO, WARN和ERROR。Level类是final的,不能有子类!文档上的言下之意貌似是有更灵活的方式来改变Level类。如果,一个logger没有被赋予等级,则它的有效等级与和它最近的被赋予等级的祖先logger一样。言下之意就是,所有logger虽然不一定都有赋予等级,但是都是具有“有效等级”的(继承自祖先)。参考图:

强调:子logger继承的是最近祖先的assigned level,而不是effective level!

Printing methods and the basic selection rule

形如logger.info("xxxxxxxx...")是一个等级为info的log请求。只有在log请求的等级高于或者等于它的logger的有效等级时才是“启用(enabled )”的,否则称该请求被禁用(disabled)。

这个规则是logback的核心。它设定的级别(logger有效等级,log请求等级)顺序如下:TRACE < DEBUG < INFO < WARN < ERROR。

basic selection rule例子程序:

package org.sample.logback;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class LogbackTest { @Test
public void testLogback() {
// root logger的默认赋予等级,也就是有效等级为DEBUG
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); // This request is disabled, because TRACE < DEBUG.
rootLogger.trace("trace:大家好!我是root logger");
// This request is enabled, because DEBUG >= DEBUG
rootLogger.debug("debug:大家好!我是root logger");
// This request is enabled, because INFO >= DEBUG
rootLogger.info("info:大家好!我是root logger");
// This request is enabled, because WARN >= DEBUG
rootLogger.warn("warn:大家好!我是root logger"); /*
output=
14:28:37.443 [main] DEBUG ROOT - debug:大家好!我是root logger
14:28:37.448 [main] INFO ROOT - info:大家好!我是root logger
14:28:37.448 [main] WARN ROOT - warn:大家好!我是root logger
PS. 唯独不见trace
*/
}
}

Retrieving Loggers - - 检索logger

名字相同的logger就是同一个logger,给定logger的名字可以在应用程序中的任何地方拿到这个logger。

按照生物学的观点,老子总是得先于孩子的。但是logback有那么点不同,它允许儿子先初始化、老子之后初始化,不过两个logger的父子关系在老子初始化后也会自动关联上去,让我们来看一个例子:

package org.sample.logback;

import ch.qos.logback.classic.Level;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class LogbackTest { @Test
public void testLogback() {
Logger son = LoggerFactory.getLogger("father.son"); // 儿子先进行初始化
// son默认继承root logger的赋予等级,也就是son的有效等级是DEBUG
son.trace("help!"); // 这个log请求未被启用,因为 TRACE < DEBUG // Level类被定义在ch.qos.logback.classic里。所以必须用ch.qos.logback.classic.Logger才能设置它的level
ch.qos.logback.classic.Logger father =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("father");
// 这个时候,初始化儿子他爹,将自动进行关联
father.setLevel(Level.TRACE); // 这样儿子的有效等级也变成TRACE了 son.trace("help!"); // 结果就是上一句不打印,这一句打印
}
}

当然,在通常情况下,logger的配置在程序初始化的时候就完成了(通过读取配置文件)。用类全名来命名logger仍然是目前已知的最好的策略。

Appenders and Layouts

允许选择性地启用和禁用log请求只是logback功能的一部分。Logback还允许将log请求打印到多个目的地。用logback里的术语来讲,日志的“输出目的地”被称为appender。一个logger可以有多个appender。

可以通过addAppender方法把一个appender添加到logger上。对于给定logger的每个enable的log请求都将被转发给该logger中的所有appender以及层次结构中更高所有appender(祖先、父母辈的appender)。换句话说,appender具有可加性。儿子能够共享老子(以及祖先,前提是老子没有setAdditive(false);)的所有appender。举个例子,在这里忽略root logger的存在,假设老子logger有且仅有一个输出到文件的appender,而儿子logger有且仅有一个输出到控制台的appender,那么老子就只能打印到文件,而儿子打印到控制台打印到文件,如果有一天儿子不想再打印到文件了,只要setAdditive(false);就可以了,并且孙子(儿子的儿子)也不会打印到文件(等于儿子全家都和老子断绝关系了。)

用户通常并不满足于决定“输出目的地”,还希望能够随意操控打印格式。要做到这一点很简单,只需把layout和相应的appender关联起来就ok了。layout负责按照用户的期望格式化log请求。PatternLayout是标准logback发行版的一部分,它允许用户根据类似于C语言printf函数的转换模式指定输出格式。

Parameterized logging参数化log请求

不用这样写:

if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

这样写就可以了↓

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

以下两行将产生完全相同的输出。但是,如果log请求被禁用,那么第二个变体的性能要比第一个变体好至少30倍

logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);

多个参数也是支持的。如下:

package org.sample.logback;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class LogbackTest { @Test
public void testLogback() {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
logger.debug("Hello {} , how are {}", "xkfx", "you");
// => 15:57:16.196 [main] DEBUG org.sample.logback.LogbackTest - Hello xkfx , how are you
logger.debug("Hello {} , how are {}, what is your {}", "xkfx", "you", "name");
// =>15:58:03.783 [main] DEBUG org.sample.logback.LogbackTest - Hello xkfx , how are you, what is your name // 还可以这样↓
Object[] paramArray = {"xkfx", "you", "name"};
logger.debug("Hello {} , how are {}, what is your {}", paramArray); // 输出同上
}
}

A peek under the hood在引擎盖下面偷看一下。。

介绍完了logback的几个基本组件,我们已经可以描述logback框架在用户发起log请求时所采取的步骤。现在让我们分析一下当用户调用名为com.wombat的logger的info()方法时,logback所采取的步骤。接下来用单步调试跟踪一下logger.info("Hello World");

1. Get the filter chain decision

首先step into进入logger.info方法:

出现一个看到名字想不出来具体含义的方法,继续step into进入filterAndLog_0_Or3Plus方法:

    private void filterAndLog_0_Or3Plus(String localFQCN, Marker marker, Level level, String msg, Object[] params, Throwable t) {
/*
如果filter chain存在,则TurboFilter链将被调用
1、Turbo过滤器可以设置上下文范围的阈值,
2、或者根据特定的信息(Marker、Level、Logger、message以及与每个log请求关联的Throwable等)
过滤掉某些事件。
(PS. 硬翻,可能有错)
*/
FilterReply decision = this.loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);
if(decision == FilterReply.NEUTRAL) {
if(this.effectiveLevelInt > level.levelInt) {
// 这里就比较顾名思义了。。
// 因为level.levelInt=20000(INFO)> 有效等级DEBUG的10000
// 所以logging request不会在这里被抛弃
return;
}
} else if(decision == FilterReply.DENY) {
return;
}
this.buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}

=> 补充turbo filter的相关资料:在Logback-classic中提供两个类型的filters , 一种是regular filters,另一种是turbo filter。regular filters是与appender 绑定的,而turbo filter是与与logger context(logger上下文绑定的,区别就是,turbo filter过滤所有logging request(log请求),而regular filter只过滤某个appender的logging request。(作者:巫巫巫政霖,原文:https://blog.csdn.net/Doraemon_wu/article/details/52072360

2. Apply the basic selection rule

就是指上面代码中的这一段:

如果decision == FilterReply.ACCEPT的话,就没有这个步骤了。

3. Create a LoggingEvent object

step into进入buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);

参数情况:

我们将用这些参数,以及当前时间、当前线程、各种类的相关数据、MDC等构造一个LoggingEvent对象。

    private void buildLoggingEventAndAppend(String localFQCN, Marker marker, Level level, String msg, Object[] params, Throwable t) {
LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
le.setMarker(marker);
this.callAppenders(le);
}

请注意,其中一些字段是惰性初始化的,只有在实际需要时才初始化。MDC用于附加上下文信息来修饰logging request。MDC在后面的一章中讨论。

4. Invoking appenders

在创建LoggingEvent对象之后,logback将调用所有当前logger可用的appender(就是自己的、老子的、祖先的)的doAppend()方法。step into进入callAppenders(ILogginEvent event);↓

    public void callAppenders(ILoggingEvent event) {
int writes = 0; for(Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if(!l.additive) {
break;
}
}
if(writes == 0) {
this.loggerContext.noAppenderDefinedWarning(this);
} }

这个for循环写得也是非常酷炫。。首先检查com.wombat的AppenderAttachableImpl,发现没有,再找com的aai,发现也没有,最后找root logger的aai,终于找到了,于是对这个aai(应该是和logger的特定appender共同存亡的一个对象)调用AppenderAttachableImpl类的appendLoopOnAppenders(event)方法,如下所示,外围的appendLoopOnAppenders方法是属于ch.qos.logback.classic.Logger类的:

    private int appendLoopOnAppenders(ILoggingEvent event) {
return this.aai != null?this.aai.appendLoopOnAppenders(event):0;
}

在appendLoopOnAppenders的方法体里拿到对应appender的引用,调用doAppend方法(传入logginevent对象作为参数)。所有logback发行版里的appender都继承自抽象类AppenderBase,这个抽象类在synchronized块里实现了doAppend方法,从而确保了线程安全。doAppend方法体中同样会调用appender所关联的过滤器,同样是会拿到一个FilterReply来决定要不要放弃掉这个logging request。

但是我却进入了一个叫做UnsynchronizedAppenderBase的doAppend方法里,并没有看到synchronized块:

    public void doAppend(E eventObject) {
if(!Boolean.TRUE.equals(this.guard.get())) {
try {
this.guard.set(Boolean.TRUE);
if(this.started) {
if(this.getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
} this.append(eventObject);
return;
} if(this.statusRepeatCount++ < 3) {
this.addStatus(new WarnStatus("Attempted to append to non started appender [" + this.name + "].", this));
}
} catch (Exception var6) {
if(this.exceptionCount++ < 3) {
this.addError("Appender [" + this.name + "] failed to append.", var6);
} return;
} finally {
this.guard.set(Boolean.FALSE);
} }
}

doAppend

5. Formatting the output

被调用的appender负责格式化logging event。然而,有些(但不是全部)appender将格式化logging event的任务委托给一个layout。layout格式化LoggingEvent实例并以字符串的形式返回结果。注意,有些appender(比如SocketAppender)不会将logging event转换为字符串,而是序列化它。因此,它们既没有也不需要layout。

6. Sending out the LoggingEvent

logging event完全格式化后,由每个appender发送到其目的地。

贴张自己做的图:

Performance

详细参考原文末尾

The logback manual #02# Architecture的更多相关文章

  1. The logback manual #03# Configuration

    索引 Configuration in logback Automatically configuring logback Automatic configuration with logback-t ...

  2. The logback manual #01# Introduction

    依赖包如下pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=&qu ...

  3. 02 Architecture Overview

    本章提要---------------------------------------------arthiecture, and some componentconnect to oracle这一章 ...

  4. Logback相关知识汇总

    例如:%-4relative 表示,将输出从程序启动到创建日志记录的时间 进行左对齐 且最小宽度为4格式修饰符,与转换符共同使用:可选的格式修饰符位于“%”和转换符之间.第一个可选修饰符是左对齐 标志 ...

  5. Logback 专题

    logback-spring.xml <?xml version="1.0" encoding="UTF-8"?> <configuratio ...

  6. SLF4J user manual

    http://www.slf4j.org/manual.html The Simple Logging Facade for Java (SLF4J) serves as a simple facad ...

  7. SLF4J user manual 专题

    System Out and Err Redirected to SLF4J The sysout-over-slf4j module allows a user to redirect all ca ...

  8. SLF4简介

    The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logg ...

  9. SOFARPC源码解析-搭建环境

    文档地址:https://www.sofastack.tech 简介摘要 SOFA 是蚂蚁金服自主研发的金融级分布式中间件,包含构建金融级云原生架构所需的各个组件,包括微服务研发框架,RPC 框架,服 ...

随机推荐

  1. Auth认证模块

    1.auth简介 auth是什么? auth是django内置的用户认证系统,可以快速的实现:登录,注销,修改密码........ 2.autho的使用 1)先创建超级用户 python3 manag ...

  2. 339A

    #include <iostream> #include <string> #include <algorithm> using namespace std; #d ...

  3. iot-dm异常日志

    iot-dm 本机断网日志 2018-03-15 08:55:41,345 INFO com.inspur.iot.client.core.IotConnection Connection is be ...

  4. ntp开机无法自启

    1.1 查询ntp状态 [root@host-172-18-0-37 ~]# service ntpd status Redirecting to /bin/systemctl status ntpd ...

  5. Mongodb 基础 复制集原理和搭建

    数据复制原理 开启复制集后,主节点会在local库下生成一个集合叫 oplog.rs,这是一个有限的集合,即大小固定.这个集合记入了整个mongod实例一段时间内数据库的所有变更操作(如:增/删/改) ...

  6. Windows下解压分卷压缩方法

    各种压缩分卷格式 rar分卷格式是*.part1.rar,*.part2.rar 等等. 360分卷压缩出来的文件的名字是*.zip.001.*.zip002 等等. WinZip分卷压缩出来的文件名 ...

  7. Sql注入基础原理介绍

    说明:文章所有内容均截选自实验楼教程[Sql注入基础原理介绍]~ 实验原理 Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击, ...

  8. [LeetCode] 598. Range Addition II_Easy tag: Math

    做个基本思路可以用 brute force, 但时间复杂度较高. 因为起始值都为0, 所以肯定是左上角的重合的最小的长方形就是结果, 所以我们求x, y 的最小值, 最后返回x*y. Code    ...

  9. .net core 路由处理请求流程图

  10. PHP开启mysqli扩展

    Call to undefined function mysqli_connect()解决这个问题需要开启mysqli扩展开启mysqli扩展需要这两个步骤缺一不可1.在php.ini中搜索php_m ...