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

举报
长路 发表于 2022/11/28 08:33:45 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

六、各种Layout案例

6.1、PatternLayout(自定义格式示例)

创建过程

①创建log4j.propertiesMaven项目放置到resource目录下

# 设置rootlogger日志等级为trace、指定appender为下面定义的ConsoleAppender
log4j.rootLogger = trace,console
# 指定ConsoleAppender设置别名为console
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定ConsoleAppender的layout设置为PatternLayout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 设置该layout的自定义格式(解析时会调用)
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • 设置自定义格式时实际上会先解析出.后面的名称为ConversionPattern之后添加上set,最后通过反射调用void setConversionPattern(String conversionPattern)方法设置自定义格式。

源码查看是如何进行赋值的

经过一番努力,定位到通过反射来执行set方法的操作,看下去即可:

image-20210305004032951

image-20210305004120012

image-20210305004425137

image-20210305004543230

  • 最终就是通过反射来执行void setConversionPattern(String conversionPattern)操作的,不仅感慨那些编写源代码的人是真的强!!!

②执行程序,输出自定义的格式内容

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-20210305004850254

  • 看框中的格式即为我们自定义的格式内容!!!


七、各种Appender示例

7.1、FileAppender及其子类

FileAppender:用于输出到文件,其有两个实现类,DailyRollingFileAppender提供了轮询功能(可根据指定时间点添加文件)、RollingFileAppender提供了根据文件大小拆分的功能。

首先看下层级图

image-20210305185202093

  • WriterAppender:提供属性encoding
    • FileAppender:提供属性append(是否追加,默认true)、file(文件路径)、bufferedIO(布尔值,默认false)、bufferSize(文件大小,默认为8K)。
      • DailyRollingFileAppender:提供属性datePattern(指定间隔轮询年月日时分秒)。
      • RollingFileAppender:提供属性maxFileSize(单个文件最大容量)、maxBackupIndex(最大分割文件数量)。

使用任意实现类时,即可设置其父类的属性。



FileAppender示例

作用:向文件进行输出,其有两个实现类可实现轮询以及分段(多文件)。

示例演示

①创建log4j.properties

log4j.rootLogger = trace,file
# FileAppender
#  file为名称   其中属性file:文件路径   encoding:编码
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.file = C:/Users/93997/Desktop/projectexers/logs/log.txt
log4j.appender.file.encoding = UTF-8
#  设置自定义布局(自定义输出格式)
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

②程序测试

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-20210305191609137

image-20210305190933275

  • 注意其中logs文件需要提前创建。

源码分析

之前我们已经通过源码看到实际赋值是通过反射调用set方法进行的,所以我们直接看其中的set方法以及默认属性即可。

image-20210305191118753

说明:对于encoding编码格式的设置,是因为FileAppender继承了WriterAppender(其中有setEncoding())。



DailyRollingFileAppender示例

作用:轮询功能(根据指定是每小时,还是每天输出log日志),父类是FileAppender,包含父类的所有设置属性。

示例演示

# 日志等级为trace,指定appender为下面以roll作为别名的
log4j.rootLogger = trace,roll
# DailyRollingFileAppender 轮询
log4j.appender.roll = org.apache.log4j.DailyRollingFileAppender
log4j.appender.roll.file = /logs/log4j.log
log4j.appender.roll.encoding = UTF-8
# datePattern指的是根据分钟来进行轮询 =》可设置年月日时分秒毫秒如右: '.'yyyy-MM-dd-HH-mm-ss-SSS
log4j.appender.roll.datePattern = '.'yyyy-MM-dd-HH-mm
#  设置自定义布局(自定义输出格式)
log4j.appender.roll.layout = org.apache.log4j.PatternLayout
log4j.appender.roll.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • log4j.properties放置在resource目录下(Maven项目)。
  • 这里设置路径file/logs:指的是c:\logs目录下

测试程序

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-20210305201255092

image-20210305201500407

  • 上图仅作测试展示,与下方规则操作并不一致。新增的文件后缀实际上就是我们指定的每隔1分钟的时间。
  • 规则说明(测试结果):我们指定路径为/logs/log4j.log,在第一次运行测试程序时会创建log4j.log以及其第二个文件(两个都输出内容),接着第二次运行测试程序时(间隔未满1分钟),则继续会追加到log4j.log中;第三次运行测试程序(超过1分钟),则会再次创建一个后缀添加日期的log文件并将日志内容输入到其中。

源码查看

image-20210305202305452



RollingFileAppender示例

作用:按照指定文件大小进行拆分,拆分最大的文件数量可指定。其父类为FileAppender

示例演示

# 日志等级为trace,指定appender为下面以rollfile作为别名的# 
log4j.rootLogger = trace,rollfile
# RollingFileAppender 分段
log4j.appender.rollfile = org.apache.log4j.RollingFileAppender
log4j.appender.rollfile.file = /logs/log4j.log
log4j.appender.rollfile.encoding = UTF-8
# 设置单个文件最大容量(KBMBGB,其他单位默认传为10MB+1)以及最大文件个数 
log4j.appender.rollfile.maxFileSize = 1MB
log4j.appender.rollfile.maxBackupIndex = 5
#  设置自定义布局(自定义输出格式)
log4j.appender.rollfile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollfile.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • log4j.properties放置在resource目录下(Maven项目)。
  • 其中设置单个容量大小为1MB,最大文件数为5个。

程序测试

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

        for (int i = 0; i < 1000000; i++) {
            //打印不同的日志等级
            logger.fatal("fatal");
            logger.error("error");
            logger.warn("warn");
            logger.info("info");
            logger.debug("debug");
            logger.trace("trace");
        }
}
  • 由于程序执行1次看不到效果,所以我们执行多次来进行查看最终效果。

image-20210305203320564

  • 这些都是LogLog的debug日志,能够看到正在执行的事情。

image-20210305203429897

  • 可以看到对应的5个文件后缀名与我们设置最大文件数有关!!!

规则:若是超过文件容量maxFileSize则进行拆分,最多拆分我们设置的maxBackupIndex值数量的文件个数,若是超过文件数量则按照最先记录日志的进行覆盖。


源码分析

提问:对于最大文件容量maxFileSize可以直接写单位的吗,这么舒服?看下去吧

老样子先看下对应的set方法

image-20210305204314888

接着我们分析其中的set方法:看看是如何根据单位分配的:

public class RollingFileAppender extends FileAppender {
      //默认为10MB  1maxFileSize=1字节
      protected long maxFileSize = 10*1024*1024;
    
      //1、设置容量方法
      public void setMaxFileSize(String value) {
        //调用了OptionConverter辅助类中的转换方法
        maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);//见2
      }
}

public class OptionConverter {
    
   //2、toFileSize()根据传入的String值来转换
   public static long toFileSize(String value, long dEfault) {
        if(value == null)
          return dEfault;
        //首先将字符串中字母全都变为大写
        String s = value.trim().toUpperCase();
        //计量单位1个字节
        long multiplier = 1;
        int index;
        
        //下面进行容量判断,如KB、MB、GB
        if((index = s.indexOf("KB")) != -1) {
          multiplier = 1024;
          s = s.substring(0, index);//例如15KB,则为15
        }
        else if((index = s.indexOf("MB")) != -1) {
          multiplier = 1024*1024;
          s = s.substring(0, index);
        }
        else if((index = s.indexOf("GB")) != -1) {
          multiplier = 1024*1024*1024;
          s = s.substring(0, index);
        }
        if(s != null) {
          try {
             return Long.valueOf(s).longValue() * multiplier;
          }
          catch (NumberFormatException e) {
                LogLog.error("[" + s + "] is not in proper int form.");
                LogLog.error("[" + value + "] not in expected format.", e);
          }
        }
        //若是其他计量单位的话,默认返回10MB+1的容量
        return dEfault;
  }
}

通过查看源码,我们得知其中对字符串中的容量单位进行查找匹配,从而换算为指定的字节容量!!!



7.2、JDBCAppender

示例演示

概述及前提准备

JDBCAppender:这个appender能够连接数据库并且执行指定的SQL语句,主要用于将日志插入到数据库中。

提前准备如下

①创建数据库表log:该表包含了日志的相关消息

CREATE TABLE `log` (
`log_id` int(11) NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
`create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
`level` varchar(255) DEFAULT NULL COMMENT '优先级',
`category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
`file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
`thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
`line` varchar(255) DEFAULT NULL COMMENT '号行',
`all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
`message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
PRIMARY KEY (`log_id`)
);
  • 记得先创建一个test数据库,下面会用到

②导入Mysqljar包(驱动包),在pom.xml中添加坐标

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.32</version>
</dependency>

示例演示

# rootlogger的日志等级是trace,appender为JDBCAppender,logDB是下面指定的别名
log4j.rootLogger = trace,logDB
# JDBCAppender 存储到数据库中
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123456
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('changlu','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
  • 配置信息包含layout(输出格式)、Driver(指定mysql驱动类)、URLUserpassword(连接数据库三要素)以及要执行的sql语句(其中的各种%形式都是PatternLayout中的指定内容,可见上面6.1)
  • SQL对应则为简单的插入语句。

测试程序

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

    //获取logger实例
    Logger logger = Logger.getLogger(LogTest.class);

    for (int i = 0; i < 1000000; i++) {
        //打印不同的日志等级
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}

image-20210305213941723

  • Log4jLogLog的配置信息。

image-20210305213846285



源码分析*

首先依旧是set方法查看:

image-20210305214250529

那么我们会发出疑问,该类是如何执行SQL语句的呢?我们看其中的execute方法。

public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
    
      //类名赋值操作
      public void setDriver(String driverClass) {
            try {
              //类加载我们的mysql驱动,此时进行初始化操作了
              Class.forName(driverClass);
            } catch (Exception e) {
              errorHandler.error("Failed to load driver", e,
                     ErrorCode.GENERIC_FAILURE);
            }
      }

     //获取连接操作
      protected Connection getConnection() throws SQLException {
          if (!DriverManager.getDrivers().hasMoreElements())
             setDriver("sun.jdbc.odbc.JdbcOdbcDriver");

          if (connection == null) {
            //获取数据库连接
            connection = DriverManager.getConnection(databaseURL, databaseUser,
                        databasePassword);
          }

          return connection;
      }
      
      //执行sql语句方法
      protected void execute(String sql) throws SQLException {

            Connection con = null;
            //这里使用到Statement来进行操作
            Statement stmt = null;

            try {
                con = getConnection();

                stmt = con.createStatement();
                //执行sql语句方法
                stmt.executeUpdate(sql);
            } finally {
                if(stmt != null) {
                    stmt.close();
                }
                closeConnection(con);
            }

            //System.out.println("Execute: " + sql);
      }
}

看了源码之后我们知道在setDriver()时就对Mysql驱动类进行了初始化,之后execute()方法中获取数据库连接,获取Statement来进行操作数据库。

舒服了,但是对于解析log4j.properties的源码部分没有进行深入,主要是对于现在的我来说太复杂了就先放着,真心觉得写源码的大佬真的太强了,自己真的还只是个菜鸟!加油吧!



八、自定义Logger

如何自定义Logger呢?依旧是在配置文件中进行配置。

  • 语法log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...。其中logger_name可以设置为指定的包名,若获取logger实例的name为这个包名及包名下的类(全限定名)即归属于该实例。

用途:通过使用自定义Logger能够将第三方包下调用的类以及自己定义的类进行日志区分,对于自己的类的日志信息输出到文件中,而对于第三方包的类输出到屏幕上。

实例演示

# rootLogger日志等级为trace,输出到屏幕上
log4j.rootLogger = trace,console

# 设置两个自定义logger
# xyz.changlu(自己创建的包)自定义logger,日志等级为info,输出到文件
log4j.logger.xyz.changlu = info,file
# 设置org.apache(第三方包)作为一个自定义logger,日志等级为error
log4j.logger.org.apache = error

# console
log4j.appender.console = org.apache.log4j.ConsoleAppender
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

# FileAppender
#  file:文件路径   encoding:编码
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.file = C:/Users/93997/Desktop/projectexers/logs/log.log
log4j.appender.file.encoding = UTF-8
#  设置自定义布局(自定义输出格式)
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • 两个自定义logger默认都会继承rootLogger的,当调用logger实例时不仅仅会调用自身的appender还会调用rootLoggerappender,并且logger实例的日志等级会改变rootLogger的日志等级(为logger实例的日志等级)。

程序测试

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实例
        //getLogger()参数为xyz.changlu包下的类,所以获取到配置文件中xyz.changlu的logger实例
        //logger实例与rootLogger的日志等级都为INFO,本身实例输出到文件,rootLogger输出到窗口
        Logger logger = Logger.getLogger(LogTest.class);
        System.out.println(logger.getName());
        //打印不同的日志等级
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");

        //② org.Apache包下的自定义logger实例,只输出到屏幕(本身实例没有设置logger)
        // 本身实例与rootLogger日志等级为error
        Logger logger1 = Logger.getLogger(Logger.class);
        System.out.println(logger1.getName());
        logger1.fatal("fatal logger1");
        logger1.error("error logger1");
        logger1.warn("warn logger1");
        logger1.info("info logger1");
        logger1.debug("debug logger1");
        logger1.trace("trace logger1");
    }
}

image-20210305233202362

image-20210305233241463

总结:当设置自定义logger配置时,当你设置name为一个包名时(如xyz.changlu),若在程序中调用Logger.getLogger(LogTest.class)(LogTest类的全限定类名为xyz.changlu.LogTest,即也就是logger实例的name)就是在配置文件中配置的xyz.changlulogger实例。



总结

1、Log4jApache提供的第三方日志框架,需要引入第三方jar包,其中同样了包含了LoggerAppender以及Layout,有六个日志等级,默认日志等级为debug。—见第一、二部分。

2、Log4j的自定义配置文件名称为log4j.xml。—见第三、四部分

3、包含了一个内置日志LogLog,需要通过LogLog.setInternalDebugging(true);来进行开启debug等级日志,作用是能够在控制台显示相关的加载配置信息过程,能够清楚其中的流程。—见第五部分

4、对于Log4j必须要对rootLogger进行初始化,默认不提供初始化,若想使用默认提供的需要调用方法BasicConfigurator.configure();初始化或者我们进行自定义配置文件见四、六、七部分,主要着重介绍输出到文件、数据库以及自定义layout的格式。—见四、六、七部分



参考资料

视频:2020年Java进阶教程,全面学习多种java日志框架

[1]. 彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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