Log4J--日志第一发

来源:互联网 发布:chairgun3弹道软件 编辑:程序博客网 时间:2024/04/29 12:05

Log4J

简介

Log4j 是 Apache 的一个开放源代码项目, 通过使用 Log4j, 我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件、甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就 是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。此外,通过Log4j 其他语言接口,您可以在 C、C+ +、.Net、PL/SQL 程序中使用 Log4j,其语法和用法与在 Java 程序中一样, 使得多语言分布式系统得到一个统一一致的日志组件模块。 而且, 通过使用各种第三方扩展,您可以很方便地将 Log4j 集成到 J2EE、JINI 甚至是 SNMP 应用中。

配置实例

Log4J Settings for log4j 1.2.x (via jakarta-commons-logging)  #  # The five logging levels used by Log are (in order):  #   #   1. DEBUG (the least serious)  #   2. INFO  #   3. WARN  #   4. ERROR  #   5. FATAL (the most serious)  # Set root logger level to INFO and append to stdout  log4j.rootLogger=INFO,stdout,file#设置log的级别为INFO意味着INFO级别以下如DEBUG的信息不会输出#其中,Log4j提供的appender有以下几种:#org.apache.log4j.ConsoleAppender(控制台),#org.apache.log4j.FileAppender(文件),#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)#其中,Log4j提供的layout有以几种:#org.apache.log4j.HTMLLayout(以HTML表格形式布局),#org.apache.log4j.PatternLayout(可以灵活地指定布局模式),#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)#Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下: # %m 输出代码中指定的消息# %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL# %r 输出自应用启动到输出该log信息耗费的毫秒数# %c 输出所属的类目,通常就是所在类的全名# %t 输出产生该日志事件的线程名# %n 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n”# %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921# %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)#stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppender  log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  log4j.appender.stdout.layout.ConversionPattern=[%p]%-d{yyyy-MM-dd HH\:mm\:ss}[%c\:%L] %m%n  #filelog4j.appender.file=org.apache.log4j.DailyRollingFileAppender  log4j.appender.file.File=/output.loglog4j.appender.file.DatePattern='.'yyyy-MM-dd  log4j.appender.file.layout=org.apache.log4j.PatternLayout  log4j.appender.file.layout.ConversionPattern=[%p]%-d{yyyy-MM-dd HH\:mm\:ss}[%c\:%L] %m%n#log4j.appender.file=org.apache.log4j.DailyRollingFileAppender  #log4j.appender.file.File=/output.html#log4j.appender.file.DatePattern='.'yyyy-MM-dd  #log4j.appender.file.layout=org.apache.log4j.HTMLLayout #log4j.appender.file.layout.ConversionPattern=[%p]%-d{yyyy-MM-dd HH\:mm\:ss}[%c\:%L] %m%n# Print only messages of level ERROR or above in the package noModule.  log4j.logger.noModule=DEBUG

Log4j的构成

通过配置文件可以知道,主要有三个方面的内容:
• 根目录
• 目的地(控制台,文件等)
• 输出样式
Logger - 日志写出器,供程序员输出日志信息 (这是程序中重要用的类)
Appender - 日志目的地,把格式化好的日志信息输出到指定的地方去 (看看配置文件log4j.properties就应该好理解了)
ConsoleAppender - 目的地为控制台的 Appender
FileAppender - 目的地为文件的 Appender
RollingFileAppender - 目的地为大小受限的文件的 Appender
Layout - 日志格式化器,用来把程序员的 logging request 格式化成字符串
PatternLayout - 用指定的 pattern 格式化 logging request 的 Layout

Logger树(Logger Hierarchy)

Name为Com.foo的Log ger是name为com.foo.Bar的parent,root Logger始终在树的root,
Invoke static Logger.getRootLogger可以得到RootLogger,其他的Logger可以通过Logger.getLogger()方法来获取.
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);

}

Level 继承

如果一个Logger没有设置level,那他会默认继承离他最近的祖先的日志level.
即:一个没有设置level的Logger 会以他向root查找到的第一个非null level为准.
看几个官方例子:
Example 1
这里写图片描述

Example 1 只有RootLogger设置了一个日志level,其他的Logger都会继承它.
Example 2
这里写图片描述
Example2 每个logger都有level,不需要继承法则.
Example 3
这里写图片描述
其他都有只有X.Y没有往上继承X的level.
这里写图片描述
Example 4
X.Y和X.Y.Z往上都继承最近的祖先X的level.

四则表格显示的很清楚了。

Logger 打印法则

在level q下一个level p的log请求只有在p>=q时才有效.
Basic Selection Rule
A log request of level p in a logger with (either assigned or inherited, whichever is appropriate) level q, is enabled if p >= q.
下面代码演示了该rule

   // 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");

Logger命名法则

根据当前类的全限定名命名logger是目前最好的命名方式
.
private final static Logger logger=Logger.getLogger(XXX.class);

Appender法则

一个Logger可以通过addAppender添加多个Appender,一个appender对应一个Layout
上图了:
这里写图片描述

Appender法则:
一个logger c 会把有效的日志请求输出到它自身所有的Appender和他所有祖先的Appender,这是Appender法则.祖先可以设置additivity flag来拒绝.

Appender Additivity
The output of a log statement of logger C will go to all the appenders in C and its ancestors. This is the meaning of the term “appender additivity”.
However, if an ancestor of logger C, say P, has the additivity flag set to false, then C’s output will be directed to all the appenders in C and its ancestors upto and including P but not the appenders in any of the ancestors of P.
Loggers have their additivity flag set to true by default.
这里写图片描述

简易配置原理

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.");   } }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之后,Logger对象树的结构如下:
这里写图片描述

Log4J初始化

Log4j初始化的代码是在LogManager的静态块里面,这个静态块无论如何都会实例化一个日志级别为DEBUG的RootLogger,并且初始化一个以这个RootLogger为根节点的级联结构,然后检查有没有用户指定重写这个日志系统的初始化工作,如果没有,那么先去找log4j.xml,如果没有找到,那么再去找log4j.properties(也就是log4j.xml的优先级高于log4j.properties), 只有找到这两个配置文件的其中一个,再初始化文件里面内容,主要是一些配置文件中指定的Logger初始化,以及各个Logger的Appender列表设定,各个Appender的具体实例,Layout等。其实,没找到任何log4j配置文件也没什么关系,因为已经有RootLogger,日志系统骨架已经完成了,所以也可以通过如前面的代码添加Logger。

static {    // By default we use a DefaultRepositorySelector which always returns 'h'.    Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));    repositorySelector = new DefaultRepositorySelector(h);    /** Search for the properties file log4j.properties in the CLASSPATH.  */    String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,                               null);    // if there is no default init override, then get the resource    // specified by the user or the default config file.    if(override == null || "false".equalsIgnoreCase(override)) {      String configurationOptionStr = OptionConverter.getSystemProperty(                              DEFAULT_CONFIGURATION_KEY,                               null);      String configuratorClassName = OptionConverter.getSystemProperty(                                                   CONFIGURATOR_CLASS_KEY,                            null);      URL url = null;      // if the user has not specified the log4j.configuration      // property, we search first for the file "log4j.xml" and then      // "log4j.properties"      if(configurationOptionStr == null) {      url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);    if(url == null) {      url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);    }      } else {    try {      url = new URL(configurationOptionStr);    } catch (MalformedURLException ex) {      // so, resource is not a URL:      // attempt to get the resource from the class path      url = Loader.getResource(configurationOptionStr);     }         }      // If we have a non-null url, then delegate the rest of the      // configuration to the OptionConverter.selectAndConfigure      // method.      if(url != null) {        LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");        try {            OptionConverter.selectAndConfigure(url, configuratorClassName,                       LogManager.getLoggerRepository());        } catch (NoClassDefFoundError e) {            LogLog.warn("Error during default initialization", e);        }      } else {        LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");      }    } else {        LogLog.debug("Default initialization of overridden by " +             DEFAULT_INIT_OVERRIDE_KEY + "property.");     }    }Xml和Prop解析不同   if(clazz != null) {     LogLog.debug("Preferred configurator class: " + clazz);     configurator = (Configurator) instantiateByClassName(clazz,                              Configurator.class,                              null);     if(configurator == null) {      LogLog.error("Could not instantiate configurator ["+clazz+"].");      return;     }   } else {     configurator = new PropertyConfigurator();   }   configurator.doConfigure(url, hierarchy);

性能

Log4J引起较多争论的是他的性能问题,用户比较在乎的问题:
1 Logger当前输出级别已失效时的性能问题.
如下代码
logger.debug(“Entry number: ” + i + ” is ” + String.valueOf(entry[i]));
在已经明知无效的情况下会引起隐形的性能消耗,组装日志信息的时间,这个消耗随着参数的复杂程度是可以非常高的.
最佳实践:
if(logger.isDebugEnabled() {
logger.debug(“Entry number: ” + i + ” is ” + String.valueOf(entry[i]));
}
2 往上寻找日志级别的消耗
在我们的应用中,每个业务代码里的类的Logger实际上本身是没有定义Level,Appender的,它们需要在判断level时去搜寻祖先Logger,这个耗时是在直接关闭情况下的3倍以上.
3 输出时格式话的消耗
在日志输出时Formatters格式化日志输出的消耗也是很大的,Logging普遍的耗时在100ms到300ms.

0 0