logback手册-第二章 架构

所有真实的分类都是族谱的。
苏格兰达尔文, 物种的起源

如果不是不可能,任何人都可以纯粹通过阅读来学习一个主题,而不将这些信息应用于具体的问题,从而强迫自己思考所读的内容。而且,我们都学习最好的东西,我们发现自己。
- 计算机程序设计艺术 - 唐纳
作者:CekiGülcü,SébastienPennec,Carl Harris 
版权所有©2000-2017,QOS.ch
 
Logback的架构
Logback的基本架构足够通用,以适应不同的情况。目前,logback分为三个模块:logback-core,logback-classic和logback-access。
所述 core 模块奠定了其它两个模块的基础。 classic 模块扩展 core 。classic模块对应于log4j的显着改进版本。Logback-classic本身实现了 SLF4J API, 因此您可以在Logback和其他日志记录系统(如JDK 1.4中引入的log4j或java.util.logging(JUL))之间来回切换。第三个称为 access的 模块集成了Servlet容器来提供HTTP访问日志功能。单独的文档涵盖了 访问模块文档
在本文的其余部分中,我们将编写“logback”来引用logback-classic模块。
Logger,Appenders和Layouts
Logback建立在三个主要的类上: Logger ,  Appender Layout 。这三种类型的组件协同工作,使开发人员能够根据消息类型和级别来记录消息,并在运行时控制这些消息的格式和报告位置。
Logger 班是的logback-classic模块的一部分。另一方面, Appender 和  Layout 接口是logback-core的一部分。作为通用模块,logback-core没有logger的概念。
Logger上下文
任何日志记录API优于普通的优势  System.out.println 在于它能够禁用某些日志语句,同时允许其他人无阻碍地进行打印。这种能力假定日志空间,即所有可能的日志语句的空间,按照一些开发人员选择的标准进行分类。在logback-classic中,这种分类是logger的固有部分。每一个logger都被连接到一个 LoggerContext 负责制造记录器的记录器上,并且将它们安排在层次结构树中。
Logger是命名实体。他们的名字是区分大小写的,他们遵循分层的命名规则:
命名层次结构
如果一个logger的名称后面跟一个点是后代logger名称的前缀,则称该logger是另一个logger的祖先。如果logger本身和后代logger之间没有祖先,则称该logger是子logger的父亲。
例如,名为 "com.foo"的logger 是名为 "com.foo.Bar" 的logger的父代。同样,  "java" 也是 "java.util"的 父亲和 "java.util.Vector"的 祖先。这个命名方案应该是大多数开发人员都熟悉的。
根logger驻留在logger层次结构的顶部。它在成立之初就是每一个等级的一部分。就像每个logger一样,它可以通过它的名字来检索,如下所示:
Logger rootLogger = LoggerFactory.getLogger( org.slf4j.Logger.ROOT_LOGGER_NAME );
所有其他logger也可以使用 org.slf4j.LoggerFactory  类中的类静态 getLogger 方法进行检索 。此方法将所需logger的名称作为参数。 下面列出了界面中的一些基本方法。 
package org.slf4j;
public interface Logger {

  // Printing methods:
  public void trace(String message);
  public void debug(String message);
  public void info(String message);
  public void warn(String message);
  public void error(String message);
}
有效级别又名级别继承
Logger可能被分配级别。 ch.qos.logback.classic.Level 课程中定义了一系列可能的级别(TRACE,DEBUG,INFO,WARN和ERROR) 。请注意,在logback中, Level 类是最终的,不能被分类,因为以 Marker 对象的形式存在更灵活的方法。
如果一个给定的Logger没有被赋予一个等级,那么它将从其最近的祖先中继承一个等级。更正式地说:
给定Logger L 的有效等级等于其层次结构中的第一个非空等级,从 L 本身开始, 并在层次结构中朝向根logger向上进行。
为了确保所有的logger最终能够继承一个级别,根logger总是有一个分配的级别。默认情况下,这个级别是DEBUG。
以下是根据级别继承规则具有各种分配的级别值以及由此产生的有效(继承)级别的四个示例。
例1
logger 名称
指定级别
有效的级别
root
DEBUG
DEBUG
X
none
DEBUG
X.Y
none
DEBUG
X.Y.Z
none
DEBUG
在上面的例子1中,只有根logger被分配了一个级别。这个级别值, DEBUG 被其他logger继承 X X.Y 并且 X.Y.Z
例2
logger 名称
指定级别
有效的级别
root
ERROR
ERROR
X
INFO
INFO
X.Y
DEBUG
DEBUG
X.Y.Z
WARN
WARN
在上面的例子2中,所有的logger都有一个分配的等级值。等级继承不起作用。
例3
logger 名称
指定级别
有效的水平
root
DEBUG
DEBUG
X
INFO
INFO
X.Y
none
INFO
X.Y.Z
ERROR
ERROR
另外,在上述实施例3中,logger root ,  X X.Y.Z 被分配了等级  DEBUG INFO ERROR  分别。记录器 X.Y 从其父项继承其级别值 X
例4
logger 名称
指定级别
有效的级别
root
DEBUG
DEBUG
X
INFO
INFO
X.Y
none
INFO
X.Y.Z
none
INFO
在上面的例子4中,logger root 和  X 和分别被分配了水平 DEBUG 和  INFO 。logger X.Y 并  X.Y.Z 从最近的 X 具有指定级别的父级继承它们的级别值。
打印方法和基本的规则选择
根据定义,打印方法确定记录请求的级别。例如,如果 L 是一个logger实例,则该语句 L.info("..") 是级别为INFO的记录语句。
如果记录请求的级别高于或等于其logger的有效级别,则说明该记录请求被 启用 。否则,该请求被认为是被 禁用的 。如前所述,没有指定级别的logger将从其最近的祖先继承。这条规则总结如下。
基本选择规则
如果 p> = q ,则向具有有效级别 q logger 发出的级别为 p的 日志请求被启用 。
这个规则是logback的核心。它假设层次的排列如下:  TRACE < DEBUG < INFO <  WARN < ERROR
更为直观地说,这里是选择规则的工作原理。在下表中,垂直标题显示记录请求的级别,由 p 指定,而水平标题显示logger的有效级别,由 q 指定。行(级别请求)和列(有效级别)的交集是基本选择规则产生的布尔值。
请求级别 p
有效的级别 q
TRACE
DEBUG
INFO
WARN
ERROR
OFF
TRACE
YES
NO
NO
NO
NO
NO
DEBUG
YES
YES
NO
NO
NO
NO
INFO
YES
YES
YES
NO
NO
NO
WARN
YES
YES
YES
YES
NO
NO
ERROR
YES
YES
YES
YES
YES
NO
这是一个基本的选择规则的例子。

import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
....

// get a logger instance named "com.foo". Let us further assume that the
// logger is of type  ch.qos.logback.classic.Logger so that we can
// set its level
ch.qos.logback.classic.Logger logger =
        (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
//set its Level to INFO. The setLevel() method requires a logback logger
logger.setLevel(Level. INFO);

Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");

// This request is enabled, because WARN >= INFO
logger.warn("Low fuel level.");

// This request is disabled, because DEBUG < INFO.
logger.debug("Starting search for nearest gas station.");

// The logger instance barlogger, named "com.foo.Bar",
// will inherit its level from the logger named
// "com.foo" Thus, the following request is enabled
// because INFO >= INFO.
barlogger.info("Located nearest gas station.");

// This request is disabled, because DEBUG < INFO.
barlogger.debug("Exiting gas station search");

检索Logger
调用 LoggerFactory.getLogger  具有相同名称的方法将始终返回对完全相同的logger对象的引用。
例如,在
Logger x = LoggerFactory.getLogger("wombat");
Logger y = LoggerFactory.getLogger("wombat");
x y 引用  完全相同 的logger对象。
因此,可以配置一个logger,然后在代码中的其他位置检索相同的实例,而无需传递引用。与亲生父母的根本矛盾,父母总是先于孩子,可以按任意顺序创建和配置登录logback。特别是,一个“父母”logger即使在它们后面被实例化,也会找到并链接到它的后代。
logback环境的配置通常在应用程序初始化时完成。首选的方法是读取配置文件。这个方法将很快讨论。
Logback通过 软件组件 可以很容易地给logger命名。这可以通过在每个类中实例化一个logger来完成,logger名称等于该类的完全限定名称。这是定义logger的有用和直接的方法。由于日志输出带有生成logger的名称,因此该命名策略可以轻松识别日志消息的来源。然而,这只是一种可能的,尽管是通用的命名logger的策略。Logback不限制可能的logger集合。作为一名开发人员,您可以根据自己的意愿自由选择logger名称。
尽管如此,在他们所在的class之后命名伐木者似乎是目前已知的最好的一般策略。
Appenders和Layouts
根据logger选择性地启用或禁用记录请求的能力只是很小一部分功能。Logback允许记录请求打印到多个目的地。在logback中,输出目标被称为appender。目前,控制台,文件,远程套接字服务器,MySQL,PostgreSQL,Oracle和其他数据库,JMS以及远程UNIX系统日志守护程序都存在appender。
可以将多个appender连接到记录器。
addAppender  方法将appender添加到给定的logger。给定logger的每个启用的记录请求将被转发给该logger中的所有appender以及层次结构中较高的appender。换句话说,appender是从logger层次继承的。例如,如果控制台appender被添加到根日志logger,则所有启用的日志记录请求将至少在控制台上打印。如果另外一个文件appender被添加到logger,说 L ,然后启用记录请求 L L 的孩子将打印在一个文件  在控制台上。可以重写此默认行为,以便通过将logger的可加性标志设置为false来使appender累积不再具有可加性。
以下总结了Appender可加性的规则。
Appender的可加性
记录器 L 的日志语句的输出将传递给 L 及其祖先中的所有appender 。这就是“appender 可加性”的意思。
但是,如果记录器 L 的祖先(比如说 P )的可加性标志设置为false,那么 L 的输出将被引导到 L中的 所有appender 和它的祖先直到并包括 P, 但不包括任何appender  P的 祖先。
记录器默认情况下将其可加性标志设置为true。
下表显示了一个例子:
Logger名称
附加的Appenders
可加性标志
产出目标
评论
root
A1
不适用
A1
由于根logger位于logger层次结构的顶部,因此可加性标志不适用于此logger。
X
A-x1,A-x2
true
A1,A-x1,A-x2
“x”和根的appenders。
X.Y
none
true
A1,A-x1,A-x2
“x”和根的appenders。
X.Y.Z
A-XYZ1
true
A1,A-x1,A-x2,A-xyz1
“xyz”,“x”和根的附属工。
security
A-sec
false
一秒
由于可加性标志设置为无appender累积  false 。只使用appender A-sec。
security.access
none
true
一秒
只有“security”的appender,因为“security”中的可加性标志被设置为  false
多数情况下,用户希望不仅自定义输出目标,而且还要自定义输出格式。这是通过将 layout 与appender 关联来实现的。该layout负责根据用户的意愿格式化记录请求,而appender负责将格式化的输出发送到其目的地。作为  PatternLayout 标准的logback分发的一部分,用户可以根据类似于C语言 printf  函数的转换模式来指定输出格式。
例如,带有转换模式“%-4relative [%thread]%-5level%logger {32} - %msg%n”的PatternLayout会输出类似于:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world。
第一个字段是程序启动以来经过的毫秒数。第二个字段是进行日志请求的线程。第三个字段是日志请求的级别。第四个字段是与日志请求关联的记录器的名称。' - '后面的文本是请求的消息。
参数化日志记录
鉴于logback-classic中 的logger 实现了 SLF4J的Logger界面 ,某些打印方法允许使用多个参数。这些打印方法的变体主要是为了提高性能,同时尽量减少对代码可读性的影响。
对于一些记录器 logger 来说,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
招致构造消息参数的成本,即转换整数两者 i entry[i]  为字符串,和连接中间字符串。这是不管消息是否被记录。
避免参数构建成本的一种可能的方法是通过测试围绕日志语句。这是一个例子。
if(logger.isDebugEnabled()) {
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
这样,如果禁用调试,则不会产生参数构建的成本 logger 。另一方面,如果logger启用了DEBUG级别,则会产生评估logger是否启用两次的成本:一次进入 debugEnabled 和一次进入  debug 。在实践中,这种开销是微不足道的,因为评估记录器的时间少于实际记录请求所花费的时间的1%。
更好的选择
基于消息格式存在一个方便的选择。假设 entry 是一个对象,你可以写:
Object entry = new SomeObject ();
logger.debug("The entry is {}.", entry);
只有在评估是否记录之后,并且只有当决定是肯定的时候,logger实现才会格式化消息并用字符串值替换“{}”对 entry 。换句话说,当日志语句被禁用时,这种形式不会产生参数构造的代价。
以下两行将产生完全相同的输出。但是,如果 禁用了 日志记录语句,则第二个变体的性能将比第一个变体至少高出30倍。
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
两个参数变体也是可用的。例如,你可以写:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
如果需要传递三个或更多个参数, Object[] 则也可以使用一个 变体。例如,你可以写:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
引擎盖下窥视
在我们引入了重要的logback组件之后,我们现在准备好描述logback框架在用户调用logger的打印方法时所采取的步骤。现在让我们来分析当用户调用 info() 一个名为 com.wombat 的logger的方法时logback所要采取的步骤 。
1.获取过滤器链决定
如果存在,则 TurboFilter 链被调用。涡轮过滤器可以设置一个上下文范围的阈值,或过滤掉基于诸如信息的某些事件 Marker ,  Level Logger ,消息,或  Throwable 与每个日志记录请求相关联。如果过滤器链的回复是  FilterReply.DENY ,则记录请求被丢弃。如果是 FilterReply.NEUTRAL ,那么我们继续下一步,即第二步。如果答复是  FilterReply.ACCEPT ,我们跳过下一步,直接跳到第三步。
在此步骤中,logback将logger的有效级别与请求的级别进行比较。如果根据此测试日志记录请求被禁用,则logback将丢弃该请求而不做进一步处理。否则,就进入下一步。
3.创建一个 LoggingEvent 对象
如果请求在先前的logger中存活,logback将创建一个 ch.qos.logback.classic.LoggingEvent 包含请求的所有相关参数的对象,例如请求的logger,请求级别,消息本身,可能已经与请求一起传递的异常,当前时间,当前线程,关于发出日志请求的类的各种数据以及 MDC 。请注意,这些字段中的一部分只是在实际需要时才被初始化。将 MDC 被用来装饰用额外的上下文信息的日志记录请求。MDC将在 下一章 讨论。
4.调用appenders
创建 LoggingEvent 对象后,logback将调用 doAppend() 所有适用appender 的方法,即从logger上下文继承的appender。
所有随logback发行版一起提供的appender都会将 AppenderBase 实现此 doAppend 方法的抽象类 扩展到 同步块中,以确保线程安全。如果存在任何这样的过滤器,该 doAppend() 方法  AppenderBase 也调用附加到附加器的自定义过滤器。可以动态连接到任何appender的自定义过滤器将在 单独的章节中介绍
5.格式化输出
被调用的appender负责格式化记录事件。但是,一些(但不是全部)appender将格式化记录事件的任务委托给布局。布局格式化 LoggingEvent 实例并以String 形式返回结果。请注意,某些appender(例如  SocketAppender ,)不会将日志记录事件转换为字符串,而是将其序列化。因此,他们没有也不需要layout。
6.发送  LoggingEvent
记录事件完全格式化后,每个appender将其发送到目的地。
这是一个序列UML图,以显示一切工作。您可能想点击图片来显示更大的版本。
性能
经常引用的反对记录日志的论据之一是其计算成本。这是一个合理的问题,因为即使是中等规模的应用程序也可能产生数千个日志请求。我们的大部分开发工作都花费在测量和调整logback的性能上。独立于这些努力,用户仍应该意识到以下性能问题。
1.记录完全关闭时的记录性能
您可以通过将根记录器的级别设置 Level.OFF 为最高级别来完全关闭日志记录。当日志记录完全关闭时,日志请求的开销包括方法调用和整数比较。在3.2Ghz Pentium D机器上,这个成本通常在20纳秒左右。
但是,任何方法调用都涉及参数构建的“隐藏”成本。例如,对于某些记录器的 x  书写,
x.debug("Entry number: " + i + "is " + entry[i]);
引发构造消息参数的成本,即转换整数二者 i 并  entry[i] 为一个字符串,和连接中间的字符串,而不管该消息是否将被记录或没有。
参数构建的成本可能相当高,并且取决于所涉及参数的大小。为了避免参数构建的成本,您可以利用SLF4J的参数化日志:
x.debug("Entry number: {} is {}", i, entry[i]);
这个变体不会产生参数构造的代价。与之前的 debug() 方法调用相比, 速度会更快。只有当记录请求被发送给附属的appender时,才会格式化消息。而且,对消息格式化的组件进行了高度优化。
尽管上述将日志语句置于严格的循环中,即非常频繁地被调用的代码是一个双输入的提议,可能会导致性能下降。即使日志记录已关闭,记录紧密的循环也会减慢应用程序的运行速度,并且如果打开日志记录,将会产生大量(因此无用的)输出。
2.打开日志记录时决定是否记录日志的性能。
在logback中,不需要走记录器层次结构。记录器在创建时知道其有效等级(即其等级,一旦考虑了等级继承)。如果父记录器的等级被改变,则联系所有的儿童记录器以注意改变。因此,在接受或拒绝基于有效水平的请求之前,记录器可以做出准瞬时决定,而不需要咨询其祖先。
3.实际记录(格式化和写入输出设备)
这是格式化日志输出并将其发送到目标目标的成本。在这里,我们再次认真地努力使layout(格式化程序)尽可能快地执行。appenders也是如此。登录到本地计算机上的文件时,实际记录的典型成本大约为9到12微秒。登录到远程服务器上的数据库时,上升到几毫秒。
虽然功能丰富,但logback的首要设计目标之一是执行速度,这是仅次于可靠性的要求。一些logback组件已经被重写了好几次以提高性能。

猜你喜欢

转载自blog.csdn.net/sjzylc/article/details/78899927