有关为什么学习Java日志框架和 JUL 的使用在上篇说过:
JUL的介绍及其使用
Log4j 全称 Logging for Java
;它是一个用于Java编程语言的日志记录库(框架),支持多种日志记录格式。包括文本、xml、数据库。和其他日志框架一样,可以把日志信息发送到控制台、文件等不同目的地。有强大的配置功能,当然也可以进行过滤,根据实际情况而论,还是比较灵活的。
Log4j 有三个主要的组件:
这三组件在上篇 JUL 中也阐述过,Log4j 中的 Logger 也是存在父子关系的,其关系和JUL是一样的。
看看log4j官方对该父子关系的举例(log4j介绍官网):
Logger 的基本用法:
public class Logger {
// Creation & retrieval methods:
public static Logger getRootLogger();
public static Logger getLogger(String name);
// printing methods:
public void trace(Object message);
public void debug(Object message);
public void info(Object message);
public void warn(Object message);
public void error(Object message);
public void fatal(Object message);
// generic printing method:
public void log(Level l, Object message);
}
从方法体中也可以发现 log4j 的日志级别名称不同于 JUL。
log4j 的级别(TRACE、DEBUG、INFO、WARN、ERROR、FATAL)。
org.apache.log4j.Logger 不同于 java.util.logging.Logger 的是其静态方法的 getLogger 方法除了可以传字符串以外,还可以传Class对象
(当然其本质还是XX.class.getName,重载的好处,让你少写点代码)。
测试:
下面出现警告信息表示:无法找到该logger的输出源,就是不知道往哪里去输出。
我们可以上官网了解一下如何进行基础配置(就像JUL是默认使用的是conf下面的logging.properties配置文件):
配置后进行检测:
可以观察到该基础配置的日志级别的debug
。
BasicCofigurator
中 configure
这个静态方法的实现:看完之后咱就可以学着自行去配置输出源和字符串的匹配模式了。上面匹配的是配置父类的,让其按那个格式在控制台进行输出,然后子类会自动继承,也拥有该‘能力’。
首先需要一个输出源(Appender),输出源需要设置输出日志信息的所在位置(setWriter),再确定输出格式,确定日志信息的布局(Layout)。最后将日志联系起输出源进行输出日志信息。
2. 进入到 parse 方法中查看解析过程。首先是去遍历 pattern 字符串,然后遇到占位符(%?)会先存入到 currentLiteral
(StringBuffer对象) 当中,然后再通过 finalizeConverter
方法进行转换。
3. 进入到 finalizeConverter
方法中查看具体实现。
4. 看看最后 head 链表咋用的(看看如何进行布局的)。
然后下面对上面占位符的具体含义进行介绍(及 finalizeConverter 对占位符的操作)。
%m 输出代码中指定的日志信息;
%p 输出日志级别,及 DEBUG、INFO 等;
%n 换行符;
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间。这种格式:2023-02-27 09:58:12,977
%l 输出日志时间发生的位置,包括类名、线程、及其代码中的行数。如:com.ncpowernode.test.TestLog4j.testLog4j(TestLog4j.java:44)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号。
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
其中 %l
所可以输出的内容,就包括了类的全名,及其输出代码中的行号。
下面测试一下:“%d [%p]- %l %m%n"的效果:
我们知道获取 Logger 对象是通过 Logger的静态方法 getLogger 去得到的。
static public Logger getLogger(Class clazz) {
return LogManager.getLogger(clazz.getName());
}
解析配置文件肯定是要在获取 Logger 对象之前的,如何在此之前呢?
通过静态代码块进行实现。 我们知道 getLogger 方法被调用了,那 LogManager 也会被初始化,所以其静态成员会运行。
下面是部分静态代码块的静态代码,主要就是先会去拿 log4j.xml 的URL,拿不到就去找 log4j.properties 的URL,然后去解析。
static{
URL url = null;
// 如果用户没有指定log4j.configuration
// 我们首先搜索文件"log4j.xml"然后是"log4j.properties"
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) {
// so, resource is not a URL:
// attempt to get the resource from the class path
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+"].");
}
就是说配置文件名字不能乱取,要么log4j.xml 要么log4j.properties.
配置 log4j.properties 文件(文件名不能乱写)。
log4j.rootLogger=TRACE, A1, FILE
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
# Print the date in ISO 8601 format
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %l - %m%n
# Print only messages of level WARN or above in the package com.foo.
log4j.logger.com.foo=WARN
# FILE users FileAppender
# fileName、append属性通过set方法进行赋值的,这里是set后面的名称
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.file=D://logs/log4j.log
log4j.appender.FILE.append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d [%t] %-5p %l - %m%n
测试:
private static final Logger logger = Logger.getLogger(TestLog4j.class);
@Test
public void testLog4jFirst(){
// 日志记录输出
logger.info("hello log4j");
// 日志级别
logger.fatal("fatal");// 严重错误;一般会造成系统崩溃和终止运行
logger.error("error");// 错误信息, 但不会影响系统运行
logger.warn("warn");// 警告信息, 可能会发生问题
logger.info("info");// 程序运行信息,数据库的连接、网络、IO操作等
logger.debug("debug");// 调试信息,一般在开发阶段使用,记录程序的变量、参数等
logger.trace("trace");// 追踪信息,记录程序的所有流程信息
}
当我们需要自定义去配置 Log4j 时,我们可以在配置文件中进行对应的配置。
在 properties
配置文件中的配置格式:log4j.logger.logger名=级别,Appender...
.
像下面配置 logger 名为 com.ncpowernode.logstudy 的 logger。
对该代码进行测试:
@Test
public void testLog4jSelfConfig(){
logger.trace("trace");
logger.debug("debug");
logger.error("error");
}
输出了两条一样的,是因为
Appender
是可以继承的,可以通过配置或者Java代码进行取消继承,跟 JUL 是一样的,使用配置可以针对某个输出源进行取消,而使用Java代码的话会默认取消所有的父类下来的Appender
。从输出结果可以看见,trace没有没输出,可以说明级别取的是父类(最接近的)。
log4j 中 Logger 类中有个 setAdditivity
方法,可以取消该 logger 上继承下来的 Appender
。
当然也可以通过配置文件进行取消:下面取消 com.ncpowernode 的父类继承的Appender。
然后再将上面的测试代码进行测试结果为:
rootLogger 配置的输出源被完美取消。
log4j.xml 配置感觉会比 properties 配置要好点,复用性强点。
具体可以看 Log.xml配置文件详解.