本篇文章中涉及到的所有代码都已经上传到gitee中: gitee.com/sss123a/log...
Hello world!
引入maven依赖:
xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第一个log4j程序:
java
import org.apache.log4j.Logger;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = Logger.getLogger(HelloWorld.class);
logger.info("Hello world!");
}
}
输出结果如下: 这里红色提示其实是log4j内部使用LogLog工具类打印的,我们可以调用LogLog.setQuietMode(true); 来禁用log4j内部一切日志输出。具体可以参考LogLog类中的静态代码块支持更强大的功能配置。
那至于为什么log4j要输出这样的提示信息,那是因为还缺少对Logger的进一步配置。 比如我们可以调用BasicConfigurator.configure(); 。其本质是为该logger对象增加了一个ConsoleAppender,它负责将日志输出到console。原理就等同于jul中的java.util.logging.ConsoleHandler类。具体代入如下:
java
package com.matio.log4j.helloworld;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
public class HelloWorld {
public static void main(String[] args) {
BasicConfigurator.configure();
Logger logger = Logger.getLogger(HelloWorld.class);
logger.info("Hello world!");
}
}
另外,log4j默认支持xml格式和properties格式的配置文件,对应log4j中的两个工具类:
- DOMConfigurator:解析xml文件
- PropertyConfigurator 解析properties文件
接下来我们以xml配置文件为例来配置我们的log4j:
1.如果xml文件在在resources目录下,且文件名为log4j.xml,那么log4j可以自动读取该文件,具体可以参考org.apache.log4j.LogManager的静态代码块
2.如果xml文件在resources目录下:
java
DOMConfigurator.configure(Loader.getResource("log4j-asyncappender.xml"))
3.如果xml文件在磁盘上任意位置:
java
DOMConfigurator.configure("E:/WorkspaceIdea/01src/log/matio-log4j/src/main/resources/log4j-asyncappender.xml");
4.也可以把xml文件放到一个指定的位置,并且使用环境变量log4j.configuration 来完成配置文件的指定。注意,在log4j.configuration的值中,可以使用文件名称或者url的方式。比如:
log4j.configuration=log4jconfig.properties (resources目录下) log4j.configuration=file:/c:/log4jconfig.xml
log4j.xml配置文件解析
java
package org.apache.log4j.spi;
import java.io.InputStream;
import java.net.URL;
public interface Configurator {
void doConfigure(InputStream inputStream, LoggerRepository repository);
void doConfigure(URL url, LoggerRepository repository);
}
该接口主要有两个核心实现类
- DOMConfigurator:解析xml文件
- PropertyConfigurator 解析properties文件
两者实现方式大同小异,我们以xml文件为例解剖log4j内部实现:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration
debug="${matio.log4j.debug}"
reset="${matio.log4j.reset}"
threshold="${matio.log4j.threshold}"
xmlns:log4j='http://jakarta.apache.org/log4j/'>
<!-- debug:同configDebug标签: LogLog.setInternalDebugging(boolean)-->
<!-- reset:LoggerRepository.resetConfiguration()-->
<!-- threshold:设置LoggerRepository#setThreshold(String) -->
<!-- loggerFactory:同categoryFactory标签,可以同时存在多个,但是最后一个才生效 -->
<loggerFactory class="${matio.log4j.loggerFactory.class}">
<!-- param属性会通过反射注入到class对象中 -->
<param name="p1" value="v1"/>
<param name="p2" value="v2"/>
<!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->
<age>30</age>
<name value="matio"/>
</loggerFactory>
<!-- appender: name和class属性必须-->
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>
</layout>
<!--过滤器设置输出的级别-->
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="levelMin" value="debug"/>
<param name="levelMax" value="warn"/>
<param name="acceptOnMatch" value="true"/>
</filter>
</appender>
<appender name="myFile" class="org.apache.log4j.RollingFileAppender">
<param name="File" value="D:/output.log"/><!-- 设置日志输出文件名 -->
<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
<param name="Append" value="true"/>
<param name="MaxBackupIndex" value="10"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%p (%c:%L)- %m%n"/>
</layout>
</appender>
<appender name="activexAppender" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="E:/activex.log"/>
<param name="DatePattern" value="'.'yyyy-MM-dd'.log'"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{MMdd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n"/>
</layout>
</appender>
<!-- 指定logger的设置,additivity指示是否遵循缺省的继承机制-->
<!-- logger:同category标签-->
<logger name="com.runway.bssp.activeXdemo" additivity="false">
<!-- level:同priority, -->
<level value="info"/>
<!-- appender-ref:可以存在多个,开始解析目标appender了, -->
<appender-ref ref="activexAppender"/>
<appender-ref ref="myFile"/>
</logger>
<logger name="com.matio.test1" class="com.matio.log4j.logger.LoggerUtil" additivity="false">
<priority value="info" class="com.matio.log4j.level.CusLevel"/>
</logger>
<!-- renderer:可以存在多个 -->
<!-- 如果打印的日志message类型是User,就调用CusObjectRenderer.doRender()二次处理生成新的message -->
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer"
renderedClass="com.matio.log4j.User"/>
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer2"
renderedClass="com.matio.log4j.Test1"/>
<!-- throwableRenderer:class类需要实现ThrowableRenderer接口 -->
<throwableRenderer class="com.matio.log4j.throwablerenderer.CusThrowableRenderer">
<!-- param属性会通过反射注入到当前class对象中 -->
<param name="p100" value="v100"/>
<!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->
<lover>zcf</lover>
</throwableRenderer>
<!-- 根logger的设置-->
<root>
<priority value="debug"/>
<appender-ref ref="myConsole"/>
<appender-ref ref="myFile"/>
</root>
</log4j:configuration>
根节点:log4j:configuration
DOMConfigurator#parse(Element)
用法如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration
debug="${matio.log4j.debug}"
reset="${matio.log4j.reset}"
threshold="${matio.log4j.threshold}"
xmlns:log4j='http://jakarta.apache.org/log4j/'>
</log4j:configuration>
重点: 可以通过${}从系统属性中获取,也可以固定写死
类型 | 描述 | 默认值 | |
---|---|---|---|
log4j:configuration | 根标签 | 同configuration | |
debug | 属性 | 是否打印log4j内部debug日志,同configDebug,LogLog.setInternalDebugging(boolean); | false |
reset | 属性 | 如果为true则调用LoggerRepository.resetConfiguration(); | |
threshold | 属性 | 默认的日志level,LoggerRepository.setThreshold(); | Level.ALL |
loggerFactory同categoryFactory:自定义LoggerFactory
如果配置了该标签,那么该xml文件中所有logger都会通过该loggerFactory去生成,不走默认的了
DOMConfigurator#parseCategoryFactory(Element)
可以同时存在多个,但是最后一个才生效
xml
<!-- loggerFactory:同categoryFactory标签,可以同时存在多个,但是最后一个才生效 -->
<loggerFactory class="${matio.log4j.loggerFactory.class}">
<!-- param属性会通过反射注入到class对象中 -->
<param name="p1" value="v1"/>
<param name="p2" value="v2"/>
<!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->
<age>30</age>
<name value="matio"/>
</loggerFactory>
类型 | 描述 | |
---|---|---|
loggerFactory | 标签 | 用户可以自定义LoggerFactory,同categoryFactory标签 |
class | 属性 | LoggerFactory具体实现类,如果没有设置class属性,则直接跳过该标签 |
param | 标签 | 通过反射将value设置给class类中的属性 |
param.name | 属性 | class类中的属性名称 |
param.value | 属性 | class类中的属性值 |
class属性:可以通过${}从系统属性中获取,比如
System.setProperty("matio.log4j.loggerFactory.class", "com.matio.log4j.loggerfactory.CusLoggerFactory");
也可以固定写死,比如class="com.matio.log4j.loggerfactory.CusLoggerFactory"
备注:如果class实现类实现了UnrecognizedElementHandler接口,那么我们还可以解析自定义xml标签,示例代码:
java
package com.matio.log4j.loggerfactory;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.xml.DOMConfigurator;
import org.apache.log4j.xml.UnrecognizedElementHandler;
import org.w3c.dom.Element;
import java.util.Properties;
public class CusLoggerFactory implements LoggerFactory, UnrecognizedElementHandler {
private String p1; // <param name="p1" value="v1"/>
private String p2; // <param name="p2" value="v2"/>
private int age;
private String name;
public Logger makeNewLoggerInstance(String name) {
return Logger.getLogger(name);
}
public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
String nodeName = element.getNodeName();
if ("age".equals(nodeName)) { // 解析:<age>30</age>
String ageStr = DOMConfigurator.subst(element.getNodeValue(), props);
if (ageStr != null) {
age = Integer.parseInt(ageStr);
}
} else if ("name".equals(nodeName)) { // 解析:<name value="matio"/>
name = DOMConfigurator.subst(element.getAttribute("value"), props);
}
return true;
}
// 忽略getter()和setter()...
}
logger同category
定义一个logger对象
DOMConfigurator#parseCategory(Element)
可以同时存在多个logger标签
xml
<logger name="com.runway.bssp.activeXdemo" additivity="false">
<!-- level:同priority, -->
<level value="info"/>
<!-- appender-ref:可以存在多个,开始解析目标appender了, -->
<appender-ref ref="activexAppender"/>
<appender-ref ref="myFile"/>
</logger>
<logger name="com.matio.test1" class="com.matio.log4j.logger.LoggerUtil" additivity="false">
<priority value="info" class="com.matio.log4j.level.CusLevel"/>
</logger>
类型 | 描述 | |
---|---|---|
logger | 标签 | 创建一个logger对象,同category标签 |
class | 属性 | 通过该类名反射调用其内部的getLogger()方法去生成logger对象 |
name | 属性 | 该logger的name |
additivity | 属性 | 是否遵循缺省的继承机制,默认true |
appender-ref | 标签 | appender的name,这个时候才开始去解析目标appender标签 |
level | 标签 | 该logger的日志level,可以通过class属性自定义level |
priority | 标签 | 同level |
param | 标签 | 通过反射将value设置给class类中的属性 |
param.name | 属性 | class类中的属性名称 |
param.value | 属性 | class类中的属性值 |
在每个logger标签被解析完成后,如果发现该logger类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
root
只能存在一个root标签
xml
<!-- 根logger的设置-->
<root>
<priority value="debug"/>
<appender-ref ref="myConsole"/>
<appender-ref ref="myFile"/>
</root>
类型 | 描述 | |
---|---|---|
appender-ref | 标签 | appender的name,这个时候才开始去解析目标appender标签 |
level | 标签 | 该logger的日志level,可以通过class属性自定义level |
priority | 标签 | 同level |
param | 标签 | 通过反射将value设置给class类中的属性 |
param.name | 属性 | class类中的属性名称 |
param.value | 属性 | class类中的属性值 |
在root标签被解析完成后,如果发现该logger类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
renderer
不知道大家有没有注意到logger.info();方法接收的参数类型是什么.其实都是Object类型(jul接受的都是string类型),这说明log4j可以打印任意类型的日志,比如:
java
BasicConfigurator.configure(); // 日志输出到console上
Logger logger = Logger.getLogger("test");
logger.info(new User("best", "zcf", "matio"));
logger.info(new Test1("aaaa"));
以上两行代码打印结果如下:
js
0 [main] INFO test - User{school='best', like='zcf', name='matio'}
1 [main] INFO test - Test1{x='aaaa'}
我们都知道输出一个对象其实就是输出其string()方法,但是log4j提供了一个工具来用户根据指定的日志对象类型输出对应的日志,这个工具就是org.apache.log4j.or。ObjectRenderer,其接口定义是如下:
java
public interface ObjectRenderer {
String doRender(Object o);
}
参数o就是我们上面的User对象和Test1对象,返回值就是最终要打印的日志结果
这里为了更好的理解什么是ObjectRenderer,附带了一个DEMO,它不需要xml配置文件,示例如下:
java
package com.matio.log4j.objectrenderer;
import com.matio.log4j.Test1;
import com.matio.log4j.User;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.or.RendererMap;
import org.apache.log4j.spi.RendererSupport;
public class ObjectRendererTest {
public static void main(String[] args) {
// 注册一个consoleAppender,等会日志打印在控制台上
BasicConfigurator.configure();
// 注册一个ObjectRenderer专门用于处理User类型的消息
{
// message的class
String renderedClassName = "com.matio.log4j.User";
// ObjectRenderer实现类
String renderingClassName = "com.matio.log4j.objectrenderer.CusObjectRenderer";
RendererMap.addRenderer((RendererSupport) LogManager.getLoggerRepository(), renderedClassName, renderingClassName);
}
// 再注册一个ObjectRenderer专门用于处理Test1类型的消息
{
// message的class
String renderedClassName = "com.matio.log4j.Test1";
// ObjectRenderer实现类
String renderingClassName = "com.matio.log4j.objectrenderer.CusObjectRenderer2";
RendererMap.addRenderer((RendererSupport) LogManager.getLoggerRepository(), renderedClassName, renderingClassName);
}
Logger logger = Logger.getLogger("test");
logger.info(new User("best", "zcf", "matio"));
logger.info(new Test1("aaaa"));
}
}
自定义ObjectRenderer,用于处理User类型和Test1类型的消息:
java
package com.matio.log4j.objectrenderer;
import com.matio.log4j.User;
import org.apache.log4j.or.ObjectRenderer;
import org.apache.log4j.xml.UnrecognizedElementHandler;
import org.w3c.dom.Element;
import java.util.Properties;
public class CusObjectRenderer implements ObjectRenderer, UnrecognizedElementHandler {
public String doRender(Object o) {
if (o instanceof User) {
User user = (User) o;
return "测试ObjectRenderer : " + user.toString();
}
return o.toString();
}
// 这个只在xml配置生效
public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
return true;
}
}
public class CusObjectRenderer2 implements ObjectRenderer {
public String doRender(Object o) {
Test1 test1 = (Test1) o;
return "测试ObjectRenderer222 : " + test1.toString();
}
}
其打印结果如下:
js
0 [main] INFO test - 测试ObjectRenderer : User{school='best', like='zcf', name='matio'}
0 [main] INFO test - 测试ObjectRenderer222 : Test1{x='aaaa'}
那么在xml文件中我们该如何配置renderer呢,具体可以参考:
DOMConfigurator#parseRenderer(Element);
renderer在xml中的配置如下,支持多个:
xml
<!-- renderer:可以存在多个 -->
<!-- 如果打印的日志message类型是User,就调用CusObjectRenderer.doRender()二次处理生成新的message -->
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer"
renderedClass="com.matio.log4j.User"/>
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer2"
renderedClass="com.matio.log4j.Test1"/>
类型 | 描述 | |
---|---|---|
renderer | 标签 | 真的指定类型的日志,就调用CusObjectRenderer.doRender()二次处理生成新的日志 |
renderingClass | 属性 | ObjectRenderer的实现类 |
renderedClass | 属性 | 日志类型 |
throwableRenderer
什么是throwableRenderer?
它可以将Throwable转成String,对应log4j中的接口定义如下:
java
package org.apache.log4j.spi;
public interface ThrowableRenderer {
public String[] doRender(Throwable t);
}
log4j默认使用DefaultThrowableRenderer
没有注册自定义ThrowableRenderer之前,实例代码如下:
java
package com.matio.log4j.throwablerenderer;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.ThrowableRendererSupport;
public class TestThrowableRenderer {
public static void main(String[] args) {
BasicConfigurator.configure(); // 日志输出到console上
Logger logger = Logger.getLogger("Te");
logger.error("错误xxx:", new IllegalArgumentException());
logger.error("错误:");
}
}
输出结果如下:
js
1 [main] ERROR Te - 错误xxx:
java.lang.IllegalArgumentException
at com.matio.log4j.throwablerenderer.TestThrowableRenderer.main(TestThrowableRenderer.java:17)
3 [main] ERROR Te - 错误:
当注册一个自定义的ThrowableRenderer后代码如下:
java
package com.matio.log4j.throwablerenderer;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.ThrowableRendererSupport;
public class TestThrowableRenderer {
public static void main(String[] args) {
BasicConfigurator.configure(); // 日志输出到console上
// 注册一个自定义的ThrowableRenderer
{
CusThrowableRenderer renderer = new CusThrowableRenderer();
((ThrowableRendererSupport) LogManager.getLoggerRepository()).setThrowableRenderer(renderer);
}
Logger logger = Logger.getLogger("Te");
logger.error("错误xxx:", new IllegalArgumentException());
logger.error("错误:");
}
}
java
package com.matio.log4j.throwablerenderer;
import org.apache.log4j.spi.OptionHandler;
import org.apache.log4j.spi.ThrowableRenderer;
import org.apache.log4j.xml.DOMConfigurator;
import org.apache.log4j.xml.UnrecognizedElementHandler;
import org.w3c.dom.Element;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Properties;
public class CusThrowableRenderer implements ThrowableRenderer, OptionHandler, UnrecognizedElementHandler {
private String p100;
private String lover;
public String[] doRender(Throwable t) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
t.printStackTrace(printWriter);
printWriter.close();
String[] strings = new String[1];
strings[0] = "自定义ThrowableRenderer: " + stringWriter.toString();
return strings;
}
public void activateOptions() {
}
// 这里只在xml配置中生效
public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
this.lover = DOMConfigurator.subst(element.getNodeValue(), props);
return true;
}
// 省略getter()和setter()...
}
输出结果如下:
js
0 [main] ERROR Te - 错误xxx:
自定义ThrowableRenderer: java.lang.IllegalArgumentException
at com.matio.log4j.throwablerenderer.TestThrowableRenderer.main(TestThrowableRenderer.java:19)
2 [main] ERROR Te - 错误:
那么在xml中如何声明throwableRenderer呢,具体参考
DOMConfigurator#parseThrowableRenderer(Element);
xml
<!-- throwableRenderer:class类需要实现ThrowableRenderer接口 -->
<throwableRenderer class="com.matio.log4j.throwablerenderer.CusThrowableRenderer">
<!-- param属性会通过反射注入到当前class对象中 -->
<param name="p100" value="v100"/>
<!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->
<lover>zcf</lover>
</throwableRenderer>
类型 | 描述 | |
---|---|---|
throwableRenderer | 标签 | 自定义ThrowableRenderer |
class | 属性 | ThrowableRendere的实现类 |
param | 标签 | 通过反射将value设置给class类中的属性 |
param.name | 属性 | class类中的属性名称 |
param.value | 属性 | class类中的属性值 |
在throwableRenderer标签被解析完成后,如果发现该throwableRenderer类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
appender
每一个handler标签都会被实例化成一个Appender对象(它其实就等同于jul中的Handler),负责将日志输出到console上、保存到文件中,保存到数据库中、或者通过socket发送给远端等
DOMConfigurator#parseAppender(Element);
xml
<!-- appender: name和class属性必须-->
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>
</layout>
<!--过滤器设置输出的级别-->
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="levelMin" value="debug"/>
<param name="levelMax" value="warn"/>
<param name="acceptOnMatch" value="true"/>
</filter>
<!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->
<age>30</age>
<name value="matio"/>
</appender>
类型 | 描述 | |
---|---|---|
appender | 标签 | 对应jul中的handler对象 |
class | 属性 | Appender实现类 |
name | 属性 | 该appender的name |
param | 标签 | 通过反射将value设置给class类中的属性 |
param.name | 属性 | class类中的属性名称 |
param.value | 属性 | class类中的属性值 |
layout | 标签 | 定义该appender的日志输出样式,见parseLayout() |
filter | 标签 | 定义该appender的日志过滤器,见parseFilters() |
errorHandler | 标签 | 输出日志时如果出现异常就交给该handler处理,见parseErrorHandler() |
appender-ref | 标签 | 只有该appender实现了AppenderAttachable接口才有效,就是代表真正的appender,可以有多个 |
在每个appender标签被解析完成后,如果发现该appender类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法
最后再添加到对应的logger中