Search This Blog

2013-05-02

说log4j 1.2.x

目录  [+]
网络上关于Log4j的文章已经很多了,这算是一篇补充文章,最好事先对Log4j有所了解。
这里有一篇讲Log4j配置的文章,描述很简洁:配置Log4j - Author: zJun - www.blogjava.net。同样的内容尽量不再介绍。
也可以看看Java Logging使用经验
下面提及的Log4j都是指1.2版本,源码是apache-log4j-1.2.16-src。

1 LoggerRepository, logger, appender, layout

Log4j has three main components: loggers, appenders and layouts. These three types of components work together to enable developers to log messages according to message type and level, and to control at runtime how these messages are formatted and where they are reported.
--引用自http://logging.apache.org/log4j/1.2/manual.html

一个配置文件可以有多个logger,logger可以继承,rootLogger必须存在。每个logger可以有多个Appender,真正记录日志的是Appender,即一条日志可以输出到多个目标位置,Appender有多种预置实现,通常都可以配置属性。每个Appender有一个layout,layout有多种预置实现,通常都可以配置属性。
这些关系都保存在LoggerRepository,默认使用的是Hierarchy这个实现类,这是在LogManager写死的。
static {
    Hierarchy h = new Hierarchy(new org.apache.log4j.spi.RootLogger((Level) Level.DEBUG));
    repositorySelector = new DefaultRepositorySelector(h);
    ...
    // 加载配置文件,搜索顺序是system property "log4j.configuration"代表的文件 -> "log4j.xml" -> "log4j.properties";
    // 之后通过PropertyConfigurator/DOMConfigurator类进行配置。
    OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository());
}

2 解析及配置原理

)PropertyConfigurator/DOMConfigurator类有一些parse/configure方法。对某一部分parse完成之后,Appender通过org.apache.log4j.Category.addAppender(Appender newAppender)设置,layout通过org.apache.log4j.Appender.setLayout(Layout layout)设置。

)Appender/layout的属性通过org.apache.log4j.config.PropertySetter.setProperty方法设置,用到了java.beans.Introspector.decapitalize, java.beans.PropertyDescriptor.getWriteMethod之类的方法,这些方法是通过反射来完成工作的。调用的都是set方法,所有set方法对应的属性都可以配置。比如FileAppender.bufferedIO、PatternLayout.conversionPattern,bufferedIO、conversionPattern就是属性名,第一个字符不区分大小写。

3 配置

3.1 基本配置

"log4j.rootLogger" / "log4j.rootCategory" = levelName + ',' + appender1 + ',' + ... + appenderN
    这是配置root logger。

"log4j.logger" + loggerName = levelName + ',' + appender1 + ',' + ... + appenderN
    这是配置普通logger,有特殊需要的时候配置,不配置就继承自rootLogger;
    还可以通过loggerName体现继承关系,就像Java的package;
    一般都是用clazz.getName()作为loggerName,在给依赖的库做单独配置的时候就需要用到。

"log4j.appender." + appenderName + ".layout" = layoutClassName
    这是设置layout。还可以继续设置layout的属性。

3.2 变量替换

所有的选项都允许变量替换。使用方式:${system_property}。比如,${java.home}会被替换为Java安装目录。
可以利用这个功能给日志文件设置一个自定义的保存路径,给java增加启动选项-Dmyapp.path={absolute_path},修改配置文件:log4j.appender.R.File=${myapp.path}/logs/myapp.log

3.3 把日志发送到指定邮箱

严重错误最好发送邮件通知,可以通过org.apache.log4j.net.SMTPAppender来做,这个类需要JavaMail API,另外,Since 1.2.16, SMTP over SSL is supported by setting SMTPProtocol to "smtps"。

3.4 关闭日志、强制输出缓存

org.apache.log4j.LogManager.shutdown()方法可以关闭日志,同时强制输出缓存的日志并且清除所有的appenders。
如果发现什么问题,可以看看http://logging.apache.org/log4j/1.2/faq.html#unload

3.5 在运行状态改变日志配置

测试代码见附录:测试代码

)配置
把配置文件放在自定义的目录,比如${myapp.path}/conf/log4j-reloadable.properties,禁用自动加载,自行加载,通过PropertyConfigurator/DOMConfigurator.configure自行配置。
在管理员控制台提供整个配置文件的内容,允许任意修改。修改完之后点击提交。
代码里面先调用LogManager.shutdown();,再调用PropertyConfigurator/DOMConfigurator.configure。
配置成功的话,把新的配置写到配置文件里边(注意原子性以及失败恢复);失败的话,不做任何操作,回到编辑页面,给出错误提醒。

)注意
不能直接通过PropertyConfigurator/DOMConfigurator.configure在运行状态改变配置,否则会有问题。因为configure方法是直接new了一个新的实例并配置,所以每调用一次configure方法就会增加每条日志的输出次数。configureAndWatch方法也会有类似的问题,因为相应的XyzWatchdog.doOnChange()也是这样处理的。
可以看相应源码或者http://logging.apache.org/log4j/1.2/faq.html#duplicate-messages

3.6 定制log4j

)自定义LoggerFactory,通过makeNewLoggerInstance方法返回自定义的Logger类;
  • log4j.loggerFactory = loggerFactoryClassName。这是设置自定义的loggerFactory,如果有自定义的属性,那通过"log4j.factory." + loggerFactoryClassPropertyName = value设置。
  • 经测试,这个配置不好用,用LogManager.getLogger(String name, LoggerFactory factory)包装一个Logger.getLogger(String name)是没问题的。

)自定义Appender,比如写到MongoDB。
)自定义layout,比如输出为XML格式。

3.7 其他配置

)log4j.debug=true,会额外输出一些log4j本身的调试信息到console,便于对照代码。

)"log4j.additivity." + loggerName = true/false,通过org.apache.log4j.Category.setAdditivity(boolean additive)设置,不设置就是null。

3.8 深入阅读

Short introduction to log4j - Author: Ceki Gülcü - logging.apache.org
http://logging.apache.org/log4j/1.2/faq.html
http://logging.apache.org/log4j/1.2/apidocs/
http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html

4 附录:测试代码

4.1 ReloadTest.java

Pre[-]
package logging;

import org.apache.log4j.*;
import org.apache.log4j.spi.*;

public class ReloadTest {
    private static final Logger LOG = Logger.getLogger(ReloadTest.class.getName());

    public static void main(String[] args) {
        PropertyConfigurator.configure(ReloadTest.class.getResource("reloadtest.properties"));
        final LoggerRepository repository = LogManager.getLoggerRepository();
        LOG.info(repository);
        final Logger root = LogManager.getRootLogger();
        {
            LOG.info(loggerToString(root));
            LOG.trace("trace1 before set level");
            root.setLevel(Level.TRACE);
            LOG.trace("trace2 after set level");
            root.setLevel(Level.DEBUG);
        }
        printLoggers();
        {
//            repository.resetConfiguration();
//            repository.shutdown();
            LogManager.shutdown();
            LOG.info("info1 after LogManager.shutdown");
            PropertyConfigurator.configure(ReloadTest.class.getResource("reloadtest.properties"));
            LOG.info("info2 after PropertyConfigurator.configure");
        }
        printLoggers();
        LOG.info("last info");
    }

    private static void printLoggers() {
        LOG.info("printLoggers============");
        java.util.Enumeration iter = LogManager.getCurrentLoggers();
        Logger log;
        while (iter.hasMoreElements()) {
            log = (Logger) iter.nextElement();
            LOG.info(loggerToString(log));
        }
    }

    private static String loggerToString(Logger log) {
        return String.format("{log: name=%s, level=%s, className=%s}", log.getName(), log.getLevel(), log.getClass().getName());
    }
}

4.2 reloadtest.properties

放到ReloadTest.java所在的目录,内容来自http://logging.apache.org/log4j/1.2/manual.html,略有改动。
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
log4j.logger.com.foo.bar=DEBUG

#log4j.debug=true

5 附录:Log4j所有可用的appender/layout属性

Log4j manual没有列出所有的可配置属性,没有完整的例子。Log4j的作者写了一本书,是完整的教程。
下面的属性是通过 /tool-java/blob/master/src/Log4jPropertiesDumper.java 生成的。

5.1 属性简介

)有些属性是不需要设置的,比如writer。PropertySetter.convertArg(String val, Class type)方法会把字符串转换为别的数据类型,但是类型很有限,所以很多属性都是转换不出来的类型,以至于无法设置。当然,有些数据不需要在convertArg方法里转换,比如RollingFileAppender的maxFileSize属性,set方法接受的是字符串,在set方法里边转换可能出现的KB-MB-GB。

)有些属性是相同的含义,不需要都设置,比如immediateFlush跟bufferedIO在FileAppender是一个意思,只需要设置其中一个。

)这个列表只是方便简易浏览,每个属性允许的值没有列出来,比如ConsoleAppender的target属性只能接受"System.out"跟"System.err",这些得参考JavaDoc或者源码。

)挑几个比较公用的说一下:
Appender接口
    errorHandler
    layout
    name
AppenderSkeleton抽象类
    threshold 就是Level
WriterAppender 不是抽象类,主要用于继承
    encoding 字符集
    immediateFlush 立即强制输出
    writer 没用,我是设置不了
FileAppender
    append
    bufferedIO
    bufferSize
    file

5.2 可用的appender属性

Pre[-]
org.apache.log4j.AsyncAppender
    blocking
    bufferSize
    errorHandler
    layout
    locationInfo
    name
    threshold

org.apache.log4j.ConsoleAppender
    encoding
    errorHandler
    follow
    immediateFlush
    layout
    name
    target
    threshold
    writer

org.apache.log4j.DailyRollingFileAppender
    append
    bufferSize
    bufferedIO
    datePattern
    encoding
    errorHandler
    file
    immediateFlush
    layout
    name
    threshold
    writer

org.apache.log4j.FileAppender
    append
    bufferSize
    bufferedIO
    encoding
    errorHandler
    file
    immediateFlush
    layout
    name
    threshold
    writer

org.apache.log4j.RollingFileAppender
    append
    bufferSize
    bufferedIO
    encoding
    errorHandler
    file
    immediateFlush
    layout
    maxBackupIndex
    maxFileSize
    maximumFileSize
    name
    threshold
    writer

org.apache.log4j.WriterAppender
    encoding
    errorHandler
    immediateFlush
    layout
    name
    threshold
    writer

org.apache.log4j.jdbc.JDBCAppender
    URL
    bufferSize
    driver
    errorHandler
    layout
    locationInfo
    name
    password
    sql
    threshold
    user

org.apache.log4j.lf5.LF5Appender
    callSystemExitOnClose
    errorHandler
    layout
    maxNumberOfRecords
    name
    threshold

org.apache.log4j.net.JMSAppender
    URLPkgPrefixes
    errorHandler
    initialContextFactoryName
    layout
    locationInfo
    name
    password
    providerURL
    securityCredentials
    securityPrincipalName
    threshold
    topicBindingName
    topicConnectionFactoryBindingName
    userName

org.apache.log4j.net.SMTPAppender
    SMTPDebug
    SMTPHost
    SMTPPassword
    SMTPPort
    SMTPProtocol
    SMTPUsername
    bcc
    bufferSize
    cc
    errorHandler
    evaluator
    evaluatorClass
    from
    layout
    locationInfo
    name
    replyTo
    sendOnClose
    subject
    threshold
    to

org.apache.log4j.net.SocketAppender
    advertiseViaMulticastDNS
    application
    errorHandler
    layout
    locationInfo
    name
    port
    reconnectionDelay
    remoteHost
    threshold

org.apache.log4j.net.SocketHubAppender
    advertiseViaMulticastDNS
    application
    bufferSize
    errorHandler
    layout
    locationInfo
    name
    port
    threshold

org.apache.log4j.net.SyslogAppender
    errorHandler
    facility
    facilityPrinting
    header
    layout
    name
    syslogHost
    threshold

org.apache.log4j.net.TelnetAppender
    errorHandler
    layout
    name
    port
    threshold

org.apache.log4j.nt.NTEventLogAppender
    errorHandler
    layout
    name
    source
    threshold

org.apache.log4j.varia.ExternallyRolledFileAppender
    append
    bufferSize
    bufferedIO
    encoding
    errorHandler
    file
    immediateFlush
    layout
    maxBackupIndex
    maxFileSize
    maximumFileSize
    name
    port
    threshold
    writer

org.apache.log4j.varia.NullAppender
    errorHandler
    layout
    name
    threshold

5.3 可用的layout属性

Pre[-]
org.apache.log4j.EnhancedPatternLayout
    conversionPattern

org.apache.log4j.HTMLLayout
    locationInfo
    title

org.apache.log4j.PatternLayout
    conversionPattern

org.apache.log4j.SimpleLayout

org.apache.log4j.TTCCLayout
    categoryPrefixing
    contextPrinting
    dateFormat
    threadPrinting
    timeZone

org.apache.log4j.xml.XMLLayout
    locationInfo
    properties

=文章版本=

2013040x
20130501 修改一下格式,顺便增减一部分内容
20130506 改为html格式

No comments:

Post a Comment