Log4j代码随读

最近需要用到log4j动态定制Logger的场景,然后加上以前对于这个日志工具拿来就用而不知其原理的原因,所以决定花点时间看下它的源码,如果你还对log4j如何使用感到困惑,那么请首先简要浏览下它的官网 http://logging.apache.org/log4j/

Log4j总体来说是一个可定制,支持同时多种形式输出日志,并且高度结构化的日志库。可定制,也就是既可以通过log4j.properties或者log4j.xml定义日志输出的级别(Level),形式(Appender)以及文本格式(Layout),也可以通过Logger类或者LogManager类取得Logger实例,并且设置日志输出级别(Level),形式(Appender)以及文本格式(Layout),可以说是相当的简便与灵活。下面一段代码简单地说明了后者的实现。
Logger MY_LOG  =  Logger.getLogger( " MY_LOG " );

// 文件形式的输出方式实例化
DailyRollingFileAppender appender  =   new  DailyRollingFileAppender();
appender.setName(name);
appender.setAppend(
true );
appender.setEncoding(
" GBK " );
// 文本的输出格式采用PatternLayout
appender.setLayout( new  PatternLayout(pattern));
appender.setFile(
new  File(getLogPath(), fileName).getAbsolutePath());
appender.activateOptions();

// 将appender加入到MY_LOG的appender集合
MY_LOG.addAppender(appender);
// 设定日志输出级别为INFO
MY_LOG.setLevel(Level.INFO);

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

// 初始化以RootLogger为根的级联结构,rootLogger默认DEBUG级别
Hierarchy h  =   new  Hierarchy( new  RootLogger((Level) Level.DEBUG));
// 有没指定log4j.configuration(内部使用,不知出于什么目的,未探究)
if (configurationOptionStr  ==   null {
    
//加载classpath下log4j.xml
     url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
   
if(url == null{
        
//如果log4j.xml没有,加载log4j.properties
        url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
    }

}
  else   {
    
try {
    url 
= new URL(configurationOptionStr);
    }
 catch (MalformedURLException ex) {
    url 
= Loader.getResource(configurationOptionStr); 
    }

}
if (url  !=   null {
    LogLog.debug(
"Using URL ["+url+"] for automatic log4j configuration."); 
    
//开始真正解析配置文件     
    OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());
}
  else   {
    LogLog.debug(
"Could not find resource:["+configurationOptionStr+"].");
}

简要介绍完Log4J的初始化后,我们有必要来看下从Logger myLogger =LogManager. getLogger(“MY_ LOG”),到 myLogger.debug(“some message”)结束之后,Log4j到底为我们做了些什么。


  
上面这幅序列图主要描述的过程就是从LoggerManager取得一个Logger的过程,其中最重要的操作在Hierarchy类中,这个类说白了就是存储Logger的仓库,其内部使用一个HashMap ht来存储Logger和ProvisionNode。

这里需要解释下ProvisionNode,这个类是一个Vector的实现,之前我们谈到过初始化的一开始,以RootLogger为根节点的级联结构(就是Hierarchy实例),那么这个ProvisionNode想当于这个级联结构中的树节点,比如我在定义了一个名字为a.b.c的Logger,那么总共会生成”a”,”a.b”两个ProvisionNode,以及一个名字为”a.b.c”的Logger。Hierarchy并没有一个链表来维护他们之间的顺序,ProvisionNode会通过其本身就是向量的特性将属于它的Logger进行有序的排列,而Logger本身则通过parent属性记住他们的日志属性可以从哪里继承。

这样做的好处有两点,第一点就是只要名字相同,从LogManager中取出来的Logger就是同一个实例,第二点好处就是级联结构,可以定义出差异化的Logger,特别是其以a.b.c.d类似包结构的拆分日志节点,使得包级别的日志差异化输出更加的容易,并且这种特性提供了日志节点属性继承的功能。

举个例子,我们一般取得一个Logger实例是这样的, Logger log=LogManager.getLogger(Class class), Log4j处理方式为getLogger(class.getName()),这也就是说,这个Logger的名字是带包名的完全限定名字,所以如果我们通过名为a.b.c.aclass,a.b.c.d.bclass这么两个类取得Logger实例,然后定义名为a.b.c和a.b.c.d 2个Logger,那么总共会生成如下的节点 
            
            ProvisionNode: a,a.b
                    Logger: a.b.c(加入a,a.b 2个ProvisionNode中并且parent为RootLogger)
                             a.b.c.d(加入a,a.b 2个ProvisionNode中,并且parent为a.b.c)
                             a.b.c.aclass  (加入到a,a.b 2个ProvisionNode中,并且parent为a.b.c), 
                             a.b.c.d.bclass(加入到a,a.b 2个ProvisionNode中,并且parent为a.b.c.d)

其中ProvisionNode可能升级为Logger,当同名的Logger加入时,该ProvisionNode中所有以该ProvisionNode为父节点的子节点修改parent指向新的Logger.

final   private   void  updateChildren(ProvisionNode pn, Logger logger)  {
    
final int last = pn.size();
   
    
for(int i = 0; i < last; i++{
         Logger l 
= (Logger) pn.elementAt(i);
          
//有可能子节点的父节点指向更低一级的节点,比如孙节点。这个应该不难理解。
         
 if(!l.parent.name.startsWith(logger.name)) {
             logger.parent 
= l.parent;
             l.parent 
= logger;
        }

     }

}

这样Logger a.b.c除了自身的日志输出设置之外,还享受rootLogger的输出(输出级别,Appenders),Logger a.b.c.d同时享受rootLogger和a.b.c的设置,a.b.c.d.bclass享受rootLogger,a.b.c,a.b.c.d的日志输出设定,也就是可以分别定义一批Logger的输出级别和输出形式以及其他属性,当然也有控制开关控制这种继承。下图说明了一些问题。



取得Logger之后,随后就是需要按指定形式输出日志内容,首先需要在LoggerRepository中判定当前Level是否可以做日志输出,包括与全局的thresholdInt进行判定(相当于总开关,这个级别通不过直接返回,不做任何事情),通过后,再与其自身Logger日志级别比较,如果没有设定,查其父节点的日志级别,仍然没有设定,那么查父节点的父节点,直到查到有日志级别设定或者最终到RootLogger(其默认为Debug级别),比较通过后,就可以调用callDependers进行日志输出了。 

日志级别为 OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL,只有输出级别大于等于Logger自身级别才能进行输出,比如 logger.debug,那么只有该Logger的级别(也可能是其先辈节点的日志级别)DEBUG或者ALL才能允许被输出。全局的thresholdInt如果不设定是保持在ALL级别。

一般Logger会通过AppenderAttachableImpl的实例来维护多个Appender,并且可以共享父节点的Appender List(包括RootLogger里面定义的appenders,所以我们一般在log4j.xml或者log4j.properties里面定义一个某个包下的Logger,然后挂有几个Appender,而程序中通过完全限定的类名(这个类属于前面指定的包)取得Logger,那么当这些Logger输出日志的时候,其本身并没有任何Appender,但是却通过先辈节点定义的Appenders得以输出。这里需要注意的是,如果在直系节点间定义相同的appender,似乎会多次重复输出,因为其会遍历自身以及所有先辈节点的Appender list并且逐一进行doAppend调用,而不会去排重,并且addAppender的时候也没有遍历先辈节点排重。

Appender持有一个Filter链表,doAppend的时候首先走一遍过滤器,结果有3种,Filter.DENY(直接拒绝返回),Filter.ACCEPT(接收输出请求,并且不再走之后的Filter),Filter.NEUTRAL(继续执行下一个Filter),顺利通过Filter链之后,进入真正的输出日志过程,这边以WriteAppender为例,首先将message通过持有的Layout进行格式化(format),然后调用输出流输出日志到目标文件(FileAppender)或者屏幕(ConsoleAppender)。

至此,整个日志输出过程结束。

下面两幅图分别是Log4j的整体类图,不是非常完整,但是大概能够了解到整个结构。



总结下,log4J出来已经很多年了,以前只是使用下,并没有去探究里面机制,但其某些机制还是相当不错的。文中可能出现一些错误,请各位能够指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用 log4j2 的 PropertyConfigurator 进行日志配置的示例代码: ```java import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.PropertyConfigurator; public class MyClass { private static final Logger logger = LogManager.getLogger(MyClass.class); public static void main(String[] args) { // 加载 log4j2 配置文件 PropertyConfigurator.configure("log4j2.properties"); logger.debug("Debug level message"); logger.info("Info level message"); logger.warn("Warn level message"); logger.error("Error level message"); logger.fatal("Fatal level message"); } } ``` 其中,`PropertyConfigurator.configure("log4j2.properties")` 方法用于加载 log4j2 的配置文件,示例代码中配置文件名称为 `log4j2.properties`。 在配置文件中,可以设置不同的日志级别、输出格式、输出目标等。示例配置文件: ```properties # 设置日志级别为 debug log4j2.rootLogger.level = debug # 设置控制台输出 log4j2.appender.console.type = Console log4j2.appender.console.name = ConsoleAppender log4j2.appender.console.layout.type = PatternLayout log4j2.appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n # 设置文件输出 log4j2.appender.file.type = RollingFile log4j2.appender.file.name = FileAppender log4j2.appender.file.fileName = logs/myapp.log log4j2.appender.file.filePattern = logs/myapp-%d{yyyy-MM-dd}.log log4j2.appender.file.layout.type = PatternLayout log4j2.appender.file.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n log4j2.appender.file.policies.type = Policies log4j2.appender.file.policies.time.type = TimeBasedTriggeringPolicy log4j2.appender.file.policies.time.interval = 1 log4j2.appender.file.policies.time.modulate = true log4j2.appender.file.policies.size.type = SizeBasedTriggeringPolicy log4j2.appender.file.policies.size.size = 10MB log4j2.appender.file.strategy.type = DefaultRolloverStrategy log4j2.appender.file.strategy.max = 10 ``` 在此配置文件中,设置了日志级别为 debug,同时配置了控制台和文件输出目标,输出格式为 `%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n`。 其中,控制台输出使用了 `ConsoleAppender`,文件输出使用了 `RollingFile`,并且设置了文件输出的大小和数量限制。 通过这种方式,可以方便地配置 log4j2 的日志输出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值