Log4j的基本使用

发布于 2022-11-05  2.37k 次阅读


    Log4j是Apache的一个开源项目,通过使用Log4j我们可以控制日志信息输送的目的地(控制台/文件/GUI组件/套接字服务器/NT事件记录器等),也可以控制每一条日志的输出格式,通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程,最重要的是我们可以通过一个配置文件来灵活的进行动态配置,而不需要修改应用代码

官网:https://logging.apache.org/log4j/1.2/

Log4j的产生比Java原生的日志框架JUL还要早,原生的JUL借鉴了Log4j而产生,所以在组件结构和内部逻辑非常相似

1. 组件说明

Log4j重要的三大组件:

  • Loggers:日志记录器,它控制日志输出以及日志级别
  • Appenders:输出控制器,指定日志的输出位置(控制台/文件/GUI组件等等)
  • Layout:日志格式化器,指定日志信息的输出格式

它们分别对标了JUL的loggers,Handler,Formatter

1)Loggers

    日志记录器,负责收集处理日志记录,实例的命名就是类的全限定名,如:com.kxj.log4j.xxx。Logger的名称大小写敏感,其命名和JUL一样具有继承机制,例如:com.kxj.log4j会继承com.kxj的配置信息,具体的全限定名继承抽象的全限定名

    和JUL一样Log4j同样是树结构存储,有一个根Logger(RootLogger),其他所有的Logger都会直接或间接的继承自root,自定义的Logger可以用getRootLogger()方法获取

  • com.kxj.log4j.wql(儿子节点)
  • com.kxj.log4j(父节点)
  • com.kxj(爷节点)
  • RootLogger(根节点)

注:自log4j 1.2版以来,Logger类已经取代了Category类。在早期的Log4j版本,Logger类可以被视为Category类的别名

    关于日志级别信息,例如DEBUG、INFO、WARN、ERROR……级别是分大小的,Debug<Info<Warn<Error,分别用来指定这条日志信息的重要程度,Log4j输出日志的规则是:只输出级别不低于设定级别的日志信息,假设Logger级别设置为Info,则Info、Warn、Error级别的日志信息都会输出,而日志级别比Info低的debug则不会输出(和JUL是一样的原理)

2)Appenders

    记录日志以及定义日志的级别仅仅是Log4j的基本功能,Log4j日志系统还提供了许多强大的功能,比如允许把日志输出到不同的地方(Console/File/GUI组件等等),可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其他地方等等

常用的Appenders:

  1. ConsoleAppender:将日志输出到控制台
  2. FileAppender:将日志输出到文件
  3. DailyRollingFileAppender:按照时间将日志输出到一个日志文件,并且每天输出到一个新的文件
  4. RollingFileAppender:将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会把文件改名,同时产生一个新的文件
  5. JDBCAppender:把日志信息保存到数据库

3)Layouts

    格式输出日志,Layouts提供了四种日志输出样式,如根据Html样式、自由指定样式、包含日志级别与信息的样式与信息的样式和包含日志时间、线程、类别等信息的样式

常用的Layouts:

  1. HTMLLayout:格式化日志输出为HTML表格形式
  2. SimpleLayout:简单的日志输出格式化,打印的日志格式如默认的INFO级别的消息
  3. PatternLayout:最强大的格式化组件,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式

2. 入门案例

导入依赖:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

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

1)和JUL操作一样直接输出(错误示范)

@Test
public void test1(){
    //logger入口程序
    Logger logger = Logger.getLogger(Log4jTest1.class);

    logger.info("wdasdwq");
}

报错:

log4j:WARN No appenders could be found for logger (com.kxj.log4j.test.Log4jTest1).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

原因:没有对Logger进行初始化,没有寻找到appenders,它和JUL不一样,JUL配置文件配置了默认Handler

2)初始化后进行输出

@Test
public void test1(){
    //加载初始化配置
    BasicConfigurator.configure();

    //logger入口程序
    Logger logger = Logger.getLogger(Log4jTest1.class);

    logger.info("wdasdwq");
}

输出:

0 [main] INFO com.kxj.log4j.test.Log4jTest1  - wdasdwq

加载初始化配置底层源码:默认加入了一个rootLogger根节点,并配置了默认的ConsoleAppender输出和PatternLayout格式化

static public void configure() {
  Logger root = Logger.getRootLogger();
  root.addAppender(new ConsoleAppender(
         new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}

3. 日志级别说明

Log4j提供了8个级别的日志等级:由低到高

  1. ALL:最低等级,用于打开所有级别的日志记录
  2. TRACE:跟踪信息,等级也非常低,一般情况下不使用
  3. DEBUG(默认级别):指出细粒度信息事件对调试应用程序是非常有帮助的,主要配合开发,在开发过程中打印一些重要信息
  4. INFO:消息的粗粒度级别运行信息
  5. WARN:警告信息,程序在运行过程中会出现的有可能发生的隐形的错误
  6. ERROR:系统的错误信息,如果不想要输出过多的日志设置该级别即可
  7. FATAL:表示有种错误,一旦系统发生就不能运行的严重错误
  8. OFF:最高等级的级别,用户关闭所有的日志记录

注:

  • ERROR:这个错误的发生不会影响整个系统的运行,最多是某个模块错误,比如:找不到数据库等
  • FATAL:这个错误会直接终结这个系统
  • 和JUL一样,只会输出比当前日志等级更高的日志信息

例:输出各个日志等级

@Test
public void test1(){

    BasicConfigurator.configure();

    Logger logger = Logger.getLogger(Log4jTest1.class);

    logger.trace("输出trace日志等级");
    logger.debug("输出debug日志等级");
    logger.info("输出info日志等级");
    logger.warn("输出warn日志等级");
    logger.error("输出error日志等级");
    logger.fatal("输出fatal日志等级");
}

输出:默认debug,只输出debug以上的日志等级信息

0 [main] DEBUG com.kxj.log4j.test.Log4jTest1  - 输出debug日志等级
4 [main] INFO com.kxj.log4j.test.Log4jTest1  - 输出info日志等级
4 [main] WARN com.kxj.log4j.test.Log4jTest1  - 输出warn日志等级
4 [main] ERROR com.kxj.log4j.test.Log4jTest1  - 输出error日志等级
4 [main] FATAL com.kxj.log4j.test.Log4jTest1  - 输出fatal日志等级

4. 日志管理器分析配置文件

1)BasicConfigurator.configure()源码解析

static public void configure() {
  Logger root = Logger.getRootLogger();
  root.addAppender(new ConsoleAppender(
         new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}
  • 创建了根节点对象,Logger root = Logger.getRootLogger()
  • 根节点添加了ConsoleAppender对象
  • 定义了PatternLayout格式化对象

这是Log4j默认提供的初始化,假如需要自定义从配置文件中获取配置并加载,就不能使用默认初始化

2)分析Logger.getLogger()源码

① 进入该方法

static public Logger getLogger(Class clazz) {
  //返回日志管理器
  return LogManager.getLogger(clazz.getName());
}

② 点击LogManager对象

static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";

static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  

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";

LogManager管理着全部的Log4j配置文件,开发中最常用的是log4j.properties属性文件

③ 加载配置文件的代码

static {
  ……………………

     if(configurationOptionStr == null) {  
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
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 {
          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.");
}} 
}

提供静态代码块加载配置文件,提供Loader.getResource()获取配置文件

  • Loader.getResource说明只能在类路径下寻找并加载配置文件

④ 配置文件读取解析,通过OptionConverter.selectAndConfigure()方法

static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
Configurator configurator = null;
String filename = url.getFile();

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);
}

通过new PropertyConfigurator()进行映射

public class PropertyConfigurator implements Configurator {

  /**
     Used internally to keep track of configured appenders.
   */
  protected Hashtable registry = new Hashtable(11);  
  private LoggerRepository repository;
  protected LoggerFactory loggerFactory = new DefaultCategoryFactory();

  static final String      CATEGORY_PREFIX = "log4j.category.";
  static final String      LOGGER_PREFIX   = "log4j.logger.";
  static final String       FACTORY_PREFIX = "log4j.factory";
  static final String    ADDITIVITY_PREFIX = "log4j.additivity.";
  static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
  static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
  static final String      APPENDER_PREFIX = "log4j.appender.";
  static final String      RENDERER_PREFIX = "log4j.renderer.";
  static final String      THRESHOLD_PREFIX = "log4j.threshold";
  private static final String      THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
  private static final String LOGGER_REF   = "logger-ref";
  private static final String ROOT_REF    = "root-ref";
  private static final String APPENDER_REF_TAG     = "appender-ref";  
………………
  • log4j.rootLogger和log4j.appender、rootLogger是必须要进行配置的

综上所述,假如配置了Log4j.properties文件,logger会自动进行加载,不需要进行初始化配置

5. 配置文件编写格式

Log4j的配置文件和一般其他的配置文件编写格式不一样,需要按照固定格式书写

1)Appender的编写格式

底层源码:

String prefix = APPENDER_PREFIX + appenderName;
  • APPENDER_PREFIX = log4j.appender.

以log4j.appender为前缀在加上自定义名称(appenderName)

2)Layouter的编写格式

底层源码:

String layoutPrefix = prefix + ".layout";
  • 以Appender为前缀在加上loyout进行设置

例:

#配置appender输出方式
log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
#配置输出的格式
log4j.appender.myconsole.layout=org.apache.log4j.SimpleLayout

3)RootLogger的编写格式

  • 通过log4j.rootLogger在类中搜索
  • 找到void configureRootCategory方法

    观察该方法,找到new StringTokenizer(value,","),这个方法表示要用逗号的方式来切割字符串,证明了log4j.rootLogger的取值,其中可以有多个值,使用逗号进行分隔

① 通过代码st.nextToken()表示切割后的第一个值是日志的级别

② while(st.hasMoreTokens())表示接下来的值,可以通过while循环遍历得到2~n个值,这就是配置的其他信息,这个信息就是appenderName

综上所述,RootLogger的配置格式为:

Log4j.rootLogger=日志级别,appenderName1,appenderName2,…………

例:配置文件并进行输出

① 配置文件

#配置appender输出方式
log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
#配置输出的格式
log4j.appender.myconsole.layout=org.apache.log4j.SimpleLayout
#配置根节点
log4j.rootLogger=trace,myconsole

② 代码

@Test
public void test1(){
 
    Logger logger = Logger.getLogger(Log4jTest1.class);

    logger.trace("输出trace日志等级");
    logger.debug("输出debug日志等级");
    logger.info("输出info日志等级");
    logger.warn("输出warn日志等级");
    logger.error("输出error日志等级");
    logger.fatal("输出fatal日志等级");
}

③ 输出 

TRACE - 输出trace日志等级
DEBUG - 输出debug日志等级
INFO - 输出info日志等级
WARN - 输出warn日志等级
ERROR - 输出error日志等级
FATAL - 输出fatal日志等级

输出了TRACE说明设置成功

6. Layout自定义格式

Layouter格式化:

  1. HTMLLayout:格式化日志输出为HTML表格形式
  2. SimpleLayout:简单的日志输出格式化,打印的日志格式如默认的INFO级别的消息
  3. PatternLayout:最强大的格式化组件,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式

其中最常用的是PatternLayout,

这种格式化输出采用类似于C语言的printf函数的打印格式化日志消息,具体的占位符及其含义如下:

  1. %m:输出代码中指定的日志信息
  2. %p:输出优先级,及DEBUG、INFO等
  3. %n:换行符(Windown平台的换行符"\n",UNIX平台为"\n")
  4. %r:输出自应用启动到输出该log信息耗费的毫秒数
  5. %c:输出打印语句所属的类的全名
  6. %t:输出产生该日志的线程全名
  7. %d:输出服务器当前时间,默认为ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
  8. %l:输出日志时间发生的位置,包括类名、线程、及在代码中的行数,如:Test.main(Test.java:10)
  9. %F:输出日志消息产生时所在的文件名称
  10. %L:输出代码中的行号
  11. %%:输出一个"%"字符,可以在%与字符之间加上修饰来控制最小宽度、最大宽度和文本的对其方式

可以在%与字符之间加上修饰符来控制最小宽度,最大宽度和文本对其方式:

  • %5c:输出category名称,最小宽度是5,category<5,默认的情况下右对齐
  • %-5c:输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
  • %.5c:输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
  • %20.30c:名称<20补空格,并且右对齐,>30字符,就从左边交远削出的字符截断

配置文件开启自定义格式:

#配置输出的格式
log4j.appender.appenderName.layout=org.apache.log4j.PatternLayout
#配置具体的PatternLayout
log4j.appender.appenderName.layout.conversionPattern = 自定义格式

例:配置自定义PatternLayout

① 配置文件

#配置appender输出方式
log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
#配置输出的格式
log4j.appender.myconsole.layout=org.apache.log4j.PatternLayout
#配置根节点
log4j.rootLogger=trace,myconsole
#配置具体的PatternLayout格式
log4j.appender.myconsole.layout.conversionPattern=[%p]%r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

② 代码

@Test
public void test1(){

    Logger logger = Logger.getLogger(Log4jTest1.class);

    logger.trace("输出trace日志等级");
    logger.debug("输出debug日志等级");
    logger.info("输出info日志等级");
    logger.warn("输出warn日志等级");
    logger.error("输出error日志等级");
    logger.fatal("输出fatal日志等级");
}

③ 输出

[TRACE]0 com.kxj.log4j.test.Log4jTest1 main 2022-11-04 11:26:50:948 输出trace日志等级
[DEBUG]1 com.kxj.log4j.test.Log4jTest1 main 2022-11-04 11:26:50:949 输出debug日志等级
[INFO]1 com.kxj.log4j.test.Log4jTest1 main 2022-11-04 11:26:50:949 输出info日志等级
[WARN]1 com.kxj.log4j.test.Log4jTest1 main 2022-11-04 11:26:50:949 输出warn日志等级
[ERROR]1 com.kxj.log4j.test.Log4jTest1 main 2022-11-04 11:26:50:949 输出error日志等级
[FATAL]1 com.kxj.log4j.test.Log4jTest1 main 2022-11-04 11:26:50:949 输出fatal日志等级

7. 自定义Logger

默认创建的Logger对象,默认都是继承RootLogger,在开发中可以自定义Logger,让其他Logger来继承中国logger

    这种继承关系和JUL中的继承一样,都是按包结构的关系进行指定的,更具体的包名Logger继承更抽象的包名Logger,最顶级的父类依然是RootLogger

如:Logger logger = Logger.getLogger(Log4jTest1.class);

  • Log4jTest1.class的包路径:com.kxj.log4j.test.Log4jTest1
  • 它的父logger就是它更上层的路径
  • 父logger有:com -> com.kxj -> com.kxj.log4j -> com.kxj.log4j.test 
  • 这些父logger彼此也有继承关系

自定义logger的配置项:

log4j.logger.父路径 = 日志级别,AppenderName1,AppenderName2,…………

例:配置Log4jTest1.class的父路径

#自定义logger
log4j.logger.com.kxj.log4j.test = info,myfile
log4j.appender.myfile=org.apache.log4j.FileAppender
#输出格式
log4j.appender.myfile.layout=org.apache.log4j.SimpleLayout
#文件输出
log4j.appender.myfile.file=D:\\mylog4j.log
#文件编码
log4j.appender.myfile.encoding=UTF-8

同时配置多个父logger的问题:

  • 如果多个父Logger配置的内容不相同,取它们配置并集,如:ParentLogger1配置了FileAppender,rootLogger配置了ConsolAppender,那么最终结果是控制台和文件都会输出
  • 如果多个父Logger配置内容不相同,那么按照直接的父logger的配置执行,如:ParentLogger1的日志等级是Error,RootLogger日志等级为Info,那么最终以ParentLogger1日志级别为准

例:配置父logger(PrantLogger和RootLogger)

① 配置文件

log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
log4j.appender.myconsole.layout=org.apache.log4j.PatternLayout
log4j.appender.myconsole.layout.conversionPattern=[%p]%r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

log4j.appender.myfile=org.apache.log4j.FileAppender
log4j.appender.myfile.layout=org.apache.log4j.SimpleLayout
log4j.appender.myfile.file=D:\\mylog4j.log
log4j.appender.myfile.encoding=UTF-8

#rootLoggeer
log4j.rootLogger=info,myfile
#父logger
log4j.logger.com.kxj.log4j.test=trace,myconsole

② 代码

@Test
public void test1(){
    logger.trace("输出trace日志等级");
    logger.debug("输出debug日志等级");
    logger.info("输出info日志等级");
    logger.warn("输出warn日志等级");
    logger.error("输出error日志等级");
    logger.fatal("输出fatal日志等级");
}

③ 输出

8. 其他

使用LogLog.setInternalDebugging(true)设置输出日志的详细信息

例:

@Test
public void test1(){

    //设置输出日志的详细信息
    LogLog.setInternalDebugging(true);

    Logger logger = Logger.getLogger(Log4jTest1.class);

    logger.trace("输出trace日志等级");
    logger.debug("输出debug日志等级");
    logger.info("输出info日志等级");
    logger.warn("输出warn日志等级");
    logger.error("输出error日志等级");
    logger.fatal("输出fatal日志等级");
}

输出:

log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@18b4aac2.
log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@18b4aac2 class loader.
log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().
log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@18b4aac2.
log4j: Using URL [file:/F:/Dome/Log-Dome/Log4j-Dome/target/classes/log4j.properties] for automatic log4j configuration.
log4j: Reading configuration from URL file:/F:/Dome/Log-Dome/Log4j-Dome/target/classes/log4j.properties
log4j: Parsing for [root] with value=[trace,myconsole].
log4j: Level token is [trace].
log4j: Category root set to TRACE
log4j: Parsing appender named "myconsole".
log4j: Parsing layout options for "myconsole".
log4j: End of parsing for "myconsole".
log4j: Parsed "myconsole" options.
log4j: Finished configuring.
TRACE - 输出trace日志等级
DEBUG - 输出debug日志等级
INFO - 输出info日志等级
WARN - 输出warn日志等级
ERROR - 输出error日志等级
FATAL - 输出fatal日志等级

 


路漫漫其修远兮,吾将上下而求索