Bootstrap

log4j1.2.17的使用与log4j.properties配置详解

                                    log4j.properties配置与使用

一、log4j简介

1、简介

   在开发中我们也许会经常使用到System.out.println(“xxx”)之类的代码来检测自己的程序,这些sysout就是日志记录信息,但使用System.out.println或者System.err.println在做日志的时候有其非常大的局限性。第一,所有的日志信息输出都是平行的(不能控制只输出某些日志)。第二,日志信息的输出不能统一控制(只能去注释代码)。第三,日志信息的输出方式是固定的(在编码期,就只能输出到eclipse的控制台上,在tomcat环境中就只能输出到tomcat的日志文件中)。Log4J就是在这种情况下诞生的,而现在Log4J已经被广泛的采用,成为了最流行的日志框架之一。

    但在2015年5月,Apache宣布Log4J 1.x 停止更新,并在2015年8月寿终正寝,最新版为1.2.17;2015年8月5日,Apache软件基金业宣布,Log4j不再维护,建议所有相关项目升级到Log4j2。 Apache Log4j 2是Log4j的升级版,对Log4j的前身Log4j 1.x进行了重大改进,并提供了Logback中可用的许多改进,同时解决了Logback体系结构中的一些固有问题。本篇,介绍 log4j1.2.17。

    log4j是使用java语言编写的可靠的、快速的、灵活的日志框架,它是基于Apache的license。log4j是一个功能强大的日志组件,提供方便的日志记录。可以在官网下载:https://archive.apache.org/dist/logging/log4j/   

2、log4j的配置步骤及结构简诉

    配置步骤:

    1)导入Log4J的包;

    2)完成Log4J的配置;

    3)对于每一个需要日志输出的类来说,通过Logger.getLogger方法得到一个专属于这个类的日志记录器。

    4)使用日志记录器完成日志信息的输出。

    输出日志示例:

package com.cn.log4j;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class log4jTest01 {

	private static Logger log1 = Logger.getLogger(logTest.class);
	
	public static void main(String[] args) {
		//我们使用了一个log4j中的基础配置器,并调用其configure()方法,完成log4j的配置。这里面完成了默认配置读取、输出目的地配置及输出格式的配置
		BasicConfigurator.configure();
		//设置日志等级:默认为DEBUG,源码中Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
		log1.setLevel(Level.ERROR);
		log1.trace("Trace Message!");
		log1.debug("Debug Message!");
		log1.info("Info Message!");
		log1.warn("Warn Message!");
		log1.error("Error Message!");
		log1.fatal("Fatal Message!");
	}

}

/**  configure()的源码
  *  Logger root = Logger.getRootLogger();
  *  root.addAppender(new ConsoleAppender(
  *  new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
  */

/**输出结果如下
 * 2020-10-17 18:51:40,573 [main] ERROR [com.cn.log4j.logTest] - Error Message!
 * 0 [main] ERROR com.cn.log4j.logTest  - Error Message!
 * 2020-10-17 18:51:40,575 [main] FATAL [com.cn.log4j.logTest] - Fatal Message!
 * 2 [main] FATAL com.cn.log4j.logTest  - Fatal Message!
 */

一句话:记录日志,其实就是规定,在什么地方用什么日志记录器以什么样的格式记录日志。

Log4j由三个重要的组件构成:

        1)Logger(记录器):日志类别和级别;

        2)Appender (输出源):日志要输出的地方;

        3)Layout(布局):日志以何种形式输出

        Logger-负责捕获日志信息。log4j定义了8个级别的log,用来指定这条日志信息的重要程度(除去OFF和ALL,可以说分为6个级别),优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。一般只使用ERROR、WARN、INFO、DEBUG这四个。

        Appender-负责输出信息到不同的目的地,日志信息的输出目的地指定了日志将打印到控制台还是文件中;

        Layout-负责使用不同的样式输出日志,输出格式则控制了日志信息的显示内容。

3、log4j级别说明及其使用原则

级别

说明

一般原则

OFF

关闭所有日志记录开关,是最高等级的

不推荐使用

FATAL

输出致命错误;指出每个严重的错误事件将会导致应用程序的退出

不推荐使用

ERROR

输出错误信息;

用于程序出错打印堆栈信息,不应该输出程序问题之外的其他信息,需要打印日志异常(Exception)就不应该抛(throw)了
WARN输出警告信息;表明会出现潜在错误的情形。系统能继续运行, 但是必须引起关注. 对于存在的问题一般可以分为两类: 一种系统存在明显的问题(比如, 数据不可用), 另一种就是系统存在潜在的问题, 比如系统参数配置不正确,或者某些消耗性能的场景。
INFO输出提示信息;消息在粗粒度级别上突出强调应用程序的运行过程。重要的业务逻辑处理完成. 在理想情况下, INFO的日志信息要能让高级用户和系统管理员理解, 并从日志信息中能知道系统当前的运行状态。一般比较没有风险的信息,如初始化环境、参数等
DEBUG输出调试信息;指出细粒度信息事件对调试应用程序是非常有帮助的主要用于调试、测试时使用,一般是在程序中不是非常重要的地方,这部分的日志较多,但是在线上运行时我们一般会将日志级别设置为INFO,避免DEBUG日志过多影响性能.
TRACE指定细粒度比DEBUG更低的信息事件系统详细信息, 主要给开发人员用, 一般来说, 如果是线上系统的话, 可以认为是临时输出, 而且随时可以通过开关将其关闭. 有时候我们很难将DEBUG和TRACE区分开, 一般情况下, 如果是一个已经开发测试完成的系统, 再往系统中添加日志输出, 那么应该设为TRACE级别
ALL打开所有日志记录开关,是最低等级的,包括自定义级别不推荐使用

注:程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少 。假设Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。

package com.cn.log4j;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class logTest {

	private static Logger logger = Logger.getLogger(logTest.class);  
	
	public static void main(String[] args) {
		logger.setLevel(Level.WARN);
        logger.trace("Trace Message!");
        logger.debug("Debug Message!");
        logger.info("Info Message!");
        logger.warn("Warn Message!");
        logger.error("Error Message!");
        logger.fatal("Fatal Message!");
    }  
}

/**
*输出结果如下:
09 十月 2020 17:21:04,683  WARN main  logTest:main - Warn Message!
09 十月 2020 17:21:04,687 ERROR main  logTest:main - Error Message!
09 十月 2020 17:21:04,687 FATAL main  logTest:main - Fatal Message!
*/

 上述案例中,把错误细粒度设置为:WARN,则WARN以及比WARN更高的Error、Fatal都已输出。

 日志输出的原则:

1)对程序的输入输出要以DEBUG记录下来,常包括从文件、数据库、网络、用户等输入的信息,向文件、数据库、网络输出的信息。

2)对重要对象或程序状态的修改,要以INFO记录修改前的信息和修改后的状态。

3)不能忽视每个调用错误,异常日志的内容要能清晰描述运行上下文,这样有助于排查错误,输出异常日志时, 第一个参数一定是一个字符串, 一般都是对问题的描述信息, 而不能是异常message(因为堆栈里面会有), 第二个参数才是具体的异常实例. ,例如:log.error("导入数据失败,错误信息为:", e);

4)记下打印信息时所在的文件名和函数名(必须能区分开重载函数),有行数最好。

5)对于高并发系统,能不打印日志就不打印日志, 输出的 日志数量、 级别、内容都要经过评估,避免日志输出影响性能, ,可以采用初期上线时多打印一些日志,上线运行一段时间后,通过观察方法调用次数,响应性能来决定减少那些日志的打印或者改变打印日志的级别。

6)对于异常日志的内容要能清晰描述运行上下文,这样有助于排查错误。

二、Log4j源码及其执行步骤概述

1、一个简单的log4j.properties案例

例如:项目根目录下的一个简单的log4j.properties配置如下:

package com.cn.log4j;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class logTest {

	private static Logger logger = Logger.getLogger(logTest.class);  
	
	public static void main(String[] args) throws Exception{
		//使用项目跟目录(与src同级)下的log4j.properties
		PropertyConfigurator.configure("log4j.properties");
//		PropertyConfigurator.configure("D:\\rjgzml\\log4jTestDemo\\config\\log4j.properties");
	    logger.trace("我是 Trace Message!");
        logger.debug("我是 Debug Message!");
        logger.info("我是 Info Message!");
        logger.warn("我是 Warn Message!");
        logger.error("我是 Error Message!");
        logger.fatal("我是 Fatal Message!");
    }  
}

/**输出结果:
  2020-10-11 20:11:24,277 [main] DEBUG [com.cn.log4j.logTest] - 我是 Debug Message!
  2020-10-11 20:11:24,278 [main] INFO  [com.cn.log4j.logTest] - 我是 Info Message!
  2020-10-11 20:11:24,278 [main] WARN  [com.cn.log4j.logTest] - 我是 Warn Message!
  2020-10-11 20:11:24,278 [main] ERROR [com.cn.log4j.logTest] - 我是 Error Message!
  2020-10-11 20:11:24,278 [main] FATAL [com.cn.log4j.logTest] - 我是 Fatal Message!
*/

2、log4j执行过程及其源码简述

    看Log4j源码,有助于我们理解log4j的框架及其执行过程。其过程大致可以总结为:

    解析全局配置 => 解析log4j.rootLogger => 解析log4j.appender.* =>解析logFactory => 解析log4j.logger.* => 解析renderers。

1)Log4j的初始化(LogManager类中的静态块中)

    当我们调用下面的代码时,实际会继续调用LogManager.getLogger(clazz.getName())

private static Logger logger = Logger.getLogger(Class clazz);

1.1)LogManager静态块初始化源码流程:   

    初始化流程主要做了以下三件事:

    第一步: LogManager获取配置文件的路径

    第二步: 通过OptionConverter获取Configurator实现类(配置类),具体实现类取决于配置文件的类型

    第三步: Configurator读取配置文件内容,配置Logger容器(默认配置Hierarchy)

//只在内部使用,将来版本将变为protected级别
static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  
//将来版本变为private级别
public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";
//将来版本将变为private级别。用来指定在定义配置类
static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
//将来版本将变为private级别。如果不为空并且不为`false`则直接跳过初始化阶段
public static final String DEFAULT_INIT_OVERRIDE_KEY =  "log4j.defaultInitOverride";
static private Object guard = null;

//Logger容器选择器
static private RepositorySelector repositorySelector;
static {
    //初始化Logger容器为Hierarchy。根节点是RootLogger,默认级别是DEBUG
    Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
    //初始化Logger容器选择器,以Hierarchy为Logger容器
    repositorySelector = new DefaultRepositorySelector(h);
    //获取系统属性log4j.defaultInitOverride
    String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);
    //如果没有设置log4j.defaultInitOverride,或者log4j.defaultInitOverride为false,进入初始化流程,否则跳过初始化
    if(override == null || "false".equalsIgnoreCase(override)) {
          //读取系统属性log4j.configuration
          String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
          //读取系统属性log4j.configuratorClass
          String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);
          URL url = null;
          //如果不存在log4j.configuration
          if(configurationOptionStr == null) {  
            //第一步先检查是否有log4j.xml
            url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
            //log4j.configuration配置的值非URL,试图在classpath读取相关文件资源log4j.properties
            if(url == null) {
              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.");
            try {
                //如果存在url,则利用URL配置Logger容器
                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."); 
    }  
} 

 

    源码过程描述:

    1)初始化Logger容器Hierarchy,设置根节点为RootLogger

    2)初始LoggerRepositorySelector(容器选择器)为默认的DefaultRepositorySelector,容器为Hierarchy

    3)读取系统属性log4j.defaultInitOverride,如果没有设置或者为false进行初始化,否则跳过初始化

    4)读取系统属性log4j.configuration(log4j文件路径配置),如果存在对应的文件,则得到URL.如果没有对应的文件,首先检查是否存在log4j.xml文件,如果存在,得到Log4j配置文件URL,如果不存在log4j.xml,继续检查是否存在log4j.properties文件,如果存在该文件,得到log4j配置文件的URL,否则提示没有发现配置文件。

    5)读取系统属性log4j.configuratorClass(自定义Configurator配置类全路径,一般不自定义)

    6)调用OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository()),初始化logger容器

1.2)OptionConverter获取Configurator实现类(配置类)

//利用给定URL配置Logger容器

static  public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { 
    Configurator configurator = null; 
    String filename = url.getFile(); 
    //优先检查使用xml文件,并查看是否有自定义的configurator
    if(clazz == null && filename != null && filename.endsWith(".xml")) { 
        clazz = "org.apache.log4j.xml.DOMConfigurator"; 
    } 
   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);
}

源码过程描述:

    1)如果没有自定义配置类Configurator并且文件的后缀名是xml.配置类设置为org.apache.log4j.xml.DOMConfigurator

    2)如果自定义了配置类,根据配置类的全限定名,发射得到配置类实例

    3)上面两种情况都没有匹配成功,默认是PropertyConfigurator配置类

    4)调用configurator.doConfigure(url,hierarchy),根据配置文件URL,配置logger容器Hierarchy(已经静态化构造了简单的容器,RootLogger是根节点)

1.3 )Configurator探究(以ProptertyConfigurator为例)

Configurator读取配置文件内容,配置Logger容器,调用doConfigure(properties,hierarchy)配置logger容器

//从URL中读取配置文件,配置Logger容器Hierarchy
publicvoid doConfigure(java.net.URL configURL, LoggerRepository hierarchy) { 
    Properties props = new Properties(); 
    LogLog.debug("Reading configuration from URL " + configURL); 
    InputStream istream = null; 
    URLConnection uConn = null; 
    try { 
        uConn = configURL.openConnection(); 
        uConn.setUseCaches(false); 
        istream = uConn.getInputStream(); 
        props.load(istream); 
    } catch (Exception e) { 
        if (e instanceof InterruptedIOException || e instanceof InterruptedException) { 
             Thread.currentThread().interrupt(); 
        } 
        LogLog.error("Could not read configuration file from URL [" + configURL + "].", e); 
        LogLog.error("Ignoring configuration file [" + configURL +"]."); 
        return; 
    } finally { 
        if (istream != null) { 
            try { 
                istream.close(); 
            } catch(InterruptedIOException ignore) { 
                Thread.currentThread().interrupt(); 
            } catch(IOException ignore) { 
            } catch(RuntimeException ignore) { 
            } 
        } 
    } 
    doConfigure(props, hierarchy);
}

1.4)doConfigure(Properties , LoggerRepository) 

public void doConfigure(Properties properties, LoggerRepository hierarchy) {
    repository = hierarchy; 
    String value = properties.getProperty(LogLog.DEBUG_KEY); 
    if(value == null) { 
        value = properties.getProperty("log4j.configDebug"); 
        if(value != null)
            LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); 
     } 
    if(value != null) { 
        LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); 
    } 
    // if log4j.reset=true then 
    // reset hierarchy 
    String reset = properties.getProperty(RESET_KEY); 
    if (reset != null && OptionConverter.toBoolean(reset, false)) { 
        hierarchy.resetConfiguration(); 
    } 
    String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties); 
    if(thresholdStr != null) { 
        hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL)); 
        LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"]."); 
    }  
    //
    configureRootCategory(properties, hierarchy); 

    configureLoggerFactory(properties); 

    parseCatsAndRenderers(properties, hierarchy); 

    LogLog.debug("Finished configuring."); 
    // We don't want to hold references to appenders preventing their 
    // garbage collection. 
    registry.clear(); 
}

void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    String effectiveFrefix = ROOT_LOGGER_PREFIX;//rootLogger
    String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
    if(value == null) {
      value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
      effectiveFrefix = ROOT_CATEGORY_PREFIX;
    }
    if(value == null)
      LogLog.debug("Could not find root logger information. Is this OK?");
    else {
      //【获取根logger】
      Logger root = hierarchy.getRootLogger();
      synchronized(root) {
        parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
      }
    }
  }

protected void configureLoggerFactory(Properties props) {
    String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,props);
    if(factoryClassName != null) {
      LogLog.debug("Setting category factory to ["+factoryClassName+"].");
      loggerFactory = (LoggerFactory)OptionConverter.instantiateByClassName(factoryClassName,LoggerFactory.class,loggerFactory);
      PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
    }
  }

protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) { 
    Enumeration enumeration = props.propertyNames(); 
    while(enumeration.hasMoreElements()) { 
        String key = (String) enumeration.nextElement(); 
        if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
            String loggerName = null;
            if(key.startsWith(CATEGORY_PREFIX)) { 
                loggerName = key.substring(CATEGORY_PREFIX.length());
            } else if(key.startsWith(LOGGER_PREFIX)) { 
                loggerName = key.substring(LOGGER_PREFIX.length());
            }
            String value = OptionConverter.findAndSubst(key, props);
            Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
            synchronized(logger) { 
                parseCategory(props, logger, key, loggerName, value); 
                parseAdditivityForLogger(props, logger, loggerName);
            } 
        } else if(key.startsWith(RENDERER_PREFIX)) {
            String renderedClass = key.substring(RENDERER_PREFIX.length());
            String renderingClass = OptionConverter.findAndSubst(key, props);
            if(hierarchy instanceof RendererSupport) { 
                RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass, renderingClass);
            } 
        } else if (key.equals(THROWABLE_RENDERER_PREFIX)) { 
            if (hierarchy instanceof ThrowableRendererSupport) { 
                ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(props, THROWABLE_RENDERER_PREFIX, org.apache.log4j.spi.ThrowableRenderer.class, null); 
                if(tr == null) { 
                    LogLog.error( "Could not instantiate throwableRenderer."); 
                } else { 
                    PropertySetter setter = new PropertySetter(tr); 
                    setter.setProperties(props, THROWABLE_RENDERER_PREFIX + "."); 
                    ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr); 
                } 
            } 
        } 
    } 
}

源码过程描述:

    1)获取log4j.debug(log4j内部是否debug打印日志),如果为ture打印,false不打印。如果没有设置,尝试读取log4j.configdebug(已经废弃,用logdebug取代)

    2)读取log4j.reset,如果设置为true,重置logger容器

    3)读取log4j.threshold,设置logger容器总阀值,低于阀值将不打印日志。如果没有配置,默认设置为最低级别Level.ALL

    4)调用configureRootCategory(Properties, LoggerRepository),配置RootLogger。RootLogger级别不能设置为空或者inherit(继承)解析设置RootLogger的Appenders和Filters。

    5)调用configureLoggerFactory(Properties props),配置Logger工厂类LoggerFactory。遍历Properties,读取以log4j.loggerFactory开头的属性log4j.loggerFactory=loggerFactoryClass将log4j默认的日志工厂替换为自定义的,基本没有必要这么做

    6)调用parseCatsAndRenderers(Properties, LoggerRepository),配置Logger以及Renderer。

    遍历Properties,读取所有以”log4j.category.”和”log4j.logger.”开头的属性,截取Logger名称调用LoggerRepository.getLogger(loggerName,loggerFactory)生成一个新的Logger。同步化新建的Logger,调用parseCategory()方法配置Logger的属性(参见前面RootLogger的配置)。然后调用parseAdditivityForLogger()方法设置Logger的继承属性。

    遍历Properties,读取所有以”log4j.renderer.”开头的属性,截取Renderer名称,如果LoggerRepository是RendererSupport的一种实例,调用RendererMap.addRenderer()方法添加Renderer。renderer就是为了解决我们在直接打印对象的时候如何输出值的问题,而不是直接调用toString方法

总结:log4j中的核心对象:Logger对象、Appender对象、Level对象、Filter对象、ObjectRenderer对象

3、log4j.properties 语法格式

################################################################################ 
#①配置根Logger,其语法为: 

#log4j.rootLogger = [level],appenderName,appenderName2,... 
#level是日志记录的优先级,分为OFF,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,ALL 
##Log4j建议只使用四个级别,优先级从低到高分别是DEBUG,INFO,WARN,ERROR 
#通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关 
#比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来 
#appenderName就是指定日志信息输出到哪个地方的配置名称(任意取)。可同时指定多个输出目的 
################################################################################ 
################################################################################ 
#②配置日志信息输出目的地Appender,其语法为: 

#log4j.appender.appenderName = fully.qualified.name.of.appender.class    添加appender提供的实现类
#log4j.appender.appenderName.optionN = valueN  添加appender提供的实现类的属性

#Log4j提供的appender常用的有以下几种: 
#1)org.apache.log4j.ConsoleAppender(输出到控制台) 
#2)org.apache.log4j.FileAppender(输出到文件) 
#3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) 
#4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) 
#5)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) 

#1)ConsoleAppender选项属性 
# -Threshold = DEBUG:指定日志消息的输出最低层次(来自父类:AppenderSkeleton.java的属性)
# -ImmediateFlush = true:默认值是true,所有的消息都会被立即输出,设置为false则不会输出 (来自父类WriterAppender.java的属性)
# -Target = System.err:默认值System.out,输出到控制台(err为红色,out为黑色) 

#2)FileAppender选项属性 
# -Threshold = INFO:指定日志消息的输出最低层次 (来自父类:AppenderSkeleton.java的属性)
# -ImmediateFlush = true:默认值是true,所有的消息都会被立即输出 (来自父类WriterAppender.java的属性)
# -Encoding = UTF-8:可以指定文件编码格式 (来自父类WriterAppender.java的属性)
# -File = C:\log4j.log:指定消息输出到C:\log4j.log文件 
# -Append = false:默认值true,将消息追加到指定文件中,false指将消息覆盖指定的文件内容 
# -BufferedIO = = false:是否启用缓冲区,默认为false
# -BufferSize = 8 * 1024://缓冲区大小,默认为8KB
#
#3)DailyRollingFileAppender选项属性 
# -Threshold = WARN:指定日志消息的输出最低层次 (来自父类:AppenderSkeleton.java的属性)
# -ImmediateFlush = true:默认值是true,所有的消息都会被立即输出 (来自父类WriterAppender.java的属性)
# -Encoding = UTF-8:可以指定文件编码格式 (来自父类WriterAppender.java的属性)
# -File = C:\log4j.log:指定消息输出到C:\log4j.log文件 (来自父类FileAppender.java的属性)
# -Append = false:默认值true,将消息追加到指定文件中,false指将消息覆盖指定的文件内容 (来自父类FileAppender.java的属性)
# -DatePattern='.'yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。还可以按用以下参数: 
#              '.'yyyy-MM:每月 
#              '.'yyyy-ww:每周 
#              '.'yyyy-MM-dd:每天 
#              '.'yyyy-MM-dd-a:每天两次 
#              '.'yyyy-MM-dd-HH:每小时 
#              '.'yyyy-MM-dd-HH-mm:每分钟 

#4)RollingFileAppender选项属性 
# -Threshold = ERROR:指定日志消息的输出最低层次 (来自父类:AppenderSkeleton.java的属性)
# -ImmediateFlush = TRUE:默认值是true,所有的消息都会被立即输出 (来自父类WriterAppender.java的属性)
# -Encoding = UTF-8:可以指定文件编码格式 (来自父类WriterAppender.java的属性)
# -File = C:/log4j.log:指定消息输出到C:/log4j.log文件 (来自父类FileAppender.java的属性)
# -Append = FALSE:默认值true,将消息追加到指定文件中,false指将消息覆盖指定的文件内容 (来自父类FileAppender.java的属性)
# -MaxFileSize = 100KB:后缀可以是KB,MB,GB.在日志文件到达该大小时,将会自动滚动.如:log4j.log.1
# -MaximumFileSize = 1024直接使用字节不带单位 
# -MaxBackupIndex = 2:指定可以产生的滚动文件的最大数 

################################################################################ 
################################################################################ 
#③配置日志信息的格式(布局),其语法为: 

#log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class 添加layout的实现类
#log4j.appender.appenderName.layout.optionN = valueN 添加layout的实现类的属性

#Log4j提供的layout有以下几种: 
#5)org.apache.log4j.HTMLLayout(以HTML表格形式布局) 
#6)org.apache.log4j.PatternLayout(可以灵活地指定布局模式) 
#7)org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串) 
#8)org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) 
#9)org.apache.log4j.xml.XMLLayout(以XML形式布局) 

#5)HTMLLayout选项属性 
# -LocationInfo = TRUE:默认值false,输出java文件名称和行号 
# -Title=Struts Log Message:默认值 Log4J Log Messages 

#6)PatternLayout选项属性 
# -ConversionPattern = %m%n:格式化指定的消息(参数意思下面有) 

#9)XMLLayout选项属性 
# -LocationInfo = TRUE:默认值false,输出java文件名称和行号 

#Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下: 
# %m 输出代码中指定的消息 
# %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 
# %r 输出自应用启动到输出该log信息耗费的毫秒数 
# %c 输出所属的类目,通常就是所在类的全名 
# %t 输出产生该日志事件的线程名 
# %n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” 
# %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式 
#    如:%d{yyyy年MM月dd日 HH:mm:ss,SSS},输出类似:2012年01月05日 22:10:28,921 
# %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 
#    如:Testlog.main(TestLog.java:10) 
# %F 输出日志消息产生时所在的文件名称 
# %L 输出代码中的行号 
# %x 输出和当前线程相关联的NDC(嵌套诊断环境),像java servlets多客户多线程的应用中 
# %% 输出一个"%"字符 

# 可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如: 
#  %5c: 输出category名称,最小宽度是5,category<5,默认的情况下右对齐 
#  %-5c:输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格 
#  %.5c:输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格 
#  %20.30c:category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉 
################################################################################ 
################################################################################ 
#④指定特定包的输出特定的级别 
#log4j.logger.org.springframework=DEBUG 
################################################################################ 

三、log4j配置详解 

1、log4j配置文件加载路径

    经过对log4j源码的解读,我们大致对log4j有了一定的了解。如果采用log4j输出日志,就要对log4j加载配置文件的过程有所了解。通过Logger.getLogger方法得到一个专属于这个类的日志记录器。

private static Logger logger = Logger.getLogger(logTest.class);

    上述方法会调用LogManager中的静态代码块读取配置文件。很少有人会去系统环境变量中配置,那么在项目中,log4j默认加载的位置在哪?上面源码中我们看到LogManager中的静态块中有这么一段:url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);//log4j.properties

    log4j启动时,默认会寻找source folder(类路径)下的log4j.xml配置文件,若没有,会寻找log4j.properties文件。然后加载配置。配置文件放置位置正确,不用在程序中手动加载log4j配置文件。如果将配置文件放到了config文件夹下,在build Path中设置下就好了,这里设置后,文件会默认在source folder下生成一份。

    若要手动加载配置文件如下,PropertyConfigurator.configure("log4j.properties") 默认读取的是项目根目录下的路径,即图中2的位置。此时的log4j.properties要放在项目目录下。也可以指定具体的路径PropertyConfigurator.configure("../mulu/log4j.properties")。

//默认会寻找source folder下的log4j.xml配置文件,若没有,会寻找log4j.properties文件。然后加载配置
// property, we search first for the file "log4j.xml" and then "log4j.properties"
   if(configurationOptionStr == null) {	
       url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);//log4j.xml
	   if(url == null) {
		    url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);//log4j.properties
	   }
    }
//source folder(项目的classpath) 就是.class编译后文件的所在位置)

//-----下面输出默认加载配置文件的目录---------
	String path1 = System.getProperty("java.class.path");//类路径
	
//-----log4j源码中默认加载的位置---------
	String path5 = Logger.class.getClassLoader().getResource("").getPath();
	String path6 =((ClassLoader)Thread.class.getMethod("getContextClassLoader",null).invoke(Thread.currentThread(), null)).getResource("").getPath();
	System.out.println("path 1 = " + path1);//path 1 = D:\rjgzml\log4jTestDemo\bin;D:\新建文件夹\log4j-1.2.17.jar
	System.out.println("path 5 = " + path5);//path 5 = /D:/rjgzml/log4jTestDemo/bin/
	System.out.println("path 6 = " + path6);//path 6 = /D:/rjgzml/log4jTestDemo/bin/

2、配置根Logger及自定义子logger(用户logger)

log4j.properties配置如下:

#配置根Logger
log4j.rootLogger = FATAL, Console 
#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.err  
#配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#配置一个名字为com.cn.log4j的子logger,继承自rootLogger
log4j.logger.com.cn.log4j= FATAL, logTest
log4j.appender.logTest=org.apache.log4j.ConsoleAppender
log4j.appender.logTest.layout=org.apache.log4j.PatternLayout
log4j.appender.logTest.layout.ConversionPattern=%d{ABSOLUTE} [%t] %-5p [%c] - %m%n
#配置一个名字为com.cn.log4j.logTest01的子logger,继承自com.cn.log4j
log4j.logger.com.cn.log4j.logTest01= FATAL, logTest01
#log4j.additivity.com.cn.log4j.logTest01=false
log4j.appender.logTest01=org.apache.log4j.ConsoleAppender
log4j.appender.logTest01.layout=org.apache.log4j.PatternLayout
log4j.appender.logTest01.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss,SSS} [%t] %-5p [%c] - %m%n

第一句:log4j.rootLogger = FATAL, Console 

    rootLogger,在配置文件log4j.properties是必须的,是所有其余logger的顶级parentrootLogger的名称为root,默认的。LogManager的静态代码块读取了配置文件,然后针对不同配置文件类型采用不同的解析器,(对于properties文件使用PropertyConfigurator)够建了一个基本的rootLogger,rootLogger有1个appender,名字叫Console,【ConsoleAppender】会将日志输出到控制台。

//log4j源码中PropertyConfigurator类中加载配置文件时调用的方法
void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    //解析log4j.properties
    String effectiveFrefix = ROOT_LOGGER_PREFIX; //log4j.rootLogger
    String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
    ....
    if(value == null)
      LogLog.debug("Could not find root logger information. Is this OK?");
    else {
      Logger root = hierarchy.getRootLogger();//设置跟logger
      synchronized(root) {
	   parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
      }
    }
  }

 使用上述配置文件,新建一个测试类:logTest01 

package com.cn.log4j;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class logTest01 {
	
	private static Logger logger = Logger.getLogger(logTest01.class); 
	
	private static Logger loggers = Logger.getLogger("com.cn.log4j");
	
	public static void main(String[] args) {
		PropertyConfigurator.configure("log4j.properties");
		logger.trace("我是 Trace Message!");
        logger.debug("我是 Debug Message!");
        logger.info("我是 Info Message!");
        logger.warn("我是 Warn Message!");
        logger.error("我是 Error Message!");
        logger.fatal("我是 Fatal Message!");
        
        loggers.trace("我是 loggers Trace Message!");
        loggers.debug("我是loggers Debug Message!");
        loggers.info("我是 loggers Info Message!");
        loggers.warn("我是loggers Warn Message!");
        loggers.error("我是 loggers Error Message!");
        loggers.fatal("我是loggers Fatal Message!");
	}
}

输出结果如下:

2020-10-18 02:51:22,017 [main] FATAL [com.cn.log4j.logTest01] - 我是 Fatal Message!
2020-10-18 02:51:22,019 [main] FATAL [com.cn.log4j] - 我是loggers Fatal Message!

2020/10/18 02:51:22,017 [main] FATAL [com.cn.log4j.logTest01] - 我是 Fatal Message!
02:51:22,017 [main] FATAL [com.cn.log4j.logTest01] - 我是 Fatal Message!
02:51:22,019 [main] FATAL [com.cn.log4j] - 我是loggers Fatal Message!

当我们log4j.additivity.com.cn.log4j.logTest01=false将这条属性打开

输出结果如下:

2020/10/18 03:02:58,050 [main] FATAL [com.cn.log4j.logTest01] - 我是 Fatal Message!
03:02:58,051 [main] FATAL [com.cn.log4j] - 我是loggers Fatal Message!

2020-10-18 03:02:58,051 [main] FATAL [com.cn.log4j] - 我是loggers Fatal Message!

log4j.additivity是子Logger 是否继承父Logger 的输出源(appender) 的标志位。具体说,默认情况下子Logger 会继承父Logger的appender,也就是说 子Logger会在父Logger的appender里输出。若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger 的appender里输出。这里就需要说到Log4j的关键之处在于它的继承思想。Logger 会根据LoggerConfig的name建立对象之间的继承关系。这种继承机制与java的package很像,name以点进行名称空间分割,子名称空间继承父名称空间。名称空间可以是全限定类名,也可以是包名,整个配置树的根节点就是rootLogger。

又例如:

package com.cn.log4j;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class log4jTest02 {

	private static Logger log1 = Logger.getLogger(logTest.class);
	
	public static void main(String[] args) {
		 BasicConfigurator.configure();
		 //root的默认级别:INFO
		 Logger.getRootLogger().setLevel(Level.INFO);//0
		 //对com.cn设置级别:WARN
		 Logger.getLogger("com.cn").setLevel(Level.WARN);//1
		 //对com.cn.log4j设置级别:ERROR
		 Logger.getLogger("com.cn.log4j").setLevel(Level.ERROR);//2
		 //对com.cn.log4j.log4jTest02设置级别:FATAL
		 log1.setLevel(Level.FATAL);//3
		 log1.trace("Trace Message!");
		 log1.debug("Debug Message!");
		 log1.info("Info Message!");
		 log1.warn("Warn Message!");
		 log1.error("Error Message!");
		 log1.fatal("Fatal Message!");
	}

}

上面案例输出:

1 [main] FATAL com.cn.log4j.logTest  - Fatal Message!

当我们把//3处注释,输出:

0 [main] ERROR com.cn.log4j.logTest  - Error Message!
1 [main] FATAL com.cn.log4j.logTest  - Fatal Message!

当我们再把//2处注释,输出:

0 [main] WARN com.cn.log4j.logTest  - Warn Message!
1 [main] ERROR com.cn.log4j.logTest  - Error Message!
1 [main] FATAL com.cn.log4j.logTest  - Fatal Message!

当我们再把//1处注释,输出:

0 [main] INFO com.cn.log4j.logTest  - Info Message!
1 [main] WARN com.cn.log4j.logTest  - Warn Message!
2 [main] ERROR com.cn.log4j.logTest  - Error Message!
2 [main] FATAL com.cn.log4j.logTest  - Fatal Message!

另外,将System.out.println(Logger.getLogger("com.cn").getParent().getName());输出:root

经过上述案例我们可以看出:

    com.cn.log4j.log4jTest02的父类是com.cn.log4j,com.cn.log4j的父类是com.cn,com.cn的父类是root。Log4J会自动的去寻找这个Logger的上下级关系,并自动的把这个新创建的Logger添加到已有的Logger结构体系中。不会去完全按照包名分割到最细,造成不必要的浪费。

    之前我们在创建Logger的时候,都是使用Logger.getLogger(Class)方法来得到一个类绑定的日志记录器的。实际上我们仅仅是使用给定的类的全限定名为Logger取了一个名字。当两个Logger的名字相同,这两个Logger就是同一个Logger实例。

package com.cn.log4j;

import org.apache.log4j.Logger;

public class log4jTest03 {

	private static Logger log1 = Logger.getLogger(logTest.class);
	
	private static Logger log2 = Logger.getLogger("com.cn.log4j.logTest");
	
	public static void main(String[] args) {
		System.out.println(log1==log2); //true
	}
}

3、配置日志信息输出目的地(appender)

###配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.err

    Appender主要解决日志输出源的问题,比如日志输出到操作系统的console,日志输出到数据库中,日志输出到socket接口,不过当前用的最多的是将日志输出到磁盘文件中。Appender的实现类很多,如下图所示:

Appender的实现类有十几个,他们有一个共同的抽象父类AppenderSkeleton。Log4j提供输出源实现类常见的有以下几种:

appender类型说明

org.apache.log4j.ConsoleAppender

将日志输出到控制台

org.apache.log4j.FileAppender

将日志输出到文件

org.apache.log4j.DailyRollingFileAppender

每天产生一个日志文件

org.apache.log4j.RollingFileAppender

文件大小到达指定尺寸时产生一个新的文件

org.apache.log4j. WriterAppender

将日志信息以流格式发送到任意指定的地方

工作中最常用的还是ConsoleAppender、FileAppender、DailyRollingFileAppender和RollingFileAppender这4个。

3.1、AppenderSkeleton.java

    AppenderSkeleton是一个抽象类,同时是所有appender类的父类.AppenderSkeleton提供对于过滤器filter的支持,比如能根据日志级别进行过滤.。其里面3个重要的属性是:layout、threshold、errorHandler 

 //AppenderSkeleton.java 源码中的属性   
    //日志字符串输出格式化类,比如格式话为字符串就直接调用Object.toString(),或者格式化为json
	protected Layout layout;
	//appender名称
	protected String name;
	//日志级别,默认为空
	protected Priority threshold;
	//默认的异常处理类OnlyOnceErrorHandler
	protected ErrorHandler errorHandler = new OnlyOnceErrorHandler();

3.2、WriterAppender.java

    WriterAppender.java通过java的IO流操作类(java.io.Writer或者java.io.OutputStream)来分别对字符流和字节流分别进行处理,拥有父类AppenderSkeleton的属性,其里面还有2个重要的属性是:immediateFlush、encoding

//WriterAppender.java 源码中的属性
    
    /**
	 * IO流是否立即清理写入到磁盘文件,默认为true
	 * 这个属性控制着java.io.Writer或者java.io.OutputStream是否每次appender执行时候立即写磁盘
	 */
	protected boolean immediateFlush = true;
 
	/**
	 * 这个属性控制着java.io.Writer或者java.io.OutputStream的编码类型,默认为null时会读取系统的编码类型
	 */
	protected String encoding;

3.3、ConsoleAppender.java

    ConsoleAppender是往console里丢入日志,ConsoleAppender具体调用的是java的System.out和System.err,默认使用System.out。拥有父类WriterAppender的属性,其里面还有1个重要的属性是:target

//ConsoleAppender.java 源码中的属性    
    //ConsoleAppender具体调用的是java的System.out和System.err,默认使用System.out
	public static final String SYSTEM_OUT = "System.out";
	public static final String SYSTEM_ERR = "System.err";
	protected String target = SYSTEM_OUT;

3.4、FileAppender.java

     FileAppender使用java.io.Writer来讲日志写入到磁盘文件。拥有父类WriterAppender的属性。其里面还有4个重要的属性是:Append、BufferedIO、BufferSize、File

//FileAppender.java 源码中的属性,这个类中的set方法有两个名字比较特殊,与定义名不一样
    protected boolean fileAppend = true;//是否在文件末尾追加内容 set方法使用:setAppend
	protected String fileName = null;//磁盘文件全路径名称        set方法使用:File
	protected boolean bufferedIO = false;//是否启用缓冲区
	protected int bufferSize = 8 * 1024;//缓冲区大小

3.5、DailyRollingFileAppender.java

     DailyRollingFileAppender仍然使用java.io.Writer来讲日志写入到磁盘文件,不同的是它可以控制按照天存储文件、按照小时存储文件、按照分钟存储文件、按照月份存储文件和按照周存储文件。拥有父类FileAppender的属性。其里面还有1个重要的属性是:datePattern 

//DailyRollingFileAppender.java 源码中的属性
private String datePattern = "'.'yyyy-MM-dd"; //按照时间来写日志文件,默认按天
DatePattern文件分割规则举例
'.'yyyy-MM按照月份存储文件在2002年5月31日午夜/foo/bar.log 将被复制到/foo/bar.log2002-05。6月份的日志将输出到/foo/bar.log直到下个月它也被滚动过。
'.'yyyy-ww按照周存储文件,每周的第一由当前系统的时区决定,比如美国以SUNDAY为一周第一天假设一周的第一天是周日2002年6月9日,周六午夜,文件/foo/bar.log 将被复制到/foo/bar.log2002-23。2002年第24周的日志将输出到/foo/bar.log直到下个星期它被翻转过来。
'.'yyyy-MM-dd按照天存储文件,每天晚上的12点会变更文件名字午夜,2002年3月8日,/foo/bar.log 将被复制到/foo/bar.log.2002-03-08。3月9日的日志将输出到/foo/bar.log直到第二天它被翻过来。
'.'yyyy-MM-dd-a按照半天存储文件,中午的12点和晚上的12点时候会变更文件名字 2002年3月9日中午/foo/bar.log 将被复制到/foo/bar.log.2002-03-09-am。9号下午的日志将输出到/foo/bar.log直到午夜被翻过来。
'.'yyyy-MM-dd-HH按照小时存储文件 大约在2002年3月9日11点/foo/bar.log 将被复制到/foo/bar.log.2002-03-09-10。3月9日11时的日志将输出到/foo/bar.log直到下一个小时开始的时候,它才被翻过来。
'.'yyyy-MM-dd-HH-mm按照分钟存储文件大约在2001年3月9日11:23:000/foo/bar.log 将被复制到/foo/bar.log. 2001-03-09-10-22.11:23。(3月9日)的记录将输出到/foo/bar.log直到下一分钟它被翻过来。

3.6、RollingFileAppender.java 

    RollingFileAppender扩展了FileAppender,支持按照文件大小来分割文件。拥有父类FileAppender的属性,其里面还有3个重要的属性是:MaxFileSize、MaximumFileSize、MaxBackupIndex。

    需要注意的是MaximumFileSize和MaxFileSize都可以设置最大文件的大小,MaxFileSize可以携带单位KB MB GB,RollingFileAppender会自己解析转换,MaximumFileSize(字节)则直接设置最大文件的大小。

//RollingFileAppender.java 源码中的属性
    /**
	 * 每个文件的最大Bytes大小值,默认10MB
	 */
	protected long maxFileSize = 10 * 1024 * 1024;

    /**
	 * 默认除了在写的日志文件外,保留最多maxBackupIndex个日志文件,默认最多保留1个文件,外加一个在写的日志文件,总计2个日志文件
	 */
	protected int maxBackupIndex = 1;

4、配置日志信息的输出格式(Layout)

###配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

    Layout主要解决日志格式化输出的问题,类似于C语言对于字符串输出的格式化,其继承关系图如下

平时开发环境中使用最多的是PatternLayout,先来看下PatternLayout支持的格式化规则:

//源码中默认格式 
	/**
	 * 默认的日志转换表达式,%m代码中的日志事件中的日志字符串,%n是在默认加个换行符
	 */
	public final static String DEFAULT_CONVERSION_PATTERN = "%m%n";
 
	/**
	 * 日志转换表达式%r [%t] %p %c %x - %m%n,依次含义为日志事件开始值LAOUT创建所花时间 [线程名] 日志优先级 包名 NDC信息 - 日志字符串 换行符
	 */
	public final static String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n";
转换字符作用
c用于输出日志事件的类别。类别转换说明符后面可以跟一个精度说明符,即括号中的十进制常量。如果给出一个精度说明符,那么只打印类别名称中最正确的组成部分的对应数字。默认情况下,类别名称是完整打印的,例如,对于类别名称“ a.b.c” ,模式% c {2}将输出“ b.c”。
C【请慎用】用于输出发出日志记录请求的调用方的完全限定类名。这个转换说明符后面可以跟一个精度说明符,即括号中的十进制常量。如果给出一个精度说明符,那么只会打印类名中最正确的组件的相应数目。默认情况下,类名以完全限定的形式输出。例如,对于类名“ org.apache.xyz.someclass” ,模式% C {1}将输出“ someclass”。

注意:使用此种转换字符获取打印日志所在类名会拖慢正常的业务处理,毕竟日志对于正常代码处理还是有影响的,请慎用。

d用于输出日志事件的日期。日期转换说明符后面可能跟着一个用大括号括起来的日期格式说明符。例如,% d { hh: mm: ss,sss }或% d { dd mmm yyyy hh: mm: ss,sss }。如果没有给出日期格式说明符,则采用 iso8601格式。

日期格式说明符允许使用与 java.text.simpledateformat 的时间模式字符串相同的语法。虽然是标准 jdk 的一部分,但 simpledateformat 的性能相当差。为了获得更好的结果,建议使用 log4j 日期格式化程序。可以使用“ absolute”、“ date”和“ iso8601”字符串中的一个来指定文件格式例如,%d{ISO8601} or %d{ABSOLUTE}.

注意:这里告诉我们其实还有比java的SimpleDateFormat更好的日期格式化工具类,似乎无意中又学习到了新知识

F【请慎用】 用于输出发出日志记录请求的文件名。警告生成调用方位置信息非常慢,应该避免,除非执行速度不是一个问题。

注意:使用此种转换字符获取打印日志所在源代码文件名比如Myclass.java会严重拖慢正常的业务处理,毕竟日志对于正常代码处理还是有影响的,请慎用。

l位置信息取决于 jvm 的实现,但通常包含调用方法的完全限定名,然后由调用方输出文件名和括号之间的行号。位置信息可能非常有用。但是,它的生成极其缓慢,应该避免,除非执行速度不是问题。
L【请慎用】用于输出发出日志记录请求的行号。生成调用方位置信息的警告非常慢,除非执行速度不是问题,否则应该避免。

注意:使用此种转换字符获取源代码里调用打印日志是哪一行代码发起的会严重拖慢正常的业务处理,毕竟日志对于正常代码处理还是有影响的,请慎用。

m用于输出与日志事件关联的应用程序提供的消息。
M【请慎用】用于输出发出日志记录请求的方法名。警告生成调用方位置信息非常慢,除非执行速度不是问题,否则应该避免这种情况。

注意:使用此种转换字符获取源代码里调用打印日志是哪个方法发起的会严重拖慢正常的业务处理,毕竟日志对于正常代码处理还是有影响的,请慎用。

n输出与平台相关的行分隔符字符或字符。这个转换字符提供了与使用非便携行分隔符字符串(如“ \n”或“\ rn”)几乎相同的性能。因此,它是指定行分隔符的首选方法。
p用于输出日志事件的优先级。
r 用于输出从构造布局到创建日志事件所经过的毫秒数。
t用于输出生成日志事件的线程的名称。
x 用于输出与生成日志记录事件的线程关联的 ndc (嵌套诊断上下文)。
X用于输出与生成日志事件的线程关联的 mdc (映射诊断上下文)。X 转换字符后面必须跟着映射的键,映射位于两个大括号之间,如% x { clientnumber } ,其中 clientnumber 是键。将输出 mdc 中与键对应的值。
%序列%% 输出一个百分比符号。
转换字符说明举例输出
%c

列出logger名字,如果加上(层数)表示列出从最内层算起的指定层数的名字空间。

例如logger的名字是"a.b.c"

%ca.b.c
%c{2}b.c
%20c

               a.b.c

[若不足20长度,左边用空格填充]

%-20c

a.b.c               

[若不足20长度,右边用空格填充]

%.30c

a.b.c

[若超过30长度,截去多余的字符]

%20.30c

               a.b.c

[若不足20长度,左边用空格填充,若超过30长度,截去多余的字符]

%-20.30c

a.b.c               

[若不足20长度,右边边用空格填充,若超过30长度,截去多余的字符]

%C列出调用logger的类全名(包含包路径)。假设当前类是“com.cn.log4jTestDemo”%Ccom.cn.log4jTestDemo
%C{1}log4jTestDemo
%d显示日志记录时间{日期格式}使用ISO8601定义的日期格式%d{yyyy/MM/dd HH:mm:ss,SSS}2020/10/12 16:01:55,117
%d{ABSOLUTE}16:01:55,117
%d{DATE}12 Oct 2020 16:01:55,117
%d{ISO8601}2020-10-12 16:01:55,117
%F显示logger的源文件名%FMyClass.java
%l输出日志事件的发生位置,包括类目名,发生的线程,以及在代码中的行数%lMyClass.main(MyClass.java:129)
%L显示调用logger的代码行数%L129
%m显示输出消息%mThis is message for debug
%M显示调用logger的方法名%Mmain
%n当前平台下的换行符%nWindows平台下表示rn  UNIX平台下表示n
%p显示该条日志的优先级%pINFO
%r显示从程序启动时到记录该条日志时以及经过的毫秒数%r1215
%t输出产生该日志事件的线程名称%tMyClass
%x按NDC(线程堆栈)顺序输出日志。假设某程序顺序是MyApp调用com.foo.Bar%c%x-%m%n

MyApp-Call - com.foo.Bar

com.foo.Bar - Log in Bar

MyApp - Return to MyApp

%X按MDC(线程映射表)输出日志。通常用于多个客户端连接一台服务器,方便服务器区分哪个客户端访问留下来的日志%X{5}记录代号为5的客户端日志
%%显示一个百分号%%%

5、log4j.properties 配置模板

Log4j配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。
log4j.rootLogger=DEBUG,console,dailyFile,im
log4j.additivity.org.apache=true
# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

# 日志文件(logFile)
log4j.appender.logFile=org.apache.log4j.FileAppender
log4j.appender.logFile.Threshold=DEBUG
log4j.appender.logFile.ImmediateFlush=true
log4j.appender.logFile.Append=true
log4j.appender.logFile.File=D:/logs/log.log4j
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 回滚文件(rollingFile)
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.Threshold=DEBUG
log4j.appender.rollingFile.ImmediateFlush=true
log4j.appender.rollingFile.Append=true
log4j.appender.rollingFile.File=D:/logs/log.log4j
log4j.appender.rollingFile.MaxFileSize=200KB
log4j.appender.rollingFile.MaxBackupIndex=50
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 定期回滚日志文件(dailyFile)
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.Threshold=DEBUG
log4j.appender.dailyFile.ImmediateFlush=true
log4j.appender.dailyFile.Append=true
log4j.appender.dailyFile.File=D:/logs/log.log4j
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 应用于socket
log4j.appender.socket=org.apache.log4j.RollingFileAppender
log4j.appender.socket.RemoteHost=localhost
log4j.appender.socket.Port=5001
log4j.appender.socket.LocationInfo=true
# Set up for Log Factor 5
log4j.appender.socket.layout=org.apache.log4j.PatternLayout
log4j.appender.socket.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# Log Factor 5 Appender
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
# 发送日志到指定邮件
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
log4j.appender.mail.Threshold=FATAL
log4j.appender.mail.BufferSize=10
log4j.appender.mail.From = [email protected]
log4j.appender.mail.SMTPHost=mail.com
log4j.appender.mail.Subject=Log4J Message
log4j.appender.mail.To= [email protected]
log4j.appender.mail.layout=org.apache.log4j.PatternLayout
log4j.appender.mail.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 应用于数据库
log4j.appender.database=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.database.URL=jdbc:mysql://localhost:3306/test
log4j.appender.database.driver=com.mysql.jdbc.Driver
log4j.appender.database.user=root
log4j.appender.database.password=
log4j.appender.database.sql=INSERT INTO LOG4J (Message) VALUES('=[%-5p] %d(%r) --> [%t] %l: %m %x %n')
log4j.appender.database.layout=org.apache.log4j.PatternLayout
log4j.appender.database.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

# 自定义Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = [email protected]
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

6、log4j.properties配置项总结

配置项前缀分类作用
log4j.debuglog4j自身日志控制开关1)格式:log4j.debug=true/false

2)开启后能查看log4j加载配置文件,解析配置文件,打印日志等操作相关日志

3)默认为false,表示未开启log4j自身日志

log4j.configDebuglog4j自身日志控制开关

1)格式:log4j.configDebug=true/false

2)作用和log4j.debug类似,优先使用log4j.debug

log4j.resetrootLogger设置

1)格式:log4j.reset=true/false

2)log4j.reset=true,会重置rootLogger的日志级别为DEBUG,

重置rootLogger的threshold为ALL

3)默认为false

log4j.thresholdrootLogger设置

1)格式:log4j.threshold=ALL/TRACE/DEBUG/INFO/WARN/ERROR/FATAL/OFF

2)ALL/TRACE/DEBUG/INFO/WARN/ERROR/FATAL/OFF

是日志级别,默认代表的值从小到大,默认值为ALL

3)日志事件级别小于threshold的将被禁止打印,也即不输出

log4j.rootLoggerrootLogger设置

1)格式:log4j.rootLogger=日志级别

2)用于设置rootLogger配置,log4j里的日志分为rootLogger和logger,

这是两类不同日志,以log4j.logger打头的为用户logger,其parent属性

指向rootLogger,意图是防止用户logger未配置appender时,默认将所有日志事件传播给rootLogger的appender执行

log4j.rootCategoryrootLogger设置

1)格式:log4j.rootCategory=日志级别

2)log4j.rootCategory和log4j.rootLogger作用一样。默认先使用log4j.rootLogger

log4j.appender.*.*appender配置

1)格式:log4j.appender.appenderName.attribute-key=attribute-value

2)attribute-key是不同的appender实现类的具体属性,

属性名不要大小写随便写,必须遵循JavaBean规范,默认是通过反射机制调用setXXX进行设置

3)attribute-key有3个属性必须小写,这三个属性如下:

log4j.appender.appenderName.layout  

log4j.appender.appenderName.errorhandler  

log4j.appender.appenderName.filter  

4)每个appender的实现类不同,其配置属性也各不相同,属性个数也不同,视具体实现类而定

log4j.loggerFactorylogger工厂类配置

1)格式:log4j.loggerFactory=com.xxx.xxx.MyLoggerFactory

2)默认值为org.apache.log4j.DefaultCategoryFactory

3)这个参数主要是为了让用户自己可以定义自己的logger工厂类实现

log4j.factorylogger工厂类配置

1)格式:log4j.factory=com.xxx.xxx.MyLoggerFactory

2)log4j.loggerFactory和log4j.factory作用一样, 默认先使用log4j.loggerFactory

log4j.category.*用户logger设置

1)格式:log4j.category.loggerName=日志级别

2)用于设置用户logger配置

3)log4j.category.和log4j.logger.作用一样,默认先使用log4j.category.

root.logger.*用户logger设置

1)格式:log4j.logger.loggerName=日志级别

2)用于设置用户logger配置

3)log4j.category.和log4j.logger.作用一样,只需配置一个即可;

默认先使用log4j.category

log4j.additivity.用户logger设置

1)格式:log4j.additivity.loggerName=true/false

2)默认log4j.additivity.loggerName=true,用户logger的parent属性指向rootLogger,日志事件经用户logger后都会传播给rootLogger,会造成日志打印两遍,可以设置log4j.additivity.loggerName=false,告诉名字为loggerName的用户logger不要将日志事件传播给rootLogger

log4j.renderer.render设置

1)格式:

log4j.render.myrender1

log4j.render.A

myrender1=com.xxx.xxx.MyObjectRender

A=com.xxx.xxx.MyObjectRender

2)对于日志事件log.info(Object),log4j并不知道Object的类型,log4j需要将Object转为String,这时候的处理逻辑先去查找是否有Object对应的Render转换类,比如A.classs对应Render类为com.xxx.MyObjectRender,此时就调用MyObjectRender.doRender方法返回字符串String,如果找不到,那么就调用A.class的toString方法,毕竟toString是所有类的父类Object的方法

3)默认com.xxx.xxx.MyObjectRender必须实现接口org.apache.log4j.or.ObjectRenderer

log4j.throwableRendererrender设置

1)格式:log4j.throwableRenderer=com.xxx.xxx.MyThrowableRenderer

2)默认会将rootLogger的throwableRenderer=null,如果用户配置了log4j.throwableRenderer,那么会将此值设置给rootLogger的throwableRenderer,当有异常发生时会调用throwableRenderer的

public String[] doRender(Throwable t)将异常转换为String数组

3)com.xxx.xxx.MyThrowableRenderer必须实现接口org.apache.log4j.spi.ThrowableRenderer

注: log4j.properties中配置项区分大小写吗?

1)上述表格中的配置项前缀必须小写

2)layout、errorhandler、filter必须小写

3)其他的配置属性遵从如下规则:

    属性名为null或者为空白字符,直接返回属性名;

    属性名长度大于或者等于2,并且属性名的前2位都是大写,那么直接返回属性名;

    非上面两种情况,则直接将首字母转换为小写然后返回属性名。

例如:FileAppender中的setFile方法

       log4j.appender.appenderName.File会将“File”先转为file,然后找设值方法setFile;

       log4j.appender.appenderName.file会将“file”先转为file,然后找设值方法setFile;

       log4j.appender.appenderName.FILE会将“FILE”先转为FILE,然后找设值方法setFILE; //这种反射就会失败

四、常用属性案例应用

1、log4j.debug 和 log4j.configDebug 使用

   可以输出log4j源码中的日志

#配置根Logger
log4j.rootLogger = DEBUG, Console

#配置这两个属性可以打印出log4j插件中的日志,看执行过程
log4j.debug = true
log4j.configDebug = true

#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.out  
log4j.appender.Console.threshold=OFF 
#配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
package com.cn.log4j;

import org.apache.log4j.Logger;

public class logTest01 {
	
	private static Logger logger = Logger.getLogger(logTest.class);  

	public static void main(String[] args) {
		logger.trace("我是 Trace Message!");
        logger.debug("我是 Debug Message!");
        logger.info("我是 Info Message!");
        logger.warn("我是 Warn Message!");
        logger.error("我是 Error Message!");
        logger.fatal("我是 Fatal Message!");
	}

}

/**先屏蔽其他信息在控制台的输出,结果如下:
log4j: Parsing for [root] with value=[DEBUG, Console].
log4j: Level token is [DEBUG].
log4j: Category root set to DEBUG
log4j: Parsing appender named "Console".
log4j: Parsing layout options for "Console".
log4j: Setting property [conversionPattern] to [%d [%t] %-5p [%c] - %m%n].
log4j: End of parsing for "Console".
log4j: Setting property [threshold] to [OFF].
log4j: Setting property [target] to [System.out  ].
log4j: Parsed "Console" options.
log4j: Finished configuring.
 */

2、log4j输出到文件RollingFileAppender

##配置根Logger
log4j.rootLogger = DEBUG, rollingFile

##配置日志信息输出目的地Appender及Appender选项
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.Threshold=INFO
log4j.appender.rollingFile.File=log/rolling.log
log4j.appender.rollingFile.ImmediateFlush=true
log4j.appender.rollingFile.Append=true
log4j.appender.rollingFile.MaxFileSize=5KB
log4j.appender.rollingFile.MaxBackupIndex=50

##配置日志信息的格式(布局)及格式布局选项
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

首次执行生成文件rolling.log

当多执行几次之后:

MaxFileSize按日志大小分割文件,MaxBackupIndex即为最多保留的日志个数除了rolling.log,最多会保留到roolling.log.50

3、log4j输出到文件DailyRollingFileAppender

##配置根Logger
log4j.rootLogger = DEBUG, dailyFile

##配置日志信息输出目的地Appender及Appender选项
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.Threshold=INFO
log4j.appender.dailyFile.File=log/daily.log
log4j.appender.dailyFile.ImmediateFlush=true
log4j.appender.dailyFile.Append=true
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd-HH-mm
##配置日志信息的格式(布局)及格式布局选项
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

DailyRollingFileAppender-log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd-HH-mm按分钟生成日志文件

五、Filter简介

   前面我们介绍了AppenderSkeleton有个属性threshold可以指定日志level。当然这里有个前提rootLogger里配置的level要小于threshold的level,否则还是按rootLogger里的level来输出。

#配置根Logger
log4j.rootLogger = ERROR, Console

#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.out  
log4j.appender.Console.threshold=DEBUG
#配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
package com.cn.log4j;

import org.apache.log4j.Logger;

public class logTest {

	private static Logger logger = Logger.getLogger("DEMO");  
	
	public static void main(String[] args) throws Exception{
		logger.trace("我是 Trace Message!");
        logger.debug("我是 Debug Message!");
        logger.info("我是 Info Message!");
        logger.warn("我是 Warn Message!");
        logger.error("我是 Error Message!");
        logger.fatal("我是 Fatal Message!");
    }  
}

/**输出结果:当threshold设置的level小于rootLogger时以rootLogger为准
 *2020-10-18 19:59:46,569 [main] ERROR [DEMO] - 我是 Error Message!
 *2020-10-18 19:59:46,570 [main] FATAL [DEMO] - 我是 Fatal Message!
 */

     当程序员已经在代码中内置了log4j-1.2.17,突然有一天发现日志打印消耗系统性能想禁用日志功能,那么此时Filter就起到作用。Filter,在threshold的基础上,用于实现进一步的过滤。可以在一个Appender上添加多个过滤器。过滤器(Filter)的核心职责就是对LogEvent日志事件进行匹配,匹配结果分为匹配和不匹配,结果值有3种:接受,拒绝,中立.可由用户自定义匹配和不匹配的行为结果。log4j中常用的Filter分为四种:DenyAllFilter、LevelMatchFilter、LevelRangeFilter、StringMatchFilter。

       Filter的类继承关系如下:

1、LevelMatchFilter

    匹配levelToMatch所对应的日志等级

log4j.properties配置如下:

#配置根Logger
log4j.rootLogger = DEBUG, Console 

#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.out  

#定义id为a,b,c的拦截器,WARN不接受,INFO不接受,ERROR接受
log4j.appender.Console.filter.a=org.apache.log4j.varia.LevelMatchFilter
log4j.appender.Console.filter.a.LevelToMatch=WARN
log4j.appender.Console.filter.a.AcceptOnMatch=false

log4j.appender.Console.filter.b=org.apache.log4j.varia.LevelMatchFilter
log4j.appender.Console.filter.b.LevelToMatch=INFO   
log4j.appender.Console.filter.b.AcceptOnMatch=false
   
log4j.appender.Console.filter.c=org.apache.log4j.varia.LevelMatchFilter
log4j.appender.Console.filter.c.LevelToMatch=ERROR  
log4j.appender.Console.filter.c.AcceptOnMatch=true
package com.cn.log4j;

import org.apache.log4j.Logger;

public class logTest {

	private static Logger logger = Logger.getLogger(logTest.class);  
	
	public static void main(String[] args) throws Exception{
		logger.trace("我是 Trace Message!");
        logger.debug("我是 Debug Message!");
        logger.info("我是 Info Message!");
        logger.warn("我是 Warn Message!");
        logger.error("我是 Error Message!");
        logger.fatal("我是 Fatal Message!");
    }  
}

/**输出结果:屏蔽掉了WARN和INFO
 * 2020-10-18 23:04:34,593 [main] DEBUG [com.cn.log4j.logTest] - 我是 Debug Message!
 * 2020-10-18 23:04:34,595 [main] ERROR [com.cn.log4j.logTest] - 我是 Error Message!
 * 2020-10-18 23:04:34,595 [main] FATAL [com.cn.log4j.logTest] - 我是 Fatal Message!
 */

    上面配置针对appender=Console ,启用了类型为LevelMatchFilter的过滤器,设置了过滤器LevelMatchFilter的属性levelToMatch=ERROR且acceptOnMatch=true,含义是这个appender可以打印级别为ERROR的日志,acceptOnMatch=false,则表示不打印该级别的日志。 

 2、LevelRangeFilter

    控制打印日志级别范围。log4j.properties配置如下:

#配置根Logger
log4j.rootLogger = DEBUG, Console 

#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.out  

#配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#定义id为d的拦截器,只接受INFO到ERROR
log4j.appender.Console.filter.d=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.Console.filter.d.LevelMin=INFO
log4j.appender.Console.filter.d.LevelMax=ERROR
log4j.appender.Console.filter.d.acceptOnMatch=true

输出结果:

2020-10-18 23:10:56,222 [main] INFO  [com.cn.log4j.logTest] - 我是 Info Message!
2020-10-18 23:10:56,224 [main] WARN  [com.cn.log4j.logTest] - 我是 Warn Message!
2020-10-18 23:10:56,224 [main] ERROR [com.cn.log4j.logTest] - 我是 Error Message!

    上面配置针对appender=Console ,启用了类型为LevelRangeFilter的过滤器,设置了过滤器LevelRangeFilter的属性levelMin=INFO且levelMax=ERROR且acceptOnMatch=true,含义是这个appender只打印级别在INFO到ERROR这个范围的日志,也即级别为INFO、WARN和ERROR的日志。

3、DenyAllFilter

    控制拒绝所有日志。log4j.properties配置如下:将不会输出日志

#配置根Logger
log4j.rootLogger = DEBUG, Console 

#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.out  

#配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#定义id为e的拦截器,拒绝输出日志
log4j.appender.Console.filter.e=org.apache.log4j.varia.DenyAllFilter

 上面配置针对appender=Console ,启用了类型为DenyAllFilter的过滤器,含义是这个appender对于一切日志都不打印。

4、StringMatchFilter

    控制打印日志中包含/不包含字符XXX的日志。log4j.properties配置如下:

#配置日志信息输出目的地Appender及Appender选项
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.target=System.out  

#配置日志信息的格式(布局)及格式布局选项
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#定义id为f的拦截器,禁止包含Info的日志
log4j.appender.Console.filter.f=org.apache.log4j.varia.StringMatchFilter
log4j.appender.Console.filter.f.stringToMatch=Info
log4j.appender.Console.filter.f.acceptOnMatch=false

输出结果:

2020-10-18 23:20:15,215 [main] DEBUG [com.cn.log4j.logTest] - 我是 Debug Message!
2020-10-18 23:20:15,216 [main] WARN  [com.cn.log4j.logTest] - 我是 Warn Message!
2020-10-18 23:20:15,217 [main] ERROR [com.cn.log4j.logTest] - 我是 Error Message!
2020-10-18 23:20:15,217 [main] FATAL [com.cn.log4j.logTest] - 我是 Fatal Message!

       上面配置针对appender=Console ,启用了类型为StringMatchFilter的过滤器,设置了过滤器StringMatchFilter的属性stringToMatch=Info且acceptOnMatch=false,含义是这个appender不处理日志中包含字符串"Info"的日志事件。

六、commons-logging 简介

1、commons-logging 概述

    在JDK最初的版本当中并不包含日志记录的API和实现,直到JDK1.4后才被加入。因此,开源社区在此期间提供了众多贡献,其中名声最大、运用最广泛的当log4j莫属,当然后续的logback、log4j2也在迅速的普及。JDK Logger最大的优点就是不需要任何类库的支持,只要有Java的运行环境就可以使用。相对于其他的日志空间,JDK自带的日志可谓是鸡肋,无论易用性,功能还是扩展性都要稍逊一筹。

    Apache Commons Logging,又名JakartaCommons Logging (JCL),它是Apache提供的一个通用的日志接口,它的出现避免了和具体的日志方案直接耦合;在日常开发中,developer可以选择第三方日志组件进行搭配使用,例如log4j、logback等;说的直白些,commons-logging提供了操作日志的接口,而具体实现交给log4j、logback这样的开源日志框架来完成;这样的方式,实现了程序的解耦,对于底层日志框架的改变,并不会影响到上层的业务代码。commons-logging是为那些需要建立在不同环境下使用不同日志架构的组件或库的开发者创建的。

2、commons-logging 结构

Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error

LogFactory:抽象类,日志工厂,获取日志类;

LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;

Log4JLogger:对log4j的日志对象封装;

Jdk14Logger:对JDK1.4的日志对象封装;

Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;

SimpleLog:commons-logging自带日志对象;将日志信息输出至控制台,建议只是用于开发环境。

NoOpLog:它很懒,什么也不做,所有给它的日志信息直接丢弃。

    调用LogFactory.getLog()时发生的事情。调用该函数会启动一个发现过程,即找出必需的底层日志记录功能的实现,具体的发现过程在下面列出:

     1)Commons的Logging首先在CLASSPATH中查找commons-logging.properties文件。这个属性文件至少定义 : 

         org.apache.commons.logging.Log属性,它的值应该是上述任意Log接口实现的完整限定名称。

         如果找到  org.apache.commons.logging.Log属性,则使用该属相对应的日志组件。结束发现过程。

     2)如果上面的步骤失败(文件不存在或属相不存在),Commons的Logging接着检查系统属性 org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系统属性,则使用该系统属性对应的日志组件。结束发现过程。

    3)如果找不到org.apache.commons.logging.Log系统属性,Logging接着在CLASSPATH中寻找log4j的类。如果找到了,Logging就假定应用要使用的是log4j。不过这时log4j本身的属性仍要通过log4j.properties文件正确配置。结束发现过程。

    4)如果上述查找均不能找到适当的Logging API,但应用程序正运行在JRE 1.4或更高版本上,则默认使用JRE 1.4的日志记录功能。结束发现过程。

    5)最后,如果上述操作都失败(JRE 版本也低于1.4),则应用将使用内建的SimpleLog。SimpleLog把所有日志信息直接输出到System.err。结束发现过程。

3、log4j与commons-logging结合

    log4j如何被commons-logging加载?
    根据上述commons-logging解耦的原理,如果log4j是通过继承抽象工厂org.apache.commons.logging.LogFactory来创建实现org.apache.commons.logging.Log接口的日志实例的,那么就可以通过3种方法让commons-logging加载log4j,分别是:

    1)从系统属性org.apache.commons.logging.LogFactory

    2)使用SPI服务发现机制,发现org.apache.commons.logging.LogFactory的实现

    3)查找classpath根目录commons-logging.properties的org.apache.commons.logging.LogFactory属性是否指定factory实现

    如果像上述一样没有指定任何org.apache.commons.logging.Log的实现,那么commons-logging首先使用的是
org.apache.commons.logging.impl.Log4JLogger作为Log实现。

例如:没有加任何的配置也没有指定commons.logging.LogFactory的实现类LogFactory.getLog、Logger.getLogger的输出结果一致。

package com.cn.log4j;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;

public class logTest {

	private static Log logger = LogFactory.getLog(logTest.class);   
	
//	private static Logger logger = Logger.getLogger(logTest01.class); 
	
	public static void main(String[] args) throws Exception{
		logger.trace("我是 Trace Message!");
        logger.debug("我是 Debug Message!");
        logger.info("我是 Info Message!");
        logger.warn("我是 Warn Message!");
        logger.error("我是 Error Message!");
        logger.fatal("我是 Fatal Message!");
    }  
}

 

;