log4j 配置与基本架构(-)

转载自:https://www.cnblogs.com/2015zzh/articles/4733756.html

在我们项目中,日志是必不可少的一个组件,平常我们也没去关注它的具体功能,只是用的时候引入一个默认的配置文件,修个几个参数,就能用了。其实它提供的功能远不止于此。

  在我一个项目中,输入数据接入与处理模块,数据量很大。但是有时候不可避免的会出现某些问题。一般思路就是在其中插入接收与处理日志,通过打印出来的日志来定位问题。这个不失为

一个好方案,但是接入与处理数据太大,如果为每条记录都打印一条日志,那样日志文件会超大,可能一两天就把硬盘撑爆了。而且那么大的日志,也很难去找的具体的问题。那么我们希望怎样

才能达到我们的要求呢?  第一,我们希望平时,只打印一般日志或错误日志。第二,当出现问题时,我们又希望能够查看此时的具体信息。我解决思路是这样,把需要详细显示的信息配置为

debug级别,其余配置为info及以上级别。另一个就是要能实时修改日志级别,能够在不重启服务下控制日志级别(如果重启服务,有可能错误不会重现,也不真实)。

  做项目因为考虑进度问题,遇到问题时首先想到的事现有的框架或工具能否解决问题。spring中提供了一个log4jLoggerListener的一个监听器,能够实时监听log4j配置文件的变化,并重新

加载,但是这个是用于web项目中,我做的项目属于一般的服务程序,该工具类用不了。但是刷新日志的逻辑应该可以模仿。spring的log4jLoggerListener是检测文件的变化,并重新加载配

置,那么我可以再服务中开启一个定时器,每过几秒去读取一下配置文件,如果发现和原先的不一样,那么重新加载,基于这个逻辑,我写了一个demo,果然,完美的实现了。附上部分代码

  

复制代码

扫描二维码关注公众号,回复: 3421514 查看本文章
package com.jsits.bus.timer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

import org.apache.log4j.PropertyConfigurator;
import org.springframework.util.ResourceUtils;
import org.topteam.esb.util.UrlUtil;

import com.jsits.bus.Main;

public class Log4jListener {
    
    /**
     * 日志配置文件
     */
    private static String log4jSetFile ;
    
    /**
     * 上次日志等级
     */
    private String lastLoggerLevel = "info";
    static{
        try {
            File f = ResourceUtils.getFile(UrlUtil.url2Utf8(UrlUtil
                    .getDirFromClassLoader(Main.class))
                    + File.separatorChar
                    + "conf" + File.separatorChar + "log4j.properties");
            log4jSetFile = f.getPath();            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 刷新当前日志等级
     */
    public void refresh()
    {
        if(isModify())
        {
            loadLog4jConfig();
        }
    }
    
    /**
     * 判断日志等级是否修改
     * @return
     */
    private boolean isModify()
    {
        try
        {
            String currentLeve = getLoggerLevel().toLowerCase();
            if(!lastLoggerLevel.equals(currentLeve))
            {
                lastLoggerLevel= currentLeve;
                return true;
            }
            else {
                return false;
            }            
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return false;
        }
                
    }
    
    /**
     * 获取当前日志等级
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     */
    private String getLoggerLevel() throws FileNotFoundException, IOException
    {
        Properties properties = new Properties();
        properties.load(new FileInputStream(log4jSetFile));
        String rootLogger = properties.getProperty("log4j.logger.com.jsits.bus");
        String level = rootLogger.split(",")[0];
        return level;
    }
    
    /**
     * 加载日志配置
     */
    private void loadLog4jConfig()
    {
        PropertyConfigurator.configure(log4jSetFile);
    }

}

复制代码

 从这行代码中 String rootLogger = properties.getProperty("log4j.logger.com.jsits.bus");我这里只监听log4j.logger.com.jsits.bus 参数的变化。

这样做解决了动态调度日志的一个问题。但是把日志级别设成debug,也带来别的问题。那就是项目是集成了很多框架的,每个框架都有大量的debug日志。就比如httpClient中,他会打印接受信息的所有信息,这个

太头痛了,我们得从一大堆的日志文件中,找出自己想要的那条日志问题,这对分析太不利了。那么我们想要的是,日志只打印出自己想看到的日志。这个有两个解决思路:

 1,把自己想打印的日志所在的包设置为自己定的级别(我那个是debug级别),把不想打印出的日志所在包的级别定位info或更高级的级别。如下面:

1

2

3

4

5

log4j.logger.com.jsits.bus=debug,RECORD

log4j.logger.org=error 

log4j.logger.akka=error

log4j.logger.com.alibaba=error

  该设置把自己需要打印日志的包com.jsits.bus定位了debug级别。把org,akka,alibaba包的日志级别定位了error。这样输出的日志就只有你需要的日志。

  2,把自己想要打印的日志单独输出到一个文件中。那么在那个文件中就只看到你包中的的日志信息,然后把其他的日志信息的输出appender的threshold设为error。如下面:

复制代码

log4j.logger.com.jsits.bus=debug,RECORD 
log4j.rootLogger=error,R

log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File=logs/log
log4j.appender.R.Encoding=UTF-8
log4j.appender.R.Threshold=error
log4j.appender.R.DatePattern='-'yyyy-MM-dd HH'.log'
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=[%t] %d %-5p - %m(%F.%M:%L)%n 
log4j.appender.file.encoding=UTF-8

log4j.appender.RECORD=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RECORD.File=logs/recordLog
log4j.appender.RECORD.Encoding=UTF-8
log4j.appender.RECORD.DatePattern='-'yyyy-MM-dd HH'.log'
log4j.appender.RECORD.layout=org.apache.log4j.PatternLayout
log4j.appender.RECORD.layout.ConversionPattern=[%t] %d %-5p - %m(%F.%M:%L)%n 
log4j.appender.file.encoding=UTF-8

复制代码

  解决了上面两个问题,我对log4j稍微有点兴趣,于是下了哥源码,看了一下架构设计,尽管没全部看懂,不过对几个基本的实现有一点的理解

  先附上日志的几个关键类图。

                                          

  我们在配置中接触到的主要有logger,Appender,layout,Level,关系为,每个logger可以定义自己的Level,并且可以配置一个或多个Appender,对于appener又可以配置自己的layout和Level..通过类图对此一一解析。

  顶层是几个抽象类:Priority,Category,AppenderSkeleton,Layout.

  

Priority :是Level的父类,类中对debug,info,error..各个级别做了定义及实现。

Layout:是所有布局的抽象父类,主要规定了layout类所必须实现的format方法。

AppenderSkeleton:从它的几个变量name,threshold,errorHandler,headFilter,tailFilter,layout中,它规定了实现类中几个关键组件,对于每个appender,可以配置他的threshold(即打印日志的最低级别),该日志的打印方式layout,对于errorhandler,headFilter,tailFilter,系统有默认配置,没去深究。

Category:是Logger的父类,可以理解为日志中的目录结构,有一个属性parent,代表他的上一级,它是和项目中的包相对应的,包中的每一层级对应唯一一个category,比如 com.bus.对应的也会有一个Category名称为com.bus,通过Category,可以配置每个包日志的级别,每个包日志打印的appender,并提供各种日志级别的打印操作。

出了几个关键的父类外,还有两个类也是极其重要的,一个是我们常用的loggerManager,另一个是Hierarchy。类图如下:

 

LoggerManager使用是当静态类使用,比如获取logger,以及初始化,同时LoggerManage在第一次调用的时候会加载log4j配置文件(log4j.xml或者log4j.properties)。是对外日志获取的一个入口。

Hierarchy类可以理解为层级结构的一个管理,或者是日志的一个管理容器。Hierarchy有几个主要属性: Hashtable ht, Logger root, RendererMap rendererMap;
其中Logger root,则代表日志的根节点。
HasTable ht 是一个Logger的管理容器,里面存放的是日志名称和对应日志的的键值对。在初始化日志配置时填充。
RendererMap rendererMap是一个Appender的管理容器,存放内容为日志名称和他对应的appender集合的键值对,在初始化日志配置时填充
该Hierarchy也可以配置threshold等参数,可以看出来在日志配置中,Hierarchy是一个核心管理器,loggerManager是一个入口,要获取某个目录,某个日志的相关配置,则需要从Hierarchy中获取。

通过对log4j中主要类的分析,我们能够大概了log4j的流程与设计。本文章主要描述的是日志的一个结构问题,对于日志写入过程的事件模型还没去细看,后面会及时更新。

********************************************************************

  上一篇主要介绍了log4j的基本架构,对于开发者来说,其实我们更关注具体的使用。log4j的配置很灵活,基本可以实现你所想的效果。下边我就结合log4j架构来分析和解释配置的具体含义。

  log4j配置有两种形式,第一种是xml格式,默认名称为log4j.xml.另一种是log4j.properties,我们项目中用后者就多,其实配置都是一样, 表现形式不同,下边就properties格式举例说明。

  首先配置根目录

    配置语法为:log4j.rootLogger=[Level],appender1,appender2

    如:log4j.rootLogger=error,CONSOLE,R

    所有的日志都是它的子类。如果没对具体子类设置日志级别,那就默认为debug的日志级别。

  同样除了根目录外,你可以对你某个包定义一个日志。

    配置语法为:log4j.[package]=[Level],appender1,appender2\

    如:log4j.logger.com.jsits=debug,RECORD

  Appender配置:

   Appender可以理解为日志写到哪个地方。  

   其语法为: 

      log4j.appender.appenderName = fully.qualified.name.of.appender.class 
      log4j.appender.appenderName.option1 = value1 
      log4j.appender.appenderName.optionN = valueN 

    如:

      log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.R.File=logs/log               //设置输出文件
      log4j.appender.R.Encoding=UTF-8         //设置输出编码格式
      log4j.appender.R.Threshold=error         //设置输出最低级别
      log4j.appender.R.DatePattern='-'yyyy-MM-dd HH'.log' //设置输出文件的组织格式(该格式为每一小时产生一个日志文件)
      log4j.appender.R.layout=org.apache.log4j.PatternLayout //设置layout
      log4j.appender.R.layout.ConversionPattern=[%t] %d %-5p - %m(%F.%M:%L)%n //设置日志样式
      log4j.appender.file.encoding=UTF-8 

    其中, Log4j 提供的 appender 有以下几种: 
      org.apache.log4j.ConsoleAppender (控制台), 
      org.apache.log4j.FileAppender (文件), 
      org.apache.log4j.DailyRollingFileAppender (每天产生一个日志文件), 
      org.apache.log4j.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新的文件),可通过 log4j.appender.R.MaxFileSize=100KB 设置文件大小,还可通过        log4j.appender.R.MaxBackupIndex=1 设置为保存一个备份文件。 
      org.apache.log4j.WriterAppender (将日志信息以流格式发送到任意指定的地方)

     Layout 
      其中, 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{yyyy MMM dd HH:mm:ss,SSS} ,输出类似: 2002 年 10 月 18 日 22         : 10 : 28 , 921 
      %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数

    主题配置大概这样。希望有所帮助

猜你喜欢

转载自blog.csdn.net/hchhan/article/details/82728012
今日推荐