摘要
log4j是一个开源的工程,开发人员可以随意控制输出控制语句。它使用外部配置文件,可完全在运行时配置。极为重要的是它的学习周期极短。
介绍
几乎所有的大型应用程序都有自己的日志和追踪API。基于此,早在1996年,EU SEMPER项目决定写自己的追踪API。在无数的改进之后,它成为了java的一个流行的日志包log4j。这个包是Apache软件许可下发布的,它是一个非常成熟的开源许可证书。最新的log4j,包括源代码,class文件和文档,能在http://logging.apache.org/log4j/下载。顺便提一下,log4j已经支持C, C++, C#, Perl, Python, Ruby和Eiffel。
往代码里插入日志语句进行调试是非常低级的,但是也可能是唯一的方式,因为并不是总有调试器或者调试器不可用。多线程和分布式程序尤其如此。
经验表明,记录日志是开发过程中的一个非常重要的环节。它有几大优势。它能提供一个精确的应用程序运行的上下文。插入代码之后,生成日志输出就不需要人为干预了。并且日志输出能存储在持久化介质中以便日后研究。除了在开发中的用途之外,大量的日志也可当作审计工具。
尽管Brian W. Kernighan和Rob Pike在他们的优秀著作《编程实践》中指出:
就个人而言,我们倾向于不使用调试器进行堆栈跟踪或者获取一两个变量的值。原因之一是很容易在复杂的数据结构和控制流的细节中迷失;我们发现一步一步地走完程序没有深思熟虑之后在关键地方加上输出语句和自检查代码效率高。在语句上来回点击比扫视精心布置的打印输出要耗时。即使我们知道关键代码的位置,单步调试到那个地方也比放置打印语句要耗时。更重要的是,调试用的语句和程序在一起,而调试会话是瞬时的。
记录日志也有它的缺点。它会降低程序的运行速度。假如输出太详细,会导致屏幕闪动。为了缓解这些担忧,log4j设计得可靠、快速和可扩展。既然记录日志基本不是应用程序的主要关注点,log4jAPI致力于易于理解和使用。
Logger, Appender和Layout
Log4j有3个主要的部分:Logger, Appender和Layout。它们协同合作,使开发人员可以根据消息的类型和级别来记录日志并在运行时控制这些消息的格式和报告位置。
Logger层级结构
任何一个记录日志的API相比于普通的System.out.println的第一个也是最重要的优势在于禁用一些日志语句而不影响其他的语句。这种特性假设日志空间,也就是所有可能的记录日志语句的空间,是根据某些开发人员选定的标准分类的。这种结论以前让我们将类别作为包的中心概念。但是自从log4j版本1.2以来,Logger类已经替代了Category类。对于那些熟悉log4j以前版本的人来说,Logger类可被当作仅仅是Category类的一个别名而已。
Logger是命名的实体。它们的名字区分大小写,并遵守层级命名规则:
命名的层级体系
如果一个logger的名字加上后面的点是子logger名字的前缀,那么这个logger就称为那个logger的祖先。如果一个logger和子logger之间没有祖先logger,这个logger就称为那个logger的母logger。
例如,叫“com.foo”的logger是叫“com.foo.Bar”的logger的母logger。同样的,“java”是“java.util”的母logger,是“java.util.Vector”的祖先。这种命名方式大多数开发者都熟悉。
根logger在logger层级结构的顶端。它有两点特别:
-
它总是存在
- 它不能通过名字检索到
调用Logger.getRootLogger可以检索到它。所有其他的logger都是通过Logger.getLogger实例化和检索的。该方法用希望得到的logger的名字作为参数。Logger类的一些基本方法列举如下:
package org.apache.log4j;
public class Logger {
// Creation & retrieval methods:
public static Logger getRootLogger();
public static Logger getLogger(String name);
// printing methods:
public void trace(Object message);
public void debug(Object message);
public void info(Object message);
public void warn(Object message);
public void error(Object message);
public void fatal(Object message);
// generic printing method:
public void log(Level l, Object message);
}
Logger可以被赋予级别。可能的级别集合如下:
TRACE, DEBUG,INFO,WARN,ERROR和FATAL
它们定义在org.apache.log4j.Level类中。虽然我们并不鼓励这样做,但是你可以通过建立Level类的子类定义你自己的级别。稍后介绍一种更好的方式。
如果某个logger没有赋予级别,那么它继承最近的有级别的祖先的级别。正式的定义如下:
|
为了确保所有的logger能最终继承一个级别,根logger总是有一个赋予的级别。
以下是四张表,说明了各种赋予的级别值以及根据以上规则产生的继承的级别值:
例一
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | none | Proot |
X.Y | none | Proot |
X.Y.Z | none | Proot |
例一中,只有根logger赋予了级别。这个级别值,Proot,被其他的logger x, x.y和x.y.z继承。
例二
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | Px | Px |
X.Y | Pxy | Pxy |
X.Y.Z | Pxyz | Pxyz |
例二中,所有的logger都赋予了级别值。不需要级别继承。
例三
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | Pxyz | Pxyz |
例三中,root、x和x.y.z被分别赋予了级别值Proot、Px和Pxyz。logger x.y从它的母logger x继承了级别值。
通过调用logger实例的某一个打印方法发出记录日志请求。这些打印方法有debug, info, warn, error, fatal and log。
根据定义,打印方法决定了日志请求的级别。例如,如果c是一个logger实例,那么 c.info("..") 语句是一个级别为INFO的日志请求。
如果一个日志请求的级别高于或者等于logger的级别,那么称这个请求为启用的。否则为禁用的。规则总结如下:
|
这个规则是log4j的核心。它假定级别是有序的。对于标准的级别,我们有DEBUG < INFO < WARN < ERROR < FATAL。
如下是这个规则的一个例子:
// get a logger instance named "com.foo"
Logger logger = Logger.getLogger("com.foo");
// Now set its level. Normally you do not need to set the
// level of a logger programmatically. This is usually done
// in configuration files.
logger.setLevel(Level.INFO);
Logger barlogger = Logger.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");
用同一个名字调用getLogger方法总是返回对用一个logger对象的引用。
例如,在
Logger x = Logger.getLogger("wombat");
Logger y = Logger.getLogger("wombat");
x和y引用完全相同的logger对象。
因此,可以先配置一个logger,然后在代码中的某处检索同一个实例,而不用到处传递引用。与生物学亲子关系中父母总是先于子女出现根本矛盾的是,log4j中的logger可以以任意顺序创建和配置。特别是,即使母logger在它的子logger之后实例化,它仍然能够找到并连接到它们。
Appender和Layout
基于logger选择性地启用或禁用记录日志请求并不是全部。log4j允许日志请求将信息打印到多个地方。一个打印输出目的地在log4j里称为appender。目前,针对控制台、文件、GUI组件、远程socket服务器、JMS、NT事件Logger和远程UNIX Syslog守护进程。它也可以异步记录日志。
可以附加多个appender到同一个logger。
addAppender方法添加一个appender到一个给定的logger。一个给定的logger的每一个启用的记录日志请求都会转发到那个logger的所有appender和层次体系上更高的appender。换句话说,appender照着logger的层次体系被附加地继承了。例如,如果一个控制台appender被添加到根logger,那么所有启用的记录日志请求至少将在控制台打印出来。如果除了一个文件appender被添加到一个logger,假设为C,那么C和C的子logger的启用的记录日志请求将会打印在这个文件和控制台上。可以通过设置additivity flag为假,覆盖这种默认的行为,使appender的积聚不再是附加的。
|
下表是一个示例:
Logger Name | Added Appenders | Additivity Flag | Output Targets | Comment |
---|---|---|---|---|
root | A1 | 不适用 | A1 | 根logger是匿名的,但是能够通过Logger.getRootLogger()方法访问。没有默认的appender附加在根上。 |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | "x"和root的appender |
x.y | none | true | A1, A-x1, A-x2 | "x"和root的appender |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | "x.y.z", "x"和root的appender |
security | A-sec | false | A-sec | 既然additivity flag被设为了false,没有appender积聚 |
security.access | none | true | A-sec | 因为"security"中additivity flag被设为了false,所以只有"security"的appender |
-------------------------------------------------------精彩未完待续-------------------------------------------------------------