日志系统的设计模式

从事服务器程序开发的工程师们,几乎不可避免地需要使用到日志系统(Logging System)。

如果从头开发一个服务器程序,我们有若干开源的日志系统可供选择,例如:Java语言中,Log4j是个不错的选择;Python语言中,有logging类;C语言中,有:Log4c、libqb等。

这些开源的日志系统,几乎都使用到相同的设计模式,不同之处在于实现语言、实现方法,以及附加的一些额外功能。今天,我想介绍一下这个共通的设计模式,这会有助于我们用好这些开源的日志系统,或改进它们。

这些日志系统,包含三个要素:

  1. content: 日志的内容,通过在代码中调用日志接口,如log_printf("xxx"),向日志系统传递内容
  2. target: 日志的目标,即日志写到什么地方,例如可以同时写到stderr/syslog/xxx.log
  3. filter: 日志过滤规则,用于决定每一条日志能否输出到某个目标上

一个日志系统,可以定义多个日志目标;每个日志目标,有属于它自己的若干条过滤规则。

当日志系统,通过日志接口,接收到一条日志内容后(content),它做以下处理:

for t in targets; {
	pass = False;
	for f in t.filters; {
		if content satisfies f; {
			pass = True;
			break;
		}
	}
	
	if (pass) {
		print content to t;
	}
}

以上是伪代码,意思是,当日志系统收到一条日志,它会循环遍历所有target,并判断这条日志是否满足这些target的某条filter,如果满足则把日志内容输出到这个target上。

因为每条日志都需要经过这些步聚,所以日志系统的一个关键点,就是性能的优化问题。

从伪代码来看,外层的for循环是无法省略的。但内层的for循环,就是遍历t.filters的这一层循环,开源界的朋友们想到了一个好方法,可以把它优化为一个简单的条件判断。

他们是如何做到的呢?在libqb中,方法是这样的:

  1. 为每条日志,关联一个static的结构体,这个结构体定义在一个自定义的section中,这个结构体保存了这条日志的文件名、行号、日志优先级别等信息。
  2. 程序在启动时,通过扫描这个自定义的section,就可以获得所有日志的相关联的结构体信息,通过*预先*把这些结构体的信息与所有target的各自的filters进行匹配,从而可以计算出这些日志可以输出到哪些target上,然后在结构体中打上标志。
  3. 通过个“预处理”的过程,在程序运行时,就不必再进行日志与filter的匹配,直接通过相关联的结构体的标志位,即可判断能否输出到该target上。
展开阅读全文

没有更多推荐了,返回首页