02、Log4j(第三方日志框架,带源码分析)(上)

举报
长路 发表于 2022/11/28 08:33:19 2022/11/28
【摘要】 文章目录前言一、认识Log4j1.1、介绍Log4j1.2、第三方jar包1.3、日志等级(6个)二、Log4j的三大组件LoggersAppendersLayouts三、入门Log4j3.1、系统初始化配置输出日志3.2、BasicConfigurator类源码分析*四、自定义配置文件4.1、LogManager、OptionConverter源码分析*4.2、PropertyConfigura

@[toc]


前言

本篇博客主要介绍第三方日志框架Log4j,其他日志框架内容可见日志专栏。

所有博客文件目录索引(包含日志框架系列学习):博客目录索引(持续更新)



一、认识Log4j

1.1、介绍Log4j

Log4j官网:https://logging.apache.org/log4j/2.x/

Log4j:Apache的开源项目,是一个功能强大的日志组件,提供方便的日志记录。在项目中使用Log4j可以控制日志信息输出到控制台、文件甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别可以更灵活的控制日志的输出过程,方便调试。



1.2、第三方jar包

若想要使用Log4j,那么就需要引入jar包,下面给出pom.xml的坐标:

<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>


1.3、日志等级(6个)

org.apache.log4j.Level源码查看

Log4j中也有一个Level类来表示日志等级,我们来查看下该类:

public class Level extends Priority implements Serializable {

    final static public Level OFF = new Level(OFF_INT, "OFF", 0);
    final static public Level FATAL = new Level(FATAL_INT, "FATAL", 0);
    final static public Level ERROR = new Level(ERROR_INT, "ERROR", 3);
    final static public Level WARN  = new Level(WARN_INT, "WARN",  4);
    final static public Level INFO  = new Level(INFO_INT, "INFO",  6);
    final static public Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7);
    public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7);
    final static public Level ALL = new Level(ALL_INT, "ALL", 7);
}
  • 其中OFFALL不作为日志等级(可看做开关),所以有六个日志等级。

日志等级从高到低:在Log4j中有6个日志等级,JUL中是7个

  • fatal: 严重错误,一般会造成系统崩溃和终止运行。
  • error:错误信息,但不会影响系统运行。
  • warn:警告信息,可能会发生问题。
  • info:程序运行信息,数据库的连接、网络、IO操作等。
  • debug:调试信息,一般在开发阶段使用,记录程序的变量、参数等。
  • trace:追踪信息,记录程序的所有流程信息。

特殊的两个日志级别:

  • OFF,可用来关闭日志记录
  • ALL,启用所有消息的日志记录。

一般只使用四个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG



二、Log4j的三大组件

Loggers

Loggers(日志记录器):负责收集处理日志记录,获取logger实例可通过类名或者全限定名获取,并且对于名称具有继承机制(与JUL类似),例如name为xyz.changlu会继承name为xyz的logger。

  • 从log4j 1.2以来,Logger类取代了Category类,Logger类可以视作Category类的别名。

在Log4j中包含一个特殊的loggerrootlogger,它是所有logger的根,其他logger会直接或间接的继承该rootlogger,可使用Logger.getLogger()获取。

看一下调用BasicConfigurator.configure();配置方法后使用Logger.getLogger()获得实例的结构图:

image-20210303220454298

  • 其中category我们现在可以看做logger。当获取到一个logger实例时,其包含一个父属性为rootlogger,该父属性日志等级为DEBUGappenderConsoleApender(用于输出到屏幕上)。

注意:图中应该是ConsoleApender



Appenders

Appender:用来指定日志输出到哪个地方(即输出目的地),不同的Appender类型有不同的输出地点。

Appender类型 作用
ConsoleAppender 将日志输出到控制台
FileAppender 将日志输出到文件中
DailyRollingFileAppender 将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender 把日志信息保存到数据库中

我们可以Log4j中的Appender相对于JUL可以输出到数据库、每天输出文件、指定文件尺寸改名。



Layouts

Layouts(布局器):用于控制日志输出内容的格式,通过使用不同的Layout类型来指定各种需要的格式。

常用的Layout如下:

格式器类型 作用
HTMLLayout 格式化日志输出为HTML表格形式
SimpleLayout 简单的日志输出格式化,打印的日志格式为(info - message)
PatternLayout 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式

image-20210304234453221

  • Log4j提供的其他Layout,包含XML格式以及日期格式。

专对于PatternLayout类中的pattern自定义格式说明

# 占位符相关含义
%p: 输出优先级,及 DEBUGINFO%m: 输出代码中指定的日志信息
%n: 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n"%r: 输出自应用启动到输出该 log 信息耗费的毫秒数
%d: 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy-MM-dd HH:mm:ss:SSS}  => 年月日 时分秒毫秒

%l: 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
	# %l即可表示下方四个修饰符
    %c: 输出打印语句所属的类的全名
    %t: 输出产生该日志的线程全名
    %F: 输出日志消息产生时所在的文件名称
    %L: 输出代码中的行号
%%: 输出一个 "%" 字符

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

比较好的搭配如下

log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

image-20210305000941086



三、入门Log4j

3.1、系统初始化配置输出日志

我们来首先尝试输出六个等级日志:

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

public class LogTest {
    public static void main(String[] args) {
        //初始化系统配置,不需要配置文件
        BasicConfigurator.configure();

        //获取logger实例,注意这里的Logger是Log4j的
        Logger logger = Logger.getLogger(LogTest.class);
        //打印不同的日志等级
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}
  • 第4行:与之前jul有点相似,jul是默认加载logging.properties,这里则需要手动调用方法调用方法来进行配置rootlogger,否则报错,若是不使用该方法就进行日志输出会报错,如下图:
    • image-20210303213409839

image-20210303213603721

注意:当通过调用方法进行配置时,logger实例的父属性rootloggerappenderConsoleAppender(formatterSimpleFormatter),logger实例的日志等级为DEBUG。所以trace等级的日志并没有输出。



3.2、BasicConfigurator类源码分析*

image-20210303224142307

  • 单独一个类,无实现接口以及继承

前面说到BasicConfigurator.configure();是进行配置rootlogger的,我们看一下源码:

//org.apache.log4j.BasicConfigurator
public class BasicConfigurator {
	
    //1、配置方法
    void configure() {
        //获取到LogManager的rootlogger(new RootLogger((Level) Level.DEBUG))
        Logger root = Logger.getRootLogger();//见2
        //添加了ConsoleAppender以及自定义格式(%r [%t] %p %c %x - %m%n)
        root.addAppender(new ConsoleAppender(
            new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
    }
}

//org.apache.log4j.Logger
public class Logger extends Category {
    
    //2、获取Logger实例
    Logger getRootLogger() {
        //实际上调用的是LogManager的方法获取的
        return LogManager.getRootLogger();//见3
    }
}

//org.apache.log4j.LogManager
public class LogManager {
    
      //3、实际上这里获得的是一个
      public static Logger getRootLogger() {
        //这里我通过看源码得知实际上获得的是一个new RootLogger((Level) Level.DEBUG)
        return getLoggerRepository().getRootLogger();
      }
}

  • 通过看源码我们总结得到获取的logger实例的父属性rootlogger其level=DEBUGAppender=ConsoleAppenderLayout=PatternLayout(pattern="%r [%t] %p %c %x - %m%n")


四、自定义配置文件

4.1、LogManager、OptionConverter源码分析*

之前在调用Logger.getLogger()获取到Logger实例前,实际上会对LoggerManager进行初始化,看下面源码:

//org.apache.log4j.Logger
public class Logger extends Category {

    //1、获取name为传入clazz的类名的实例
    Logger getLogger(Class clazz) {
      //此时会调用LogManager的静态方法取得logger实例(此时首先做的动作是对LogManager进行初始化操作,执行其中的静态模块)
      return LogManager.getLogger(clazz.getName());//见2
    }
}
   
//org.apache.log4j.LogManager
public class LogManager {
    
      //静态模块
      //这两个配置文件是通过类加载器的getResource()来查找到的
      static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
      static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  
      //下面三个是去查找System.getProperty获取键值对的值
      static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";
      static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
      public static final String DEFAULT_INIT_OVERRIDE_KEY = 
                                                     "log4j.defaultInitOverride";
      static private Object guard = null;
      static private RepositorySelector repositorySelector;

      static {
        //之前的getRootLogger()就是返回的该实例中的rootlogger
        Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);
        String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
                                   null);
        if(override == null || "false".equalsIgnoreCase(override)) {
              String configurationOptionStr = OptionConverter.getSystemProperty(
                                      DEFAULT_CONFIGURATION_KEY, 
                                      null);
              String configuratorClassName = OptionConverter.getSystemProperty(
                                                           CONFIGURATOR_CLASS_KEY, 
                                   null);
              URL url = null;
              if(configurationOptionStr == null) {	
                    //这里是去找log4j.xml这个配置文件
                    url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
                    if(url == null) {
                      //重要:若是没有log4j.xml就去查看是否有log4j.properties这个配置文件
                      //会去classpath目录下去查找log4j.properties(Maven打包后则为target/classes目录下查找)
                      //对于Maven项目我们将log4j.properties放置在resource目录下
                      url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
                    }
              } else {
                    try {
                      url = new URL(configurationOptionStr);
                    } catch (MalformedURLException ex) {
                      url = Loader.getResource(configurationOptionStr); 
                    }	
              }
              //若是找到log4j.xml或者log4j.properties的配置文件时
              if(url != null) {
                    //该方法默认是关闭打印提示信息的(因为LogLog的debugEnabled默认为false)
                    LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
                    try {
                        //进行读取配置方法(重要)
                        OptionConverter.selectAndConfigure(url, configuratorClassName,
                                   LogManager.getLoggerRepository());//见3
                    } 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."); 
            }  
      } 
    
    
    //2、这里如何获取实例先不深究(主要看前面的静态模块初始化)
    public static Logger getLogger(final String name) {
    	return getLoggerRepository().getLogger(name);
  	}
}

//org.apache.log4j.helpers.OptionConverter  (一个log4j的工具类)
public class OptionConverter {

    //3、开始进行读取xml或properteies的配置文件
    static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
       Configurator configurator = null;
       //获取配置文件的名称
       String filename = url.getFile();
	   //这里是判断读取的配置文件是否为log4j.xml文件
       if(clazz == null && filename != null && filename.endsWith(".xml")) {
             //若是的话则会提供一个DOMConfigurator工具类(应该是用于解析XML的)
             clazz = "org.apache.log4j.xml.DOMConfigurator";
       }
	   //若是配置文件log4j.xml的话
       if(clazz != null) {
         LogLog.debug("Preferred configurator class: " + clazz);
         //使用反射技术获取到DOMConfigurator实例(用于解析xml工具类的)
         configurator = (Configurator) instantiateByClassName(clazz,
                                  Configurator.class,
                                  null);
         if(configurator == null) {
              LogLog.error("Could not instantiate configurator ["+clazz+"].");
              return;
         }
       } else {
         //若读取到时log4j.properties时,获取PropertyConfigurator实例(应该是用来读取properteis配置文件类)
         configurator = new PropertyConfigurator();
       }
	   
       //这里使用到了多态的技术,根据使用不同的解析类来进行解析操作(重点关注)
       configurator.doConfigure(url, hierarchy);
   }
  • 从源码中我们可以看到在获取logger实例前会优先加载存在的配置文件或系统变量项,其中读取指定配置文件包含log4j.xml以及log4j.properteis文件,也就是说我们能够进行自定义配置文件来对日志进行操作。

其中的configurator实际为一个接口,包含多个实现类,其中第一个实现类中的方法用来解析XML配置,第二个实现类用来解析properties文件:

image-20210303232444073

image-20210303232648335

一般来说我们使用XML配置文件或者Properteis进行配置,那么我们就可以重点关注DOMConfigurator(读取xml配置文件)以及PropertyConfigurator(读取properteis配置文件)。当我们使用哪种配置文件时就可以看指定实现类的解析方法从而知道其中的配置项有哪些了!!!



4.2、PropertyConfigurator类源码分析*

前面说到了对于读取log4j.properties配置文件的操作类是PropertyConfigurator类:

image-20210304200213793

image-20210304200258938

  • 若是读取的是log4j.properteis,就会执行该类的doConfigure()方法。

下面部分是源码中的注释内容,其中包含了如何在log4j.properties中进行键值对的设置,下面的内容是我通过IDEATranslate插件翻译过来的:

从文件中读取配置。 现有配置不会清除也不会重置。 如果你需要一个不同的行为,然后调用resetConfiguration之前调用方法doConfigure 。
配置文件由格式为key=value的语句组成。 下文讨论了不同配置元素的语法。
整个存储库的阈值
整个存储库阈值均按级别过滤日志记录请求,而与记录器无关。 语法为:
    log4j.threshold=[level]
    
级别值可以包含字符串值OFFFATALERRORWARNINFODEBUGALL或自定义级别值。 可以以level#classname形式指定自定义级别值。 默认情况下,存储库范围的阈值设置为最低可能值,即ALL级别。
追加配置
Appender的配置语法为:
    # For appender named appenderName, set its class.
    # Note: The appender name can contain dots.
    log4j.appender.appenderName=fully.qualified.name.of.appender.class

    # Set appender specific options.
    log4j.appender.appenderName.option1=value1
    ...
    log4j.appender.appenderName.optionN=valueN
    
对于每个命名的附加程序,您都可以配置其Layout 。 配置追加器布局的语法为:
    log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
    log4j.appender.appenderName.layout.option1=value1
    ....
    log4j.appender.appenderName.layout.optionN=valueN
    
将Filter添加到附加程序的语法为:
    log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
    log4j.appender.appenderName.filter.ID.option1=value1
    ...
    log4j.appender.appenderName.filter.ID.optionN=valueN
    
第一行定义了由ID标识的过滤器的类名; 具有相同ID的后续行指定过滤器选项-值巴黎。 多个过滤器按照ID的字典顺序添加到附加器。 将ErrorHandler添加到附加程序的语法为:
    log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.class
    log4j.appender.appenderName.errorhandler.root-ref={true|false}
    log4j.appender.appenderName.errorhandler.logger-ref=loggerName
    log4j.appender.appenderName.errorhandler.appender-ref=appenderName
    log4j.appender.appenderName.errorhandler.option1=value1
    ...
    log4j.appender.appenderName.errorhandler.optionN=valueN
    
配置记录器
配置根记录器的语法为:
      log4j.rootLogger=[level], appenderName, appenderName, ...
    
此语法意味着可以提供一个可选级别,后跟以逗号分隔的追加程序名称。
级别值可以包含字符串值OFFFATALERRORWARNINFODEBUGALL或自定义级别值。 可以使用level#classname形式指定自定义级别值。
如果指定了级别值,则将根级别设置为相应级别。 如果未指定级别值,则根级别保持不变。
可以为根记录器分配多个附加程序。
每个appenderName (用逗号分隔)将被添加到root记录器中。 命名的附加程序是使用上面定义的附加程序语法定义的。
对于非根目录类别,语法几乎相同:
    log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
    
上面已针对根记录器讨论了可选级别值的含义。 但是,此外,可以指定值INHERITED,这意味着命名的记录器应从记录器层次结构继承其级别。
如果未提供级别值,则命名记录器的级别保持不变。
默认情况下,类别从层次结构继承其级别。 但是,如果设置了记录器的级别,然后又决定该记录器应继承其级别,则应将INHERITED指定为该级别值的值。 NULLINHERITED的同义词。
与root记录器语法相似,每个appenderName (用逗号分隔)将附加到命名记录器。
有关可加additivity标志的含义,请参见用户手册中的附加器可加性规则。
对象渲染器
您可以定制在记录之前将给定类型的消息对象转换为String的方式。 通过为要自定义的对象类型指定一个ObjectRenderer来完成此操作。
语法为:
    log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
    
如
    log4j.renderer.my.Fruit=my.FruitRenderer
    
ThrowableRenderer
您可以自定义Throwable实例在记录之前转换为String的方式。 这是通过指定ThrowableRenderer来完成的。
语法为:
   log4j.throwableRenderer=fully.qualified.name.of.rendering.class
   log4j.throwableRenderer.paramName=paramValue
   
如
   log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer
   
记录仪工厂
不鼓励使用自定义记录器工厂,并且不再记录在案。
重置层次结构
当属性文件中存在log4j.reset = true时,将在配置之前重置层次结构。
例子


下面给出了示例配置。 其他配置文件示例在examples文件夹中给出。
    # Set options for appender named "A1".
    # Appender "A1" will be a SyslogAppender
    log4j.appender.A1=org.apache.log4j.net.SyslogAppender

    # The syslog daemon resides on www.abc.net
    log4j.appender.A1.SyslogHost=www.abc.net

    # A1's layout is a PatternLayout, using the conversion pattern
    # %r %-5p %c{2} %M.%L %x - %m\n. Thus, the log output will
    # include # the relative time since the start of the application in
    # milliseconds, followed by the level of the log request,
    # followed by the two rightmost components of the logger name,
    # followed by the callers method name, followed by the line number,
    # the nested disgnostic context and finally the message itself.
    # Refer to the documentation of PatternLayout for further information
    # on the syntax of the ConversionPattern key.
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n

    # Set options for appender named "A2"
    # A2 should be a RollingFileAppender, with maximum file size of 10 MB
    # using at most one backup file. A2's layout is TTCC, using the
    # ISO8061 date format with context printing enabled.
    log4j.appender.A2=org.apache.log4j.RollingFileAppender
    log4j.appender.A2.MaxFileSize=10MB
    log4j.appender.A2.MaxBackupIndex=1
    log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
    log4j.appender.A2.layout.ContextPrinting=enabled
    log4j.appender.A2.layout.DateFormat=ISO8601

    # Root logger set to DEBUG using the A2 appender defined above.
    log4j.rootLogger=DEBUG, A2

    # Logger definitions:
    # The SECURITY logger inherits is level from root. However, it's output
    # will go to A1 appender defined above. It's additivity is non-cumulative.
    log4j.logger.SECURITY=INHERIT, A1
    log4j.additivity.SECURITY=false

    # Only warnings or above will be logged for the logger "SECURITY.access".
    # Output will go to A1.
    log4j.logger.SECURITY.access=WARN


    # The logger "class.of.the.day" inherits its level from the
    # logger hierarchy.  Output will go to the appender's of the root
    # logger, A2 in this case.
    log4j.logger.class.of.the.day=INHERIT
    
有关特定于类的选项,请参见每个Appender和Layout中的setOption方法。
使用#或! 注释行的开头字符。
  • 可以看其中的示例配置,查看用法。

若是想要知道如何进行解析log4j.properties,可以去看源代码中的方法。



4.3、初次自定义配置文件(log4j.properties)

配置要求:给rootlogger设置日志等级为trace,其appenderConsoleAppender,该appenderlayoutSimpleLayout

实现过程

由于我们创建的Maven项目,将自定义配置文件log4j.properties放置到resource目录下:

image-20210304204559174

# rootLogger配置,第一个参数为日志等级,第二个参数为指定的appender(使用别名)
log4j.rootLogger = trace,console
# 这里的console只是作为指定appender的别名
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 这里就是设置ConsoleAppender的layout为SimpleLayout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout

测试一下:

public class LogTest {
    public static void main(String[] args) {
        //获取logger实例
        Logger logger = Logger.getLogger(LogTest.class);
        //打印6个不同的日志等级
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}
  • 可以看到当我们使用自定义配置文件时,就不需要像之前一样调用BasicConfigurator.configure();来进行配置rootlogger了。

image-20210304205008347



五、Log4j内置的日志LogLog

5.1、LogLog源码分析以及开启其debug模式*

LogLog:它是Log4j内置的Log,能够记录自身执行的过程信息,内部统一了开关的判断。

  • 目的:可以观察配置信息,便于后期维护。

引出LogLog类

LogManager类源码中我们可以看到需要LogLog进行打日志的操作,如下:

public class LogManager {

    //静态代码块中
    static{
        ...
              if(url != null) {
                LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
                try {
                    OptionConverter.selectAndConfigure(url, configuratorClassName,
                               LogManager.getLoggerRepository());
                } catch (NoClassDefFoundError e) {
                    //若是在初始化配置时出现异常打了warn级别日志
                    LogLog.warn("Error during default initialization", e);
                }
              } else {
                 //资源没找到,打了debug日志
                LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
              }
            } else {
                LogLog.debug("Default initialization of overridden by " + 
                    DEFAULT_INIT_OVERRIDE_KEY + "property."); 
            }  
      ...
    }
  
  //静态方法中
  static public LoggerRepository getLoggerRepository() {
    if (repositorySelector == null) {
        repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
        guard = null;
        Exception ex = new IllegalStateException("Class invariant violation");
        String msg =
                "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
        //在判断是否是可能的安全方案中,调用了LogLog进行日志输出
        if (isLikelySafeScenario(ex)) {
            LogLog.debug(msg, ex);
        } else {
            LogLog.error(msg, ex);
        }
    }
    return repositorySelector.getLoggerRepository();
  }
}

此时我们会抛出疑问,为什么明明打了不同的日志等级在显示器中并不显示呢?我们依旧去看LogLog类源码:


LogLog源码

LogLog类:其是单独的一个类

image-20210304230025304

  • 可以看到其中包含了debugerrorwarn这几个日志等级的方法。
public class LogLog {
    
      //默认都是false
      protected static boolean debugEnabled = false;  
      private static boolean quietMode = false;
    
      //error方法,默认会输出err问题
      public static void error(String msg) {
            if(quietMode)
               return;
            System.err.println(ERR_PREFIX+msg);
      }  
    
      //debug方法
      public static void debug(String msg) {
            //由于debugEnabled是false,所以不会执行方法体内容
            if(debugEnabled && !quietMode) {
              System.out.println(PREFIX+msg);
            }
      }
    
      //warn方法,默认是会输出错误情况
      void warn(String msg) {
            if(quietMode)
              return;

            System.err.println(WARN_PREFIX+msg);
      }  
}
  • 通过看源码我们得知,对于进行debug的调试日志是默认关闭的,而对于warnerror警告及错误信息默认是会输出的。这样其实我们就能知晓为什么在第三部分没有进行配置rootlogger时的报错信息了!!!

开启LogLogdebug模式

那么我们怎么样开启调试debug信息呢?其实在LogLog类中给我们提供了一个方法可以设置debugEnabled的布尔值!源码如下:

/**
   Allows to enable/disable log4j internal logging.
 */
static public void setInternalDebugging(boolean enabled) {
  debugEnabled = enabled;
}

那么我们赶紧试试调用方法将debugEnabled设置true之后日志调试的效果吧。

开启LogLog.setInternalDebugging(true);


我们使用4.3中的案例(自定义log4j.properties)进行测试:

import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;

public class LogTest {
    public static void main(String[] args) {
        //开启LogLog的debug模式
        LogLog.setInternalDebugging(true);

        //获取logger实例
        Logger logger = Logger.getLogger(LogTest.class);
        //打印不同的日志等级
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}

image-20210304232125750

其中还有个void setQuietMode(boolean quietMode),若是设置成true,那么LogLog中的三个日志等级都失效了!!!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。