资讯专栏INFORMATION COLUMN

【译】log4j介绍

ztyzz / 1493人阅读

摘要:在中对一个实例命名非常的简单,在每一个类中可以有一个静态的实例对象,可以用类的完全限定名作为实例的名字。但不管怎样,用类的完全限定名作为实例的名字是一个非常好的方式。

本文是对log4j官网Introduction部分的翻译,原文链接地址:http://logging.apache.org/log...。

Introduction:

几乎每个大型应用都包含自己的日志API。1996年,为了整个项目的一致性,E.U. SEMPER项目团队决定开发自己的日志API。经过无数次的改进,这款日志API成为了Java领域非常流行的日志package,这就是log4j。
在代码中加日志进行调试是一种低级的方式。但因为调试工具并不总是可以使用,所以打日志有时候是唯一的调试方法。例如多线程应用和分布式应用。
有经验表明在软件开发周期中,日志组件占有重要的地位。加入日志有许多好处。可以通过日志精确了解应用运行的状态。加入代码中的日志,无需人们手工干预就可自动生成输出结果。日志的输出结果可以保存在永久存储介质上,方便今后对其进行查看。此外,在软件开发周期中,充足丰富的日志也可以当做审计材料使用。
日志也有缺点,它会使应用程序变慢。如果日志过多,还会导致屏幕闪动。为了缓和这些缺点,log4j被设计成可靠的、快速的和可扩展的。由于日志很少是一个应用中关注的重点,所以log4j的API尽可能设计的简单易懂。

Loggers, Appenders and Layouts

log4j主要有三大组件——loggers、appenders和layouts。这三大组建共同协作,使开发者可以根据不同的日志级别和日志类型输出信息,并且可以指定信息输出的格式和信息输出的目的地。

Logger hierarchy

相比于使用简单的System.out.println语句,日志API最大的优势就是它可以禁止某一类型的日志输出,同时又不影响其它类型的日志输出。要实现这种能力,需要开发人员根据某种条件,将日志划分为不同的类型。老版本的log4j将Category类作为核心就是由于上面这个原因。但log4j到1.2版本时,已经使用Logger类代替了原来的Category类。对于那些熟悉老版本log4j的人,可以简单把Logger类当做Category类的一个别名。
Loggers是被命名的实体,Logger的名称是大小写敏感的,并且遵循层次命名规则。
举个例子,命名为“com.foo”的logger是命名为“com.foo.Bar”的logger的父亲。命名为“java”的logger是命名为“java.util”的logger的父亲,是命名为“java.util.Vector”的祖先。这种命名规则应该被许多研发人员所熟悉。
root logger位于整个logger继承体系的最顶端,相比于普通logger它有两个特别之处:

root logger总是存在。

root logger不能通过名称获取。

可以通过调用Logger类的静态方法getRootLogger获取root logger对象。其它普通logger的实例可以通过Logger类的另一个静态方法getLogger获取。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);
}

Loggers可以被分配日志级别,可以分配的级别如下:
TRACE
DEBUG
INFO
WARN
ERROR和
FATAL
这些级别被定义在org.apache.log4j.Level类中。虽然你也可以通继承Level类定义你自己的专有级别,但是我们不鼓励你这样做。
如果一个logger没有指定任何level,那么这个logger会从它的父亲那里继承level。
为了保证所有的logger可以最终被指定一个level,root logger总是被分配一个level。
下面四个表是上面规则的例子:
Example 1

在第一个例子中,只有root logger被分配了一个level值Proot,Proot会被其它所有的logger——x、x.y、x.y.z继承。
Example 2

在第二个例子中,所有的logger都被分配了一个level值,就不需要继承level了。
Example 3

在第三个例子中,root、x和x.y.z三个logger分别被分配了Proot、Px和Pxyz三个level值,x.y这个logger从它的父亲那里继承level值。
Example 4

在第四个例子中,root和x两个logger分别被分配了Proot和Px这两个level值。x.y和x.y.z两个logger则从离自己最近的祖先x继承level值。
可以调用logger实例的printing方法输入日志。printing方法包括debug、info、warn、error、fatal和log。
按照定义,printing方法决定了日志请求的等级。例如,c是一个logger实例,c.info("..")语句请求输出INFO级别的日志。
只有日志请求级别大于等于日志级别的时候,日志请求才会被准许输出信息。否则,日志请求会被禁止。这条规则是log4j的核心。日志的level是有序的。对于标准的日志级别:DEBUG下面是这条规则的一个例子:

   // 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实例之后,可以很方便的在代码的其它地方获取到这个logger实例,而无需传递logger实例的引用。
与生物学中的父子关系不同,log4j中的父亲不一定总是早于它的孩子出生。log4j中的logger实例可以以任意的顺序被构造或配置。一个parent logger即使在它的后代之后被实例化,它们也依然可以建立起父子关系。
log4j的配置通常会在应用初始化时被完成。最常用的方式是通过读取配置文件完成配置。稍后会讨论这个过程。
在log4j中对一个logger实例命名非常的简单,在每一个类中可以有一个静态的logger实例对象,可以用类的完全限定名作为logger实例的名字。这对于定义一个logger非常有用,由于在输出日志的时候可以带有logger实例的名字,所以这种用类的完全限定名作为logger实例的名字可以很容易看出日志发生的位置。当然这只是一种通用做法,log4j对此并没有限制,开发人员可以随意指定logger实例的名字。但不管怎样,用类的完全限定名作为logger实例的名字是一个非常好的方式。

Appenders and Layouts

禁止和允许日志输出的能力只是全部功能的一部分。log4j允许将日志输出到多个目的地。在log4j的术语中,日志输出目的地被称为appender。目前,存在的appender包括命令行、文件、GUI组件、远程socket服务器、JMS、NT事件日志和远程UNIX Syslog后台进程。并且可以支持异步的方式记录日志。
一个logger实例可以同时挂载多个appender。
addAppender方法向logger实例添加一个appender。对于每一个被允许的日志输出请求,logger实例不仅将该请求转发到自己所有的appender上,而且还将日志输出请求转发到它祖先上的所有appender上。例如,如果一个命令行appender被添加到root logger上,那么所有被允许的日志请求至少会输出到命令行中。如果在这个基础上再向C logger中添加一个文件appender,那么对于C和它的后代,被允许的日志会同时输出到命令行和文件。通过将additivity flag设置为false,可以覆盖这种默认行为。
Appender Additivity:对C logger的日志输出请求会转发到C自己和它祖先们的全部appender。这种行为用术语“appender additivity”表示。如果 P是C的祖先,P将additivity flag设置为false。那么C的日志会输出到它自己的appender和C到P之间(包括P)每个logger的appender,而不会输出到P以上祖先的appender。对于每个logger,它的additivity flag默认是设置为true的。
下面的表格是这样的一个例子:

通常,研发不仅希望指定日志输出的目的地,而且希望能够指定日志输出的格式。可以在appender上关联一个layout用于指定日志输出格式。layout会按照用户的意愿输出一定格式的日志信息。
PatternLayout可以让用户像使用C语言中的printf那样使用格式化表达式定制日志输出的格式。
例如,使用PatternLayout的表达式"%r [%t] %-5p %c - %m%n”可以包含下面日志信息:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一个字段是程序启动到现在经过的毫秒数,第二个字段是输出日志的线程,第三个字段是日志的级别,第四个字段是logger的名字。’-’后面的文本是日志输出信息。%n是换行。
log4j会按照用户指定的具体条件输出日志内容。例如,如果你频繁的需要输出Orange类对象的日志,那么一可以注册一个OrangeRenderer,每当输出orange的日志时,它都会被调用。
Object rendering follows the class hierarchy. For example, assuming oranges are fruits, if you register a FruitRenderer, all fruits including oranges will be rendered by the FruitRenderer, unless of course you registered an orange specific OrangeRenderer.
Object renderers have to implement the ObjectRenderer interface.

Configuration

在现有应用中加入日志需要大量的工作。调研表明,大约有接近4%的代码跟日志有关。因此,即使不是那么大的应用也会有成千上万行的日志代码。Given their number, it becomes imperative to manage these log statements without the need to modify them manually.
og4j环境是完全可以通过写程序进行配置的。然而,使用配置文件对log4j进行配置会更加的灵活。目前,配置文件可以采用XML和properties两种格式的文件。

 import com.foo.Bar;

 // Import log4j classes.
 import org.apache.log4j.Logger;
 import org.apache.log4j.BasicConfigurator;

 public class MyApp {

   // Define a static logger variable so that it references the
   // Logger instance named "MyApp".
   static Logger logger = Logger.getLogger(MyApp.class);

   public static void main(String[] args) {

     // Set up a simple configuration that logs on the console.
     BasicConfigurator.configure();

     logger.info("Entering application.");
     Bar bar = new Bar();
     bar.doIt();
     logger.info("Exiting application.");
   }
 }

MyApp在导入了log4j相关的类,然后用MyApp的全限定类名定义了一个静态的logger实例变量。
MyApp中使用的Bar类:

 package com.foo;
 import org.apache.log4j.Logger;

 public class Bar {
   static Logger logger = Logger.getLogger(Bar.class);

   public void doIt() {
     logger.debug("Did it again!");
   }
 }

调用BasicConfigurator.configure方法创建了一个非常简单的log4j配置。它以硬编码的方式向root logger中添加了一个ConsoleAppender,日志输出会使用PatternLayout的模板"%-4r [%t] %-5p %c %x - %m%n”进行格式化。注意默认情况下,root logger被分配的日志级别是Level.DEBUG。
上面程序输入的日志为:

0    [main] INFO  MyApp  - Entering application.
36   [main] DEBUG com.foo.Bar  - Did it again!
51   [main] INFO  MyApp  - Exiting application.

下面的图形是MyApp在调用完BasicConfigurator.configure方法之后的对象图:

前面的这种方式只能一直输出同一种配置模式的日志信息,可以很容易的在MyApp启动时修改日志配置信息,使其输出不同配置模式的日志。

 import com.foo.Bar;

 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;

 public class MyApp {

   static Logger logger = Logger.getLogger(MyApp.class.getName());

   public static void main(String[] args) {


     // BasicConfigurator replaced with PropertyConfigurator.
     PropertyConfigurator.configure(args[0]);

     logger.info("Entering application.");
     Bar bar = new Bar();
     bar.doIt();
     logger.info("Exiting application.");
   }
 }

这个版本的MyApp使用PropertyConfigurator解析文件并对日志进行配置。
下面这个配置文件产生的配置结果与之前使用BasicConfigurator产生的结果完全相同。

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

如果我们不再对com.foo包中任何组件输出的日志感兴趣,下面的配置文件可以实现这一点:

log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout

# Print the date in ISO 8601 format
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

# Print only messages of level WARN or above in the package com.foo.
log4j.logger.com.foo=WARN

现在MyApp的日志输出如下:

2000-09-07 14:07:41,508 [main] INFO  MyApp - Entering application.
2000-09-07 14:07:41,529 [main] INFO  MyApp - Exiting application.

由于logger com.foo.Bar没有被指定任何的日志级别,所以它会从com.foo上继承,在配置文件中指定com.foo的日志级别是WARN,而代码Bar.doIt中的log语句日志请求输出的是DEBUG级别,要比WARN级别低,所以doIt方法中的日志请求不会被响应。
下面是另一个配置文件,它使用了多个appenders:

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

# Pattern to output the caller"s file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log

log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

使用这个配置文件会向命令行输出如下日志信息:

INFO [main] (MyApp2.java:12) - Entering application.
DEBUG [main] (Bar.java:8) - Doing it again!
INFO [main] (MyApp2.java:15) - Exiting application.

此外,root logger被分配的第二个appender,是将日志信息直接输出到example.log文件中的。当example.log文件到达100KB时,会发生roll-over。这时老版本的example.log会自动移动到example.log.1。
注意到要改变日志行为无需重新编译代码。我们可以改变配置文件让其日志输出到UNIX Syslog daemon、将所有的com.foo输出的日志都重定向到NT Event logger、或者将日志事件转发到远程的log4j服务器。

Default Initialization Procedure

log4j库没有对它的运行环境做过任何的假设。也就是说,log4j没有任何默认的appender。然而在一些环境下,日志类的静态初始化器会自动尝试配置log4j。Java从语言层面保证在类加载的时候,静态初始化器会被调用一次且仅被调用一次。但是要额外留意不同的classloader可能会对同一个类加载多个副本。
The default initialization is very useful in environments where the exact entry point to the application depends on the runtime environment. For example, the same application can be used as a stand-alone application, as an applet, or as a servlet under the control of a web-server.
The exact default initialization algorithm is defined as follows:

设置log4j.defaultInitOverride系统属性为非false值会导致log4j略过默认的初始化过程

设置log4j.configuration系统属性的字符串值。通过设置log4j.configuration系统属性的值指定默认初始化文件是最常用的方式。如果系统属性log4j.configuration没有被明确定义,则为它分配默认值为log4j.properties。log4j.configuration的值记为resource变量。

尝试将resource变量转换为url。

如果resource变量无法转化为url,比如在转化url时发生MalformedURLException异常,那么调用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)在classpath中寻找resource并返回一个url。

如果找不到url,终止默认初始化,否则使用url对log4j进行配置。

如果url不是以“.xml”为后缀,那么将会使用PropertyConfigurator解析url并对log4j进行配置。如果url的后缀是“.xml”,那么将会使用DOMConfigurator完成上述工作。你可以随意指定一个定制的配置器(configurator)。log4j.configuratorClass系统属性的值就是你定制配置器的全限定类名。你自己定制的配置器必须实现Configurator这个接口。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67570.html

相关文章

  • 强推!大牛程序员必备的Java日志框架,性能无敌

    摘要:本文要来分享给大家程序员最常用的日志框架组件。没有基础的同学也不要着急,这套教程覆盖了目前所有的日志框架,只要你学,就一定用得到,先收藏,以备不时之需。 作为一名Java程序员,我们开发了很多Java应用程序,包括桌面应用、WEB应用以及移动应用。然而日志系统是一个成熟Java应用所必不可少的。在开发和调试阶段,日志可以帮...

    zebrayoung 评论0 收藏0
  • spring-springmvc-mybatis-shiro项目介绍

    摘要:项目介绍在之前的整合项目之后,新增日志简单集成,之前的代码不予展示与介绍,想了解的请参考整合项目项目代码获取项目结构代码控制层,,主要包含登录及几个页面跳转会跳到我们自定义的中登录用户名或密码错误业务处理层,包含一个包,以接口类型存在 spring-springmvc-mybatis-shiro项目介绍 在之前的mybatis整合项目之后,新增日志、简单集成shiro,之前的代码不予展...

    fanux 评论0 收藏0
  • log4j2搭建并使用

    摘要:作为一个实用主义者,我喜欢在理解基本原理后快速的搭建系统,当系统运行起的时候有那种愉悦和兴奋。,着手搭建,我用的是进行的。要使用日志系统,就需要进行相关配置,这个不用我多说了叁。 作为一个实用主义者,我喜欢在理解基本原理后快速的搭建系统,当系统运行起的时候有那种愉悦和兴奋。最近在完善公司框架,从最基本的日志系统开始。 java日志系统比较流行的是log4j,slf4j和logbac...

    lauren_liuling 评论0 收藏0
  • Java日志那些事

    摘要:但是考虑到各不相同,所以出现了等日志框架。日志框架只是统一的,其底层的具体的日志记录工作还是由等承担。如何选择和搭配日志系统目前来说,新应用使用是首选,一些老系统中很可能使用的是等。所以若日志冲突时,使用的三方库只需要相应的实现库即可。 日志系统的发展 我们日常接触到的日志系统有很多种,log4j,JUL(jdk自带),logback等,我们可以直接根据对象的日志API进行使用。但是考...

    GeekQiaQia 评论0 收藏0
  • Log4j各级别日志重复打印的问题

    摘要:今天在配置日志的时候,发现日志重复打印的问题。把配置文件修改成如下日志控制台日志级别日志级别日志级别日志通过以上配置模板即可解决各级别日志重复打印的问题。 今天在配置Log4j日志的时候,发现日志重复打印的问题。网上查了很多资料,发现介绍Log4j配置的文章数量不少,但提到这个问题的文章却寥寥,解决了自己的问题以后,赶紧记录一下。 原文地址:http://www.jianshu.com...

    tylin 评论0 收藏0

发表评论

0条评论

ztyzz

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<