3.第三章Log4j
3.1Log4j简介
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
最令人感兴趣的就是,这些可以通过一个配置文件来灵活的进行配置,而不需要修改应用的代码。
3.2Log4j组件介绍
Log4j主要由Loggers(日志记录器)、Appenders(输出控制器)和Layout(日志格式化器)组成。其中Loggers控制日志的输出以及输出级别(JUL做日志级别Level);Appenders指定日志的输出方式(输出到控制台、文件等);Layout控制日志信息的输出格式。
1.Loggers
日志记录器,负责收集处理日志记录,实例的命名就是类的全限定名,如com.lsh.log4j.XX,Logger的名字大小写敏感,其命名有继承机制;例如 name为com.lsh.log4j的logger会继承name为com.lsh。Log4j中有一个特殊的logger叫做“root”,它是所有logger的根,也就意味着其他所有的logger都会直接或者间接的继承自root。Root logger可以用Logger.getRootLogger()方法获取。自log4j1.2版本以来,Logger类已经取代了Category类。对于熟悉早期版本的log4j的人来说,Logger类可以被视为Category类的别名。
关于日志级别信息,例如DEBUG、INFO、WARN、ERROR...级别是分大小的,DEBUG<INFO<WARN<ERROR,分别用来指定这条日志信息的重要程度,Log4j输出日志的规则是:只输出级别不低于设定级别的日志信息,假如Loggers级别设定为INFO,则INFO、WARN、ERROR级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。
2.Appenders
记录日志以及定义日志的级别仅仅是log4j的基本功能,Log4j日志系统还提供许多强大的功能,比如允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其他地方等等。
常用Appenders:
ConsoleAppender 将日志输出到控制台
FileAppender 将日志输出到文件
DailyRollingFileAppender 将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender 将日志信息输出到一个日志文件,并且指定文件的大小,当文件大小达到指定大小时,会自动把文件改名,同时产生一个新的文件
JDBCAppender 将日志信息保存到数据库中
3.Layouts
有时用户希望根据自己的喜好格式化自己的日志输出,Log4j可以在Appenders的后面附加上Layouts来完成这个功能。Layouts提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。
常用Layouts:
HTMLLayout 格式化日志输出为HTML表格形式
SimpleLayout 简单的日志输出格式化,打印的日志格式如默认INFO级别的消息
PatternLayout 最强大的格式化组件,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式
3.3日志输出格式说明
使用PatternLayout可以自定义格式输出,是我们最常用的方式。
这种格式化输出采用类似于C语言的printf函数的打印格式格式化日志信息,具体的占位符及其含义如下:
%m 输出代码中指定的日志信息
%p 输出优先级,及DEBUG、INFO等
%n 换行符(Windows平台的换行符为“\n”,Unix平台为“\n”)
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日HH:mm:ss}
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中行号
%% 输出一个“%”字符
可以在%与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如下:
%5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
%-5c 输出category名称,最小宽度是5,category<5,“-”号指定左对齐,会有空格
%.5c 输出category名称,最大宽度5,category>5,就会将左边多出的字符截掉,<5不会有空格
%20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉
3.4案例分析
第一步我们先创建一个maven项目,我们需要引入Log4j日志实现的依赖,并引入JUnit对日志实现进行测试
<dependencies>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- log4j依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
从执行结果我们可以了解到,程序在执行过程中未发现可以的Appenders ,并且提示需要对log4j系统配置进行初始化,因此我们可以通过以下方式进行初始化信息加载
在上一章的JUL学习中我们通过Logger的log方法找到了Level类了解到了JUL的日志级别划分,Log4j我们可以使用同样的方法找到Level类了解Log4j的日志级别划分情况,如下:
Log4j提供了8个级别的日志输出,分别为:
ALL 最低等级,用于打开所有级别的日志记录
TRACE 程序推进下的追踪信息,这个追踪信息的日志级别非常低,一般情况下是不会使用的
DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要配置开发,在开发过程中打印一些重要的运行信息
INFO 消息的粗粒度级别的运行信息
WARN 表示警告,程序在运行过程中会出现的有可能会发生的隐形的错误
ERROR 系统的错误信息,发生的错误不影响系统的运行
FATAL 表示严重的错误,它是那种一旦发生,系统就不可能继续运行的严重错误,如果这种级别的错误出现了,表示程序可以停止运行了
OFF 最高等级的级别,用户关闭所有级别的日志记录
其中DEBUG是我们在没有设置的情况下,默认的日志级别
接下来,我们可以对Log4j的源码进行一下分析
首先我们可以进入到BasicConfigurator初始化配置的方法中,从中我们可以了解到如下两点信息:
(1)创建了根节点的对象Logger root = Logger.getRootLogger();
(2)根节点添加了ConsoleAppender对象(表示默认打印到控制台,自定义的格式化输出)
接下来进一步分析,我们进入到Logger.getLogger(Log4jTest.class);的getLogger()方法里,我们看到LogManager.getLogger(clazz.getName());,Log4j使用了日志管理器
我们进入LogManager,看一看日志管理器里都实现了什么
从中我们看到很多常量信息,它们代表的是不同形式(后缀名不同)的配置文件,我们最常用的肯定是log4j.properties属性文件(语法简单,使用方便),由此我们继续分析,log4j.properties属性文件的加载时机,继续观察LogManager
在 LogManager的static静态代码块中我们找到上图红框中的一行代码,这行代码给我们一个重要提示信息,系统默认要从当前类路径下找到log4j.properties,对于我们当前maven项目,那么理应在resources路径下,找到如何加载属性文件后,我们继续对LogManager进行分析,观察配置文件是如何读取的
找到上图红框代码,进入方法中继续观察
作为属性文件的加载,执行相应的properties配置对象:configurator = new PropertyConfigurator();,进入到PropertyConfigurator类中观察里面的常量信息
这些常量信息就是我们在properties属性文件中的各种属性配置项, 其中,我们看到了如下两项信息,这两项信息是必须要进行配置的
static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
static final String APPENDER_PREFIX = "log4j.appender.";
继续观察PropertyConfigurator类,我们找到
String prefix = APPENDER_PREFIX + appenderName;
我们需要自定义一个appenderName,我们起名叫做console(起名字也需要见名知意,console那么我们到时候配置应该配置的就是控制台输出)log4j.appender.console,取值就是log4j中为我们提供的appender类,例如:
log4j.appender.console=org.apache.log4j.ConsoleAppender
通过代码我们还可以发现
String layoutPrefix = prefix + ".layout";
在appender输出的过程中,还可以同时指定输出的格式,取值就是log4j中为我们提供的Layout类,例如:
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
通过log4j.rootLogger继续在类中分析,找到configureRootCategory方法,在这个方法中执行了parseCategory方法,观察该方法找到上图红框内代码,第一个表示要以逗号的方法来切割字符串,证明了log4j.rootLogger的取值,其中可以多个值,使用逗号进行分隔,第二个表示切割后的第一个值是日志级别,继续向下分析
红框内代码表示接下来的值,是可以通过while循环遍历得到的,第2~第n个值,就是我们配置的其他信息,这个信息就是appenderName,证明了我们配置的方式log4j.rootLogger=日志级别,appendName1,appendName2,appendName3... 表示可以同时在根节点上配置多个日志输入的途径,根据上述分析,我们就可以去除原有的配置代码,使用属性文件进行log4j的配置
下面我们通过log4j.properties属性文件的方式进行log4j配置,首先需要在项目的resources路径下创建log4j.properties属性文件,文件中所有配置内容如下:
#配置根节点logger(日志级别为trace,输出方式为console输出到控制台)
log4j.rootLogger=trace,console
#配置自定义logger(日志级别为info,输出方式为file输出到文件)
log4j.logger.com.lsh.log4j.test=info,file
#配置apache的logger(日志级别为error,输出方式为console输出到控制台)
log4j.logger.org.apache=error,console
#配置appender输出方式 输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#配置输出的格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#配置自定义格式
log4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
#配置appender输出方式 输出到文件
log4j.appender.file=org.apache.log4j.FileAppender
#配置输出的格式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
#配置自定义格式
log4j.appender.file.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
#第一个file是自己命名的appender名称,第二个file是用来指定文件位置的属性
log4j.appender.file.file=D://my-projects//log-framework-study//logs//log4j.log
log4j.appender.file.encoding=UTF-8
#配置RollingFileAppender,按照文件大小进行拆分
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
#配置日志文件的输出路径
log4j.appender.rollingFile.file=D://my-projects//log-framework-study//logs//log4j.log
#配置文件编码格式
log4j.appender.rollingFile.encoding=UTF-8
#指定日志文件内容大小(文件超出1MB进行拆分)
log4j.appender.rollingFile.maxFileSize=1MB
#指定日志文件的数量(日志超出时,按照时间来进行覆盖,原则就是保留新的,覆盖旧的)
log4j.appender.rollingFile.maxBackupIndex=5
#配置DailyRollingFileAppender,按照时间来进行文件的拆分
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.dailyRollingFile.file=D://my-projects//log-framework-study//logs//log4j.log
log4j.appender.dailyRollingFile.encoding=UTF-8
#拆分策略(默认按照天来拆分'.'yyyy-MM-dd)
log4j.appender.dailyRollingFile.datePattern='.'yyyy-MM-dd HH-mm-ss
#配置appender输出方式 输出到数据库表
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.cj.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=bjaxt
log4j.appender.logDB.Sql=INSERT INTO log4j_log(name,create_time,level,category,file_name,message) values('project_log','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%m')
以上log4j的配置文件中包含了根节点logger,自定义logger以及apache的logger,并且配置了控制台输出appender,文件输出appender,按文件大小拆分appender,按时间拆分appender以及数据库表输出appender,从根节点logger与自定义logger的输出情况我们可以得出,如果根节点logger和自定义父logger配置的输出位置不同时,则取二者的并集,配置的位置都会进行输出操作,如果二者的日志级别不同,主要以自定义父logger的级别输出。
自定义logger的应用场景
我们之所以要自定义logger,就是为了针对不同系统信息做更加灵活的输出操作
例如:
我们可以在原有案例的基础之上,加上一个apache的日志输出
#配置apache的logger
log4j.logger.org.apache=error,console
我们现在在配置文件中配置的logger有如下3项:
log4j.rootLogger=trace,console
log4j.logger.com.lsh.log4j.test=info,file
log4j.logger.org.apache=error,console
当前类路径com.lsh.log4j.test.Log4jTest,在配置文件中所找到的能够作用的父logger和根logger
log4j.rootLogger=trace,console
log4j.logger.com.lsh.log4j.test=info,file
我们刚才配置的apache的路径和我们的com.lsh.log4j.test.Log4jTest不相符,不构成父子关系,所以没有执行apache相关的配置
org.apache.log4j.Logger
console在根节点中进行了配置,在apache中也进行了配置,由于输出的位置appender取的是并集,所以,既然都配置了,那么就输出了两次
3.5总结
Log4j日志框架使用方式总结
1.初始化LogManager,LogManager加载log4j.properties配置,添加Logger到LogManager
2.配置根节点、自定义logger,参数为=日志级别level,输出位置appender1,输出位置appender2...
3.配置输出方式appender,控制台输出、文件输出、按大小文件拆分输出、按时间文件拆分输出、数据库表输出