`

使用Log4j进行日志记录

阅读更多
发布日期:2006年07月30日,更新日期:2006年07月30日
1996年初,欧洲安全电子市场(EU SEMPER)项目组决定编写自己的日志记录API,后来这个API演变成了Log4j。Log4j是一个开放源码项目,一个非常流行的Java日志记录包。它允许开发者向代码中插入日志记录语句,还允许在不修改应用程序源码的情况下修改记录日志的行为。

1996年初,欧洲安全电子市场(EU SEMPER)项目组决定编写自己的日志记录API,后来这个API演变成了Log4j。Log4j是一个开放源码项目,一个非常流行的Java日志记录包。它允许开发者向代码中插入日志记录语句,还允许在不修改应用程序源码的情况下修改日志记录的行为。

几乎每一个项目都会使用日志记录,但是由于日志记录不是项目的核心,因此受重视的程度一般不是很高。我们认为使用日志记录是一件非常严肃的事情,而且做好使用日志记录的规划比单纯记录日志本身更加重要。

本文将比较全面的阐述Log4j的设计原理和使用方法。

日志记录

日志记录记录的是应用程序运行的轨迹。我们可以通过查看这些轨迹来调试应用程序,这可能也是日志记录最为流行的用法了。但是我们必须意识到规划良好的日志记录中还含有丰富的信息,通过手工的方式或借助一些工具(大多数时候需要自己来书写这些工具)来分析挖掘这些信息。

例如,如果我们在规划中指出必须记录用户的每一次操作,记录的样式为 [日志信息]-[操作开始的时间]-[日志级别]-[日志类别]-[用户名]-[操作名]-[消息],这只是我们假设的一种样式,实际的日志中一般会含有比这更加丰富的信息。为了更好的理解,我们根据该样式构造了一些日志记录(其中日志类别org.solol.Main、org.solol.Parser和org.solol.UserOperator使用了不同的样式):

[日志信息]-[2006-07-30 08:54:20]-[INFO]-[org.solol.Main]-[具体的消息]
[日志信息]-[2006-07-30 08:55:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查询报表1]-[具体的消息]
[日志信息]-[2006-07-30 08:55:30]-[INFO]-[org.solol.UserOperator]-[User1]-[查询报表2]-[具体的消息]
[日志信息]-[2006-07-30 08:56:01]-[INFO]-[org.solol.Parser]-[具体的消息]
[日志信息]-[2006-07-30 08:57:26]-[INFO]-[org.solol.UserOperator]-[User2]-[添加用户User3]-[具体的消息]
[日志信息]-[2006-07-30 08:58:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查询报表3]-[具体的消息]
[日志信息]-[2006-07-30 08:59:38]-[INFO]-[org.solol.UserOperator]-[User3]-[查询报表1]-[具体的消息]
[日志信息]-[2006-07-30 08:59:39]-[INFO]-[org.solol.UserOperator]-[User2]-[退出系统]-[具体的消息]

从上面的日志记录中我们很容易抽取出某一用户的操作列表,如对于用户User1我们的结果为:

[日志信息]-[2006-07-30 08:55:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查询报表1]-[具体的消息]
[日志信息]-[2006-07-30 08:55:30]-[INFO]-[org.solol.UserOperator]-[User1]-[查询报表2]-[具体的消息]
[日志信息]-[2006-07-30 08:58:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查询报表3]-[具体的消息]

这样我们就得到了某一时间段中User1的操作列表,可以利用这一列表来进行安全分析。

我们还可以从另外的角度来分析上面的日志记录,如我们很容易统计出操作(日志类别为org.solol.UserOperator)发生的总次数(6次),其中操作[查询报表1]为2次,[查询报表2]为1次,[查询报表3]为1次,[添加用户User3]为1次,[退出系统]为1次。这样我们就可以得出系统中的那些操作用户使用的比较频繁。

以上我们从两个角度对日记记录中的信息进行了简单的挖掘,实际中待挖掘的方面要丰富的多,这取决于您的意图和您的想象力。

这里我们还要特别强调一下:所有这一切都需要有使用日志记录的良好规划。如果规划不好(即日志记录没有规律性),那么我们挖掘时的任务就会非常繁重或者使挖掘成为一个不可能的任务。

文章到了这里我们要来描述日志记录的最为流行的用法了,即调试应用程序。我们在调试应用程序时一般会使用两种方法,除了日志记录之外,还有debugger调试器。

我们不想把他们放到一起来描述,因为这是两个完全不同的问题,虽然他们都用来调试应用程序。使用debugger调试器我们可以清楚的知道引发错误的上下文及其相关信息,也可以使用单步执行、设置断点、检查变量以及暂挂和恢复线程等等比较高级的能力,但是尽管这样它也不能替代日志记录,同样日志记录也不能替代debugger调试器。我们要结合使用这两种方法,不同的场景使用不同的方法会有更好的效果。

我们认为使用日志记录来调试应用程也应该充分考虑软件的开发周期。这里我们只考虑软件开发周期中的与日志记录有关的两个阶段:

  • 开发阶段,用来记录应用程序的方方面面和各种细节,非常详细,使得一看到它就知道那里出了问题,出了什么样的问题。
  • 出品阶段,要能够记录各种级别的错误和警告,同时也要适度记录应用程序正常运行的关键信息,这些信息可以给相关人员(开发人员、测试人员、用户等)极大的信心,使他们可以毫不犹豫的告诉您--瞧我们的软件在正常的运行。如一个好的web服务器的启动日志记录不仅要包含错误和警告,还要包含服务器正在启动,正在加载某某组件等等,最后还要提示启动是成功还是失败。

阅读到这里我们就应该着手实现我们的日志记录了。比较幸运的是我们有好多日志记录软件包可选,这就使我们不必关心日志记录的细节,只要把主要的精力放到日志记录的规划上就好了。我们选择的是Log4j,文章的余下部分将主要介绍这个Java日志记录软件包。

log4j的特性

log4j的特性列表:

  • 在运行速度方面进行了优化
  • 使用基于名称的日志(logger)层次结构
  • 是fail-stop的
  • 是线程安全的
  • 不受限于预定义的实用工具集
  • 可以在运行时使用property和xml两种格式的文件来配置日志记录的行为
  • 在一开始就设计为能够处理Java异常
  • 能够定向输出到文件(file)、控制台(console)、java.io.OutputStream、java.io.Writer、远程服务器、远程Unix Syslog守护者、远程JMS监听者、NT EventLog或者发送e-mail
  • 使用DEBUG、INFO、WARN、ERROR和FATAL五5个级别
  • 可以容易的改变日志记录的布局(Layout)
  • 输出日志记录的目的地和写策略可以通过实现Appender接口来改变
  • 支持为每个日志(logger)附加多个目的地(appender)
  • 提供国际化支持

log4j的设计原理

Log4j有三个主要的组件:Logger、Appender和Layout。这三个组件相互配合使得我们可以获得非常强大的日志记录的能力。

Logger

Logger的名称是区分大小写的,依据名称可以确定其层次结构(即父子关系),规则如下:

  • 如果Logger A的名称后跟一个点(.)是Logger B的名称的前缀就认为Logger A是Logger B的祖先。
  • 如果在Logger A和Logger B之间,Logger B没有任何其它的祖先就认为Logger A是Logger B的父亲。

在Logger的层次结构的最顶层是root logger,它会永远存在,而且不能通过名字取到。

上面文字的描述可能不好的理解,为此我们给出了一张图,Logger的层次结构图,从中可以非常直观的看出三种主要组件的关系和各自所起的作用。

图示 1. Logger的层次结构图
Logger的层次结构图

 

Loger x.y是Logger x.y.z的祖先,因为x.y.是x.y.z的前缀,这符合规则的前一条。另外在Logger x.y和Logger x.y.z之间,Logger x.y.z没有其它的祖先,因此Logger x.y是Logger x.y.z的父亲,这符合规则的后一条。这样我们依据上面的规则就可以构造出如图1所示的Logger的层次结构。

从图1中我们还可以看到每一个Logger都有一个Level,根据该Level的值Logger决定是否处理对应的日志请求。如果Level没有被设置,就象图1中的Logger x.y一样,又该怎么办呢?答案是可以从祖先那里继承。

如果Logger C没有被设置Level,那么它将沿着它的层次结构向上查找,如果找到就继承并结束,否则会一直查找到root logger结束。因为log4j在设计时保证root logger会被设置一个默认的Level,所以任何logger都可以继承到Level。

图1中的Logger x.y没有被设置Level,但是根据上面的继承规则,Logger x.y继承了root logger的Level。

我们在来看看Logger选择日志记录请求(log request)的规则:

假设Logger M具有q级的Level,这个Level可能是设置的也可能是继承到的。

如果向Logger M发出一个Level为p的日志记录请求,那么只有满足p>=q时这个日志记录请求才会被处理。

org.apache.log4j.Logger中的不同方法发出不同Level的日志记录请求,如下:

  • public void debug(Object message),发出Level为DEBUG的日志记录请求
  • public void info(Object message),发出Level为INFO的日志记录请求
  • public void warn(Object message),发出Level为WARN的日志记录请求
  • public void error(Object message),发出Level为ERROR日志记录请求
  • public void fatal(Object message),发出Level为FATAL的日志请求
  • public void log(Level l, Object message),发出指定Level的日志记录请求

其中的静态常量DEBUG、INFO、WARN、ERROR、FATAL是在org.apache.log4j.Level中定义的,除了使用这些预定义的Level之外,Log4j还支持自定义Level。

注:org.apache.log4j.Level中还预定义了一些其它的Level。

Appender

在Log4j中,Appender指的是日志记录输出的目的地。当前支持的Appender(目的地)有文件(file)、控制台(console)、java.io.OutputStream、java.io.Writer、远程服务器、远程Unix Syslog守护者、远程JMS监听者、NT EventLog或者发送e-mail。如果您在上面没有找到适合的Appender,那就需要考虑实现自己的自定义Appender了。

每个Logger可以有多个Appender,但是相同的Appender只会被添加一次。

Appender的附加性意味着Logger C会将日志记录发给它的和它祖先的所有Appender。在图1中Logger a会将日志记录发给它自己的JDBCAppender和它的祖先root logger的ConsoleAppender和FileAppender。Logger x.y.z自己没有Appender,它将把日志记录发给它的祖先root logger的ConsoleAppender和FileAppender,如果Logger x.y也含有Appender,那么它们也会包括在内。

Appender的附加性是可以被中断的。假设Logger C的一个祖先为Logger P,如果Logger P的附加性标志(additivity flag)设置为假,那么Logger C会将日志记录只发给它的和在它和Logger P之间的祖先(包括Logger P)的Appender,而不会发给Logger P的祖先的Appender。Logger的附加性标志(additivity flag)默认值为ture。

在图1中如果没有设置Logger a的附加性标志(additivity flag),而是使用默认值true,那么Logger a会将日志记录发给它自己的JDBCAppender和它祖先root logger的ConsoleAppender和FileAppender,这和上面的描述相同。如果设置Logger a的附加性标志(additivity flag)的值false,那么Logger a会将日志记录发给它自己的JDBCAppender而不会在发给它祖先root logger的ConsoleAppender和FileAppender了。

Layout

Appender定制了输出目的地,通常我们还需要定制日志记录的输出格式,在Log4j中是通过将Layout和Appender关联到一起来实现的。Layout依据用户的要求来格式化日志记录。PatternLayout(标准Log4j组件)让用户依据类似于C语言printf函数的转换模式来指定输出格式。

例如,转换模式(conversion pattern)为"%r [%t] %-5p %c - %m%n"的PatternLayout将生成类似于以下内容的输出:

176 [main] INFO  org.foo.Bar - Located nearest gas station.

在上面的输出中:

  • 第一个字段表示自程序开始到发出日志记录请求时所消耗的毫秒数
  • 第二个字段表示发出日志记录请求的线程
  • 第三个字段表示日志记录请求的Level
  • 第四个字段表示发出日志记录请求的Logger的名称
  • 第五个字段(-后的文本)表示日志记录请求的消息

Log4j中还提到了一些其它的Layout,包括HTMLLayout、SimpleLayout、XMLLayout、TTCCLayout和DateLayout。如果这些不能满足您的要求,还可以自定义自己的Layout。

log4j的配置

依据既有的经验显示用于日志记录的代码大约是全部代码量的4%。如果应用程序具有一定的规模,日志记录语句的数量还是比较巨大的,因此必须有效的管理这些语句。

在Log4j中我们可以通过配置Log4j环境来有效的管理日志记录。配置的方式有三种:

  • 通过程序配置
  • 通过Property文件配置
  • 通过XML文件配置

程序配置

通过程序配置Log4j环境实际上就是在应用程序的代码中改变Logger的Level或增加减少Appender等等。

Log4j提供了BasicConfigurator,它只是为root logger添加Appender。其中,

  • BasicConfigurator.configure()为root logger添加一个关联着PatternLayout.TTCC_CONVERSION_PATTERN的ConsoleAppender
  • BasicConfigurator.configure(Appender appender)为root logger添加指定的Appender

我们可以把BasicConfigurator看成是一个简单的使用程序配置Log4j环境的示例。例如,要给root logger添加两个Appender(A和B),下面的代码分别完成了这个要求。

不使用BasicConfigurator:

//示例代码,不能直接使用
Logger root = Logger.getRootLogger();
root.addAppender(A);
root.addAppender(B);

使用BasicConfigurator:

//示例代码,不能直接使用
BasicConfigurator.configure(A);
BasicConfigurator.configure(B);

Property文件配置

这里要使用PropertyConfigurator来分析配置文件并设置日志记录,但是要注意日志记录先前的配置不会被清除和重设。

Property文件是由key=value这样的键值对所组成的,可以使用#或!作为注释行的开始。下面给出了两个简单的示例:

非常简单的示例1:

log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n

稍显复杂的示例2:

log4j.rootLogger=, A1, A2
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n
log4j.appender.A2=org.apache.log4j.FileAppender
log4j.appender.A2.File=filename.log
log4j.appender.A2.Append=false
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%-5r %-5p [%t] %c{2} - %m%n

上面的两个示例只是让您对配置文件的格式有一个大体的认识,我们将在后面详细的描述各个配置元素的语法。

Repository-wide threshold:

Repository-wide threshold指定的Level的优先级高于Logger本身的Level。语法为log4j.threshold=[level],level可以为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。也可以使用自定义Level,这时的语法为log4j.threshold=[level#classname]。默认为ALL。

依据上面的规则,我们有这样的结论:如果log4j.threshold=ERROR,Logger C的Level=DEBUG,这时只有高于等于ERROR的日志记录请求会被Logger C处理。

Appender的配置:

Appender的配置语法为

# For appender named appenderName, set its class.
# Note: The appender name can contain dots.
log4j.appender.appenderName=fully.qualified.name.of.appender.class
# Set appender specific options.
log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN
#For each named appender you can configure its Layout.
#The syntax for configuring an appender's layout is:
log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
log4j.appender.appenderName.layout.option1=value1
....
log4j.appender.appenderName.layout.optionN=valueN

Logger的配置:

root logger的配置语法:

log4j.rootLogger=[level], appenderName, appenderName, ...,其中level可以为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。也可以使用自定义Level,这时的语法为[level#classname]。

如果Level被指定那么root logger的Level将被配置为指定值。如果Level没有被指定那么root logger的Level不会被修改。从上面的语法中我们可以看出通过用,分隔的列表可以为root logger指定多个Appender。

对于root logger之外的logger语法是相似的,为log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...

上面只有INHERITED和NULL需要说明一下,其它部分和root logger相同。INHERITED和NULL的意义是相同的。如果我们使用了它们,意味着这个logger将不在使用自己的Level而是从它的祖先那里继承。

Logger的附加性标志(additivity flag)可以使用log4j.additivity.logger_name=[false|true]来配置。

ObjectRenderer配置:

我们可以通过ObjectRenderer来定义将消息对象转换成字符串的方式。语法为log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class。如:

//my.Fruit类型的消息对象将由my.FruitRenderer转换成字符串
log4j.renderer.my.Fruit=my.FruitRenderer

对上面的各个配置元素的语法理解之后,在来看示例1和2就很容易了。

PropertyConfigurator不支持Filter的配置。如果要支持Filter您可以使用DOMConfigurator,即使用XML文件的方式配置。

XML文件配置

要使用DOMConfigurator.configure()来读取XML格式的配置文件。XML文件格式的定义是通过org/apache/log4j/xml/log4j.dtd来完成的,各个配置元素的嵌套关系如下:

<!ELEMENT log4j:configuration (renderer*, appender*,(category|logger)*,root?,categoryFactory?)>

这里没有给出更为详细的内容,要了解详细的内容需要查阅log4j.dtd。

下面这个简单的示例可以使您对XML配置文件的格式有一个基本的认识:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j SYSTEM "log4j.dtd">
<log4j>
<appender name="A1" class="org.apache.log4j.FileAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %c{2} - %m\n"/>
</layout>
</appender>
<appender name="A2" class="org.apache.log4j.FileAppender">
<layout class="org.apache.log4j.TTCCLayout">
<param name="DateFormat" value="ISO8601" />
</layout>
<param name="File" value="warning.log" />
<param name="Append" value="false" />
</appender>
<category name="org.apache.log4j.xml" priority="debug">
<appender-ref ref="A1" />
</category>
<root priority="debug">
<appender-ref ref="A1" />
<appender-ref ref="A2" />
</root>
</log4j>

默认初始化过程

默认初始化过程在LogManager类的静态初始化器中完成。具体步骤如下:

  • 检查系统属性log4j.defaultInitOverride,如果值为false则执行初始化过程,否则跳过初始化过程。
  • 将系统属性log4j.configuration的值赋给变量resource。如果log4j.configuration没有被定义则使用默认值log4j.properties。
  • 试图转换变量resource到一个url。
  • 如果变量resource不能转换成一个url,那么将使用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)得到一个url。
  • 如果还是得不到url,将忽略默认初始化过程。如果得到url将使用PropertyConfigurator或DOMConfigurator来配置,也可以使用自定义的XXXConfigurator。
参考资料
solo L 一位有些理想主义的软件工程师,创建了solol.org。他常常在这里发表一些对技术的见解。
分享到:
评论

相关推荐

    使用log4j进行日志记录

    Log4j提供了灵活的配置方法,默认是调用BasicConfigurator.configure()来进行配置,但如果只是简单的调用BasicConfigurator.configure()来进行配置工作,那么所有的配置都是固定的,不方便以后修改配置。另一种是...

    示范如何在android工程中使用log4j记录日志

    在Android开发中,日志记录是一项非常重要的任务,它...通过以上步骤,你就能在Android工程中成功使用Log4j进行日志记录了。记得合理配置日志级别,平衡调试需求与性能消耗,以及定期清理日志,以保持应用的健康运行。

    log4j使用与java中log4j记录日志如何写入数据库

    Log4j是Apache组织提供的一款开源的日志记录工具,它功能强大、灵活易用。本文将详细介绍如何在Java中使用Log4j来记录日志,并将其写入数据库。 首先,我们需要了解Log4j的基本结构。一个简单的Log4j项目通常包含...

    tomcat8更换log4j记录日志

    压缩包中的"**lib**"目录可能包含Log4j的必要依赖库,比如`log4j.jar`,这是运行Log4j的日志记录功能所必需的。确保这些库文件被添加到Tomcat的`lib`目录,使得所有部署的应用都能访问Log4j。 综上所述,通过正确...

    log4j2.xml记录日志到到数据库

    最后,在代码中使用Log4j2进行日志记录,例如: ```java import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Main { private static final Logger logger = ...

    Log4j记录日志DEMO

    本DEMO将深入探讨如何使用Log4j进行日志记录。 ### 1. Log4j组件 Log4j主要由以下三个核心组件构成: - **Logger**: 日志记录器,负责生成日志事件。 - **Appender**: 输出端,定义日志信息的输出目的地,如...

    AndroidStudio 使用log4j记录日志,按照大小定期滚动日志Demo

    首先,log4j是一款广泛使用的日志记录框架,它提供了灵活的日志配置和强大的功能。在Android环境中,由于系统默认的日志记录器(Logcat)有一些限制,如日志长度、级别控制等,因此,引入log4j可以提供更高级别的...

    在Eclipse中应用Log4J记录日志

    通过上述步骤,你可以在Eclipse中成功地配置并使用Log4J进行日志记录。Log4J的强大之处在于其高度可定制性和灵活性,可以根据项目的实际需求调整日志输出格式、日志级别以及日志保存位置,极大地提高了日志管理的...

    logging-log4j日志记录

    **使用Log4j进行日志记录** 在代码中,可以通过以下方式创建并使用`logger`: ```java import org.apache.log4j.Logger; public class MyClass { private static final Logger logger = Logger.getLogger...

    使用log4j 记录日志到数据库

    这篇博客“使用log4j记录日志到数据库”将介绍如何配置和使用Log4j,以便将日志信息存储到数据库中,而非传统的文本文件。数据库存储的日志便于进行结构化查询,有助于进行长期的数据分析和管理。 首先,理解Log4j...

    weblogic中使用log4j生成日志文件实例

    3. **编写代码**: 使用Log4j进行日志记录非常简单。在Java代码中,我们导入`org.apache.log4j.Logger`,然后获取一个日志实例,最后调用相应的方法输出日志。例如: ```java import org.apache.log4j.Logger; ...

    使用log4j写日志文件

    在Java应用中使用Log4j进行日志记录的第一步,是创建并配置log4j的属性文件(log4j.properties)。这个文件定义了日志的级别、输出目的地以及日志的格式。以下是一个典型的log4j配置示例: ```properties # 设置根...

    log4j+slf4j实现 log4j测试代码,log4j+slf4j实现 log4j测试代码

    总结,SLF4J和Log4j的组合使用让日志管理更加灵活,开发者可以通过SLF4J的简洁API进行日志记录,同时利用Log4j的强大功能,如自定义输出格式和多种输出目的地。通过适当的配置和测试,我们可以确保日志系统按照预期...

    log4j日志包日志包

    在Java代码中,使用`log4j`进行日志记录非常简单。首先需要导入`org.apache.log4j.Logger`,然后创建一个`Logger`实例,最后调用相应的日志方法: ```java import org.apache.log4j.Logger; public class MyClass ...

    关于开源日志记录工具log4j的使用.txt

    ### 关于开源日志记录工具log4j的...以上就是关于如何使用Log4j进行日志记录的详细介绍,包括环境配置、日志配置以及实际代码中的日志记录方法。通过合理的配置和使用,Log4j能够有效地提高软件的可维护性和调试效率。

    使用log4j2实现日志数据脱敏

    Log4j2支持多种日志记录级别(如DEBUG、INFO、WARN、ERROR),并允许通过XML、JSON、YAML或纯Java配置文件进行灵活配置。 二、日志数据脱敏概念 日志数据脱敏是指在不影响日志分析的前提下,对敏感信息进行替换、...

Global site tag (gtag.js) - Google Analytics