1. 概述
本文主要描述Log4j的API的唯一特性和它的设计原理。Log4j是一个基于许多作者的开放源码的项目。它允许开发员以任意的间隔来控制日志的输出。它通过设在外部的配置文件而达到运行时灵活的设置。最重要的是,Log4j有一个平稳的学习曲线。注意:根据来自用户的反馈判断,它很容易使人上瘾。
2. 导言
几乎所有的大型应用程序都包括它的自己的日志和跟踪API。顺应这个规则,E.U. SEMPER 项目决定写它自己的跟踪PAI。这是1996年初。在无数次加强,几次变形和许多工作后,那个API变成了如今的Log4j,一个流行的java日志包。这个包以Apache Software License协议发布,一个成熟的开放源吗协议。最新的Log4j版本,包括全部的源码,class文件和文档,你可以在http://jakarta.apache.org/Log4j/上找到。顺便,Log4j已经给C, C++, C#, Python, Ruby, and Eiffel 语言都提供了接口。
为了调试而插入日志输出到代码里是一个低技术成分的方法,但它可能也是唯一的方法,因为调试器并不是一直可用或者可以适应的,尤其对于多线程的分布使式的大型程序而言。
经验指出调试是软件开发周期中一个重要的组成部分。
Log4j拥有几个优点:
首先,它提供关于运行程序的准确的环境。一旦代码被插入,不需要人工干预就可以产生调试信息。
其次,日志输出可以被有计划的保存在永久媒体中以便日后研究。
另外,除了在开发周期中,一个充分详尽的日志包可以被用来作为以后的统计工具。
Log4j当然还有它的缺点,它可能减慢程序。如果太详细,它可能导致屏幕盲目滚动。排除这些情况,Log4j是可靠的,快速的,可以扩展的。因为日志很少是一个应用程序的主要目的, 设计者们正努力使得Log4j API学习和使用简单化。
3. 日志类别、输出源和布局
Log4j有三个主要的组件:日志类别(Loggers)、输出源( Appenders)和布局(Layouts)。这三种类型的组件一起工作使得开发员可以根据信息的类型和级别记录它们,并且在运行时控制这些信息的输出格式和位置。
3.1 日志类别的层次结构(Loggers)
Log4j首要的相对于简单的使用System.out.println()方法的优点是基于它的在禁止一些特定的信息输出的同时不妨碍其它信息的输出的能力。这个能力源自于日志命名空间,也就是说,所有日志声明的空间,它根据一些开发员选择的公式而分类。以前的观察引导我们选择类别作为包的中心概念。然而,自从Log4j的1.2版本,Logger类被Catalog类所取代,对于那些熟悉Log4j以前版本的人来说,Logger类可以被想象成仅仅是Category 类的别名。
Loggers 被指定为实体,Logger的名字是大小写敏感的,它们遵循以下的命名
规则:
² 命名继承
如果类别的名称(后面加一个点)是其子类别名称的前缀,则它就是另一个类别的祖辈。
如果一个类别(Logger)和它的子类别之间没有其它的继承关系,我们就称之为parent与child的关系。
例如,类别"com.foo"是类别"com.foo.Bar"的parent。相似的,"java"是"java.util"的parent,是"java.util.Vector"的父辈。.这个命名规则应该被大多数的开发员所熟悉。
根(root) 类别位于logger继承结构的最上层。它有两种例外:
1.它一直存在
2.它不能根据名称而获得。
调用类的静态方法Logger.getRootLogger可以得到它。其它所有的Logger可以通过静态方法Logger.getLogger而得到它们自己的实例。这个方法取希望的Logger名作为参数。Logger的一些基本的方法示例如下:
package org.apache.Log4j;
public Logger class {
// Creation & retrieval methods:
public static Logger getRootLogger();
public static Logger getLogger(String name);
// printing methods:
public void debug(Object message);
public void info(Object message);
public void warn(Object message);
public void error(Object message);
// generic printing method:
public void log(Level l, Object message);
}
Loggers可以被分配的级别。所有级别的集合包括:
DEBUG
INFO
WARN
ERROR
FATAL
它们被定义于org.apache.Log4j.Level 类。虽然我们不鼓励,但是你们可以通过继承Level类来定义你们自己的级别。我们随后将介绍一个比较好的方法。
如果一个Logger没有被分配一个级别,那么它将从一个被分配了级别的最接近它的ancestor哪里继承。
正规的说:
² 级别继承
对于一个给定的Logger C,它的继承的级别等于从C开始上溯到的第一个拥有非空级别的Logger的级别。
为了保证所有的Logger最终能够继承到一个级别,根Logger通常有一个已经定义了的级别。
以下四个表中的数据演示了根据以上规则得到的结果。
类别名 |
分配的级别 |
继承的级别 |
root |
Proot |
Proot |
X |
none |
Proot |
X.Y |
none |
Proot |
X.Y.Z |
none |
Proot |
Example 1 |
在例子1中,只有根Logger定义了一个级别,它的级别的值--"Proot"被所有其它的Loggers X, X.Y, 和X.Y.Z所继承。
类别名 |
分配的级别 |
继承的级别 |
root |
Proot |
Proot |
X |
Px |
Px |
X.Y |
Pxy |
Pxy |
X.Y.Z |
Pxyz |
Pxyz |
Example 2 |
在例子2中,所有的Logger都有一个被分配的级别值,所以它们不需要级别继承。
类别名 |
分配的级别 |
继承的级别 |
root |
Proot |
Proot |
X |
Px |
Px |
X.Y |
none |
Px |
X.Y.Z |
Pxyz |
Pxyz |
Example 3 |
在例子3中,根Logger,以及X和X.Y.Z被分别分配了级别Proot,Px和Pxyz。Logger X.Y从它的parent X继承了级别值Px。
类别名 |
分配的级别 |
继承的级别 |
root |
Proot |
Proot |
X |
Px |
Px |
X.Y |
none |
Px |
X.Y.Z |
none |
Px |
Example 4 |
在例子4中,根Logger和X被分别分配了级别"Proot"和"Px",Logger X.Y 和 X.Y.Z从被分配了级别的最接近它们的ancestor X那里得到继承。
我们需要通过调用Logger的输出的实例方法之一来实现日志请求。这些输出的方法是debug, info, warn, error, fatal 和 log.
通过定义输出方法来区分日志的请求的级别。例如,如果c是一个Logger的实例,那么声明 c.info 就是一个INFO级别的日志请求。
如果一个日志的请求的级别高于或等于日志的级别那么它就能被启用。反之,将被禁用。一个没有被安排级别的Logger将从它的父辈中得到继承。这个规则总结如下。
² 基本的选择规则
假如在一个级别为q的Logger中发生一个级别为p的日志请求,如果p>=q,那么请求将被启用。
这是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 progamitcally. This is usually done
// in configuration files.
cat.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对象的实例。
例如,
Categoty x = Logger.getLogger("wombat");
Categoty y = Logger.getLogger("wombat");
x和y参照的是同一个Logger对象。
这样我们就可以先定义一个Logger,然后在代码的其它地方不需传参就可以重新得到我们已经定义了的Logger的实例.
同基本的生物学理论--父先于子相反,Log4j 的loggers可以以任何顺序创造和配置。特别是,一个后实例化的"parent"logger能够找到并且连接它的子logger。
配置Log4j的环境通常在一个应用程序被初始化的时候进行,最好的方法是通过读一个配置文件。这个方法我们将简短介绍。
Log4j使得通过软件组件命名logger很容易。我们可以通过Logger的静态的初始化方法在每一个类里定义一个logger,令logger的名字等于类名的全局名,而实现logger的命名。这是一个实效的简单的定义一个logger的方法。因为日志输出带有产生日志的类的名字,这个命名策略使得我们更容易定位到一个日志信息的来源。虽然普通,但却是命名logger的常用策略之一。Log4j没有限制定义logger的可能。开发员可以自由的按照它们的意愿定义logger的名称。
然而,以类的所在位置来命名Logger好象是目前已知的最好方法。
3.2 输出源(Appenders)和布局(Layouts)
有选择的能用或者禁用日志请求仅仅是Log4j的一部分功能。Log4j允许日志请求被输出到多个输出源。用Log4j的话说,一个输出源被称做一个Appender. 。Appender包括console(控制台), files(文件), GUI components(图形的组件), remote socket servers(socket 服务), JMS(java信息服务), NT Event Loggers(NT的事件日志), and remote UNIX Syslog daemons(远程UNIX的后台日志服务)。它也可以做到异步记录。
一个logger可以设置超过一个的appender。
用addAppender 方法添加一个appender到一个给定的logger。对于一个给定的logger它每个生效的日志请求都被转发到该logger