log4j源码解析

本文详细解析了log4j的核心组件,包括Logger、Appender、Layout及其工作原理。介绍了日志级别,如error、warn、info、debug等,并解释了如何通过源码分析log4j的配置加载过程。同时,探讨了RollingFileAppender的日志备份策略,以及SSH与log4j整合的优势,如动态改变日志级别和路径配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一)几个基本概念

Logger - 日志写出器,供程序员输出日志信息
Appender - 日志目的地,把格式化好的日志信息输出到指定的地方去
ConsoleAppender - 目的地为控制台的Appender
FileAppender - 目的地为文件的Appender
RollingFileAppender - 目的地为大小受限的文件的Appender
Layout - 日志格式化器,用来把程序员的logging request格式化成字符串
PatternLayout - 用指定的pattern格式化logging request的Layout

日志级别

每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分为:
A:off         最高等级,用于关闭所有日志记录。
B:fatal       指出每个严重的错误事件将会导致应用程序的退出。
C:error      指出虽然发生错误事件,但仍然不影响系统的继续运行。
D:warm     表明会出现潜在的错误情形。
E:info         一般和在粗粒度级别上,强调应用程序的运行全程。
F:debug     一般用于细粒度级别上,对调试应用程序非常有帮助。
G:all           最低等级,用于打开所有日志记录。

上面这些级别是定义在org.apache.log4j.Level类中。Log4j只建议使用4个级别,优先级从高到低分别是 error,warn,info和debug。通过使用日志级别,可以控制应用程序中相应级别日志信息的输出。例如,如果使用了info级别,则应用程 序中所有低于info级别的日志信息(如debug)将不会被打印出来。

 

(二)源码解析

 

基本步骤:

1、LogManager类对log4j.xml或者log4j.properties文件进行解析(xml文件优先,若存在xml文件则不对properties文件进行解析)

 

2、逐个解析配置文件中的logger对象,并将这些对象放入一个Hierarchy的实例中(Hierarchy类中定义了一个hashtable来存储所有的logger)

      a、取得RootLogger

      b、取得RootLogger对应的Appender (必须在配置文件中指明rootLogger的Appender)

      c、取得Appender的Layout,将Layout追加到Appender中 

      d、将Appender追加到RootLogger中

      e、将RootLogger添加进Hierarchy中

      f、依照上面abcde的步骤将其他logger添加进Hierarchy中

 

3、通过LogManager.getLogger(String name)或LogManager.getLogger(Class clazz)获取获取到指定的logger对象(事实上,传递进去的clazz也会被转换为字符串,不过要加上它的包名,如Test.Class-->com.test.Test)

      a、通过传递进来的参数,判断Hierarchy中是否已经含有此logger;(通过hashtable.get(key)获取再判断)

      b、若该logger已经存在于Hierarchy中则将Hierarchy的logger返回

      c、通过new Logger(String name)或new Logger(Class clazz)创建一个新的logger,设置这个logger的parent属性,将此对象放入Hierarchy中并返回    

//为使代码更简洁明了,此段代码我做了部分的删减
private final void updateParents(Logger cat)
    {
        String name = cat.name;
        int length = name.length();
        boolean parentFound = false;
        //由此段代码可看出,这里是根据包名判定父节点的
        for(int i = name.lastIndexOf('.', length - 1); i >= 0; i = name.lastIndexOf('.', i - 1))
        {
            String substr = name.substring(0, i);
            CategoryKey key = new CategoryKey(substr);
            Object o = ht.get(key);
            if(o instanceof Category)
            {
                parentFound = true;
                cat.parent = (Category)o;
                break;
            }
        }
        //若没找到父节点,则默认其父节点为root
        if(!parentFound)
            cat.parent = root;
    }

 

4、通过logger的error(Object message, Throwable t)等方法(包括error,warn,info,debug)输出日志

      a、判断你要输入的日志级别是否高于该logger的level,若高于,则调用该logger的各个appender输出日志,若低于,则不进行输出

           注:如果没有设置日志记录器(Logger)的级别,那么它将会继承最近的祖先的级别。 

//获取有效的日志级别
//若该logger没有定义level则使用其最近的祖先定义的level
// Loger类继承于Category 
   public Level getEffectiveLevel()
    {
        for(Category c = this; c != null; c = c.parent)
            if(c.level != null)
                return c.level;

        return null;
    }

      b、逐个调用该logger的appender输出日志

           注:appender中也有级别限制,只有满足这个限制的日志才能被输出

     c、 若该logger的additive属性为true,则调用父类的appender输出日志(additive属性默认为true)

 public void callAppenders(LoggingEvent event)
    {
        category;
        JVM INSTR monitorexit ;
        continue;
        exception;
        throw exception;
        int writes = 0;
        //由以下代码可以看出如果additive属性为true则会逐层调用祖先的appender,直到root或者遇到additive属性为false的祖先
label0:
        for(Category c = this; c != null; c = c.parent)
        {
label1:
            {
                synchronized(c)
                {
                    if(c.aai != null)
                        writes += c.aai.appendLoopOnAppenders(event);
                    if(c.additive)
                        break label1;
                }
                break label0;
            }
        }

        if(writes == 0)
            repository.emitNoAppenderWarning(this);
        return;
    }

 

(三)关于日记文件的备份

RollFileAppender

当日志文件大小超出了指定值时,log4j会将我们之前存放在指定文件(如:normal.log)的日志备份(即转移)到一个新的日记文件(如normal_1.log)

DailyRollFileAppender

按指定的时间频道,将之前存放在指定文件(如:normal.log)的日志备份(即转移)到一个新的日志文件(如 normal_20120101.log)

在DailyRollingFileAppender中可以指定monthly(每月)、 weekly(每周)、daily(每天)、half-daily(每半天)、hourly(每小时)和minutely(每分钟)六个频度,这是通过为 DatePattern选项赋予不同的值来完成的。DatePattern选项的有效值为:

  • '.'yyyy-MM,对应monthly(每月)
  • '.'yyyy-ww,对应weekly(每周)
  • '.'yyyy-MM-dd,对应daily(每天)
  • '.'yyyy-MM-dd-a,对应half-daily(每半天)
  • '.'yyyy-MM-dd-HH,对应hourly(每小时)
  • '.'yyyy-MM-dd-HH-mm,对应minutely(每分钟)

PS:以上种方式是分别通过大小和时间进行日志的备份,如果在实际项目开发中需要根据实际条件指定新的备份方式,我们可以参照log4j.jar中的RollFileAppender和DailyRollFileAppender的源码,自己进行编写一个Appender类就行了

 

(四)SSH与Log4j整合的好处

1.配置文件(log4j.properties或者log4j.xml)可以不用放在class-path中,而可以让我们指定,一般我们会把该配置文件与项目中的其他配置文件放在一起

在web.xml中的详细设定如下:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>WEB-INF/log4j.properties</param-value>
</context-param>

 

2. 日志文件的存放路径可以用相对路径指定,比如说放在当前项目下的WEB-INF/logs文件夹中,而不需要用绝对路径定义日志存放位置

<context-param>
   <param-name>webAppRootKey</param-name>
   <param-value>web.root</param-value>
</context-param>
<!-- 需要添加spring-web.jar包-->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

 

这里涉及到了另一个知识点,Spring通过 org.springframework.web.util.WebAppRootListener 这个监听器将我们当前项目的所在路径配置进了系统的环境变量中,

也就是相当于在系统环境变量中配置了一个叫“web.root”的变量,这个变量指向了我们当前项目的物理路径。这样,我们在项目中就可以通过System.getProperty("web.root")来获取当前项目的物理路径,同时,我们也可以在log4j.properties 里这样定义logfile位置log4j.appender.logfile.File=${webapp.root}/WEB-INF/logs/mylog.log

 

如果在web.xml中已经配置了 org.springframework.web.util.Log4jConfigListener
这个监听器,则不需要配置WebAppRootListener了。因为Log4jConfigListener已经包含了WebAppRootListener的功能

部署在同一容器中的Web项目,要配置不同的<param-value>,不能重复,因为这个值事实上就是相当于path环境变量中的一个key,key自然不能重复。

 

3、动态的改变记录级别和策略,即修改log4j.properties,不需要重启Web应用,这需要在web.xml中设置自动扫描log4j文件的间隔(每隔一段时间刷新一次配置)

在web.xml中的配置如下:
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>

 

补充扩展

              正式上线的项目中应尽量少用system.out.println(),因为该语句会把内容打印到tomcat默认的日志文件中,不便于日志的管理,而且容易占据系统资源;

              hibernate自带的showsql功能也是默认将对应的sql语句输出到tomcat默认的日志文件中,hibernate的日志输出也是依赖于log4j实现的,所以我们也可以通过配置将该日志输出控制起来。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值