logback课程(2) Logback结构详解

logback教程(2) Logback结构详解

长话短说:LogBack的结构

为了适用不同的环境,logback的基础结构符合常规。

logback分为三个模块:

logback-core,logback-classic以及logback-access。

1 >核心模块(core)为其他两个模块提供基础。

2 >classic模块继承自core。classic模块很明相当于log4j的增强版。

3 >Logback-classic原生的继承自SLF4J API因此你可以很容易的在LogBack和其他像日志系统比如log4j或java.util.logging来回切换。第三个模块access结合Servlet容器来提供HTTP-access日志功能,这个会有专门的文章来介绍。

在下文里,我们会写logback-classic模块的“lockback”。



Logger,Appender,和Layouts

Logback建立于三个重要的类:Logger,Appender和Layout。这个三个类同心协力让开发者能根据信息的类型和级别来打印日志,并能控制日志的输出格式和输出目的地。

一方面,“Logger”类属于logback-classic模块。另一方面,“Appender”和“Layout”接口属于logback-core模块。作为通用模块,logback-core并没有定义日志类。


Logger context

对于任何日志的API首要的优点那就是,相对普通的System.out.println,要有控制或屏蔽不同日志的输出的能力。

这种能力是根据开发者的选择,对不同的日志空间来分类的。

在logback-classic这个分类是logger固有的。LoggerContext用来像树的层次一样构建Logger,从而每一个单独的Logger都绑定到一个LoggerContext上。

Logger被称为实体(entity),他们的命名对区分小写并遵循如下命名规则:

    命名规则:

     一个Logger如果点号后面的名字是另一个子孙Logger的前缀,这个Logger就是另一个Logger的的父级。


举个栗子:

        "com.foo"Logger是 "com.foo.Bar“Logger的父级。类似的,"java"是的 "java.util" 父级,是"java.util.Vector"的祖先级。这种命名方案大多数开发者非常的熟悉。


根Logger位于Logger的最高层,是每一个Logger书的顶层。通每一个logger一样,它可以像下面这样通过名字来获得(retrieved)。

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

所有其他的loggers同样用org.slf4j.LoggerFactory的静态方法getLogger来获取,这个方法的参数是logger的名。

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); 
}


Log的级别
Logger可以分级别。可选的级别在ch.qos.logback.classic.Level类里定义,一共有TRACE, DEBUG, INFO, WARN and ERROR这些级别。
注意,在logback,Level类是final的,不可继承,以标记类的形式提供了灵活性。
如果一个Logger没有分配一个level,它会继承离它最近的祖先所分配的等级。
更正式的表述:
                     对于一个Logger L,其等级是它所在继承树里第一个不为空的level,从它本身开始到根Logger结束.
为了保证所有的Logger最终都绑定一个level,根Logger(root Logger)一定绑定一个level,默认是”DEBUG“

下面是根据level的继承规则,不同的level绑定后,最终生效的level
例子1
Logger name Assigned level Effective level
root DEBUG DEBUG
X none DEBUG
X.Y none DEBUG
X.Y.Z none DEBUG
例子1可以看出,只有root分配了一个level。其level值为DEBUG,被其他logger X, X.YX.Y.Z继承。
例子2
Logger name Assigned level Effective level
root ERROR ERROR
X INFO INFO
X.Y DEBUG DEBUG
X.Y.Z WARN WARN
例子2里,所有的Logger都分配了一个level,则未出现继承level的情况。
例子3
Logger name Assigned level Effective level
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z ERROR ERROR
Logger root,x,以及x.y.z 被分别分配了级别为DEBUG,INFO和ERROR。Logger x.y和x.y.z从让他们最近的分配了level的祖先x那里继承了level。
例子4
Logger name Assigned level Effective level
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z none INFO
在例子4中,Logger root和x分别分配了level 为DEBUG和INFO。Logger x.y和x.y.z从离他们最近的分配了level的x那里继承了level。
打印方法以及选择打印的规则
按照定义,打印方法决定了打印请求的级别。比如说,如果L为一个Logger实例,则L.info("")便是一个INFO级别的打印。
如果日志打印请求的级别高于Logger设置的生效级别,则打印请求有效。反之则不。如前所述,一个没有分配level的Logger将继承离它最近的设置了level的祖先的level。此规则总结如下:
打印选择规则
对于一个生效level为Q的Logger,处理一个level为P的日志打印请求,如果P>=Q,则打印生效。

这条规则是logback的核心,它规定的级别顺序如下:

TRACE < DEBUG < INFO < WARN < ERROR.

用一个更图像化的方式,来说明打印选择的规则是如何起作用的,在下表,列标题是日志打印请求的级别,设为P,行标题为Logger的生效级别,设为Q。其它和表格内容为对应的打印选择的结果。

level of 
request p
effective level 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");


运行结果为:
17:33:51.028 [main] WARN  com.foo - Low fuel level.
17:33:51.030 [main] INFO  com.foo.Bar - Located nearest gas station.



获得 Logger
以同样的名称触发LoggerFactory.getLogger方法永远会返回同一个Logger实例。
例如,在
Logger x = LoggerFactory.getLogger("wombat"); 
Logger y = LoggerFactory.getLogger("wombat");
x和y实际上是同一个Logger实例。

因此,
如果在代码的其它地方没有传递引用,创建的Logger也可能是同一个实例。生物学遗传的规律是父代总是先于他们的子孙出现,但logback与之相反。logback可以在以任何顺序被创建。
特别的,一个“父级”Logger即使后于子孙被创建,也将会发现并关联它自己的子孙。

logback的配置在程序开始的时候初始化。首选的方法是读取配置文件,这个方式我会简单说明。
logback使得命名Logger简单化。可以在每个类里实例化Logger的时候,通过将每个类的全名作为Logger的名字来完成。
这个方法来定义Logger有效、直接。输出日志的时候,这种命名直接将标识一个日志的来源简单化。然而,这只是一种可行的,虽然比较常见的,直接的命名策略。
logback没用限制Logger的产生组合。作为一个开发者,你可以随心所欲的去命名Logger。

不过,根据类的位置来命名Logger是目前为止已知的最好的惯用策略了。

Appenders and Layouts

让Logger有选择的使日志打印请求能否放行的能力只是冰山一角。Logback循序日志请求打印到多重目的地。
在logback里,一个日志的输出目的地称为Appender。
目前,Appender可以是控制台, 文件,远程socket服务器,MySQL, PostgreSQL, Oracle和其他数据库以及JMS, 和远程的UNIX Syslog进程(UNIX Syslog daemons)

一个Logger可以绑定多个Appender。
addAppender方法可以添加一个Appender到一个Logger。
每一个放行的打印日志请求会指向所在Logger所有的Appender,同时Appender的层级比较高。换言之,Appender的继承附属于logger的的层级。
例如,如果一个控制台Appender添加到得了root Logger(根Logger),那么所有放行的日志打印请求将至少会打印在控制台上。如果另外一个文件Appender添加到了一个Logger,我们设它为L,随之L和L的子孙放行的
日志打印请求将会打印到文件盒控制台。
也可以通过设置additivity flag为false来覆盖它的默认设置让Appender的添加不再是全部累加。


控制Appender 增加的规则总结如下:
Appender增加
一个日志L的输出将会到达所有L以及L的祖先的Appender,我们称之为"Appender的积累性"
然而,如果L的一个祖先,我们设为P,将additivity flag 设置为false,这样L的输出将会到达所有L以及L祖先的Appender包括P但是除了P的祖先的Appender。
additivity flag默认为true。

例子如下面的表
Logger Name Attached Appenders Additivity Flag Output Targets Comment
root A1 not applicable A1 Since the root logger stands at the top of the logger hierarchy, the additivity flag does not apply to it.
x A-x1, A-x2 true A1, A-x1, A-x2 Appenders of "x" and of root.
x.y none true A1, A-x1, A-x2 Appenders of "x" and of root.
x.y.z A-xyz1 true A1, A-x1, A-x2, A-xyz1 Appenders of "x.y.z", "x" and of root.
security A-sec false A-sec No appender accumulation since the additivity flag is set to false. Only appender A-sec will be used.
security.access none true A-sec Only appenders of "security" because the additivity flag in "security" is set to false
与往往不同的是,我们不仅是想制定日志的输出目的地,而且还想制定它的格式。
这就需要将一个layout绑定一个Logger来完成。
layout负责根据用户的意愿来格式化日志打印请求的输出。而Appender则负责将格式化的输出送到目的地。

PatternLayout作为logback的标准组成,使得用户根据匹配( Pattern )指定输出格式,就像C语言的printf方法。
比如说,PatternLayout具有这样的匹配 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 将会输出如下结果:
176  [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一个字段是从程序开始的的毫秒。
第二个字段是日志发出打印请求的线程。
第三个字段是日志打印请求的级别。
第四个字段是日志请求相关的Logger名。
'-'后面是的日志打印的信息

参数化的日志输出

logback给定的Logger继承自SLF4J's Logger interface,某个打印方法可以有不止一个参数。
这些打印方法的变体主要是在兼顾代码可读性的同时提高性能。

对于某个Logger,这样写,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
不管这条信息是否被放行,最终为了构建参数信息,将两个“i”以及“entry[i]”组合成一个字符串,以及连接字符串,产生了消耗。
有一个避免参数构建消耗的方法是在日志打印区域,用一个test。
例子如下:
if(logger.isDebugEnabled()) { 
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}