初学 LOGBack,内容来自 LOGBack Chapter2: Architecture

包粒度级别继承

下级的包若无特定设定日志级别,则会继承上层包的日志级别。

如下表所示:

日志器(包名)分配日志级别生效日志级别
rootWARNWARN
cn.tripleznoneWARN
cn.triplez.demoDEBUGDEBUG

日志级别优先级

TRACE < DEBUG < INFO < WARN < ERROR

获取日志器

多次获取相同名称的日志器则会返回相同对象的引用。

Appenders and Layouts

在日志中插入变量值

假如你想写一条调试日志,如下:

1
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

这样的代码运行效率是很低的,因为该语句会先拼接日志字符串,再进行是否符合 DEBUG 层级的判断。因此,你可以写成如下形式以提高运行速度:

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

更好地方法

事实上,在 LOGBack 中提供了更好的拼接方法,其能够基于模板填充变量。需要填充的变量只需在模板中用 {} 代替即可,而且该方法有很好的性能1,它能够先判断日志层级再进行字符串的处理。举个栗子:

1
logger.debug("Hello, {}!", name);

也可以写多个变量:

1
logger.debug("Hello, {}! I'm {}.", name, yourName);

如果有很多个变量,也可以使用 Object[] 来整合:

1
2
Object[] paramArray = {val1, val2, val3};
logger.debug("Value 1: {}, Value 2: {}, Value 3: {}", paramArray);

一瞥内部原理

时序图:

性能

完全关闭日志记录功能

当日志功能在设置文件中被设置为 Level.OFF, 日志功能会被关闭。但是,写在程序中的 log 语句仍会产生一定的开销,主要是方法调用和整数比较。不过这个开销非常之小,我们完全不用担心。

不过,在某些写法之下,方法调用会产生隐藏的变量构建的开销,这一点在前面已经提过,举个栗子:

1
x.debug("Entryu number: " + i + "is " + entry[i]);

如上的写法会产生额外的字符串拼接成本,其需要将 ientry[i] 都转换成为字符串,并将四个字符串对象拼接起来。而这个过程是与有效层级无关的,也就是说,无论是否满足输出该条日志的条件,都会进行字符串的拼接操作。

为了避免产生这种额外的运行开销,提高程序运行效率,我们可以使用 SLF4J 的变量化日志记录接口:

1
x.debug("Entry number: {} is {}", i, entry[i]);

这种变体写法不会产生额外的变量构建成本,日志信息只有该日志请求被发送到追加器(即满足记录要求及输出层级)之后才会被格式化,并且该方法经过了优化处理,能够很大程度提高运行效率。

不建议在循环中使用日志记录,即使不记录日志也会产生额外的开销,记录了日志也会输出大量的重复无用信息。

日志器在检查日志层级的性能

在 LOGBack 中,日志器不需要根据继承关系去寻找自己真正所对应的记录层级,在日志器被创建之后,继承关系就会确定并且确定了该日志器自身的记录层级。并且更改了某个父记录器的记录层级之后,相对应的子记录器都会被发送请求以更改记录层级。因此,在比较日志层级时,日志器能够做到准即时的决策,而不需要在此处去请求父记录器才能做出决策。

日志输出

日志真正被输出到文件或流中是开销最大越是最为费时的操作。从日志开始记录到写入文件系统中的典型开销是 9 到 12 毫秒,若为写到远端数据库或服务器会再增加几毫秒的延时。尽管 LOGBack 的功能非常丰富,但是 LOGBack 的核心依然是保证运行效率,开发者们已多次为了得到更好地性能重构 LOGBack 组件。


  1. 官方指出对比直接拼接字符串的方法能够至少提高 30% 的性能。 ↩︎


知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。