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

全文注释:

  • 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. c 字符数组与字符串

    char a[]="abc"; printf( char str[]="abc"; int size = sizeof(str)/sizeof(char); p ...

  2. DLNg[结构化ML项目]第二周迁移学习+多任务学习

    1.迁移学习 比如要训练一个放射科图片识别系统,但是图片非常少,那么可以先在有大量其他图片的训练集上进行训练,比如猫狗植物等的图片,这样训练好模型之后就可以转移到放射科图片上,模型已经从其他图片中学习 ...

  3. Python才排第8名!2018增速最快TOP 10编程语言盘点

    在技术前沿的硅谷,开发者们不仅要学习多种热门的编程语言,还要时时盯着新的编程语言的崛起,生怕自己掉队. 作为世界最大开源软件社区,Github每年都会发布年度Octoverse报告,向大家展示年度最流 ...

  4. 22-Python3 输入和输出

    ''' 输出格式美化 ''' s = 'Hello,Runoob' #repr():将输出读值转化成字符串,产生一个解释器易读读字符 print('repr():',repr(s)) #str():将 ...

  5. trie字典树

    ---恢复内容开始--- 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1251 #include <bits/stdc++.h> usin ...

  6. react 写一个贪吃蛇

    示例: 全部代码如下: snake.jsx import React, { Component } from 'react'; import PropTypes from 'prop-types'; ...

  7. reveal破解

    ~/Library/Preferences/com.ittybittyapps.Reveal.plist 把IBAApplicationPersistenceData这一项删除就是了.”

  8. Appium基础(二)demo

    具体配置,请看上一篇 一.启动Android模拟 Start 打开如下界面:这个过程可能会有些慢,黑屏等等,请耐心等待 二.appium配置 1.点机器人图标 PlatformVersion 选19, ...

  9. Ecshop表结构 order_info

    CREATE TABLE IF NOT EXISTS `ecs_order_info` (  `order_id` mediumint(8) unsigned NOT NULL AUTO_INCREM ...

  10. ajax提交完表单数据依然跳转的解决办法

    1. 既然ajax提交数据,就把表单里面submit按钮换掉,因为触发submit他就会跳转页面 提交的时候他会先触发ajax 再触发submit的提交 2.如果确定了表单没有submit,那么把提交 ...