介绍
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
使用
依赖包:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
import org.apache.log4j.Logger;
public class Application {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Application.class);
logger.trace("This is a trace message");
logger.debug("This is a debug message");
logger.info("This a info message");
logger.error("This a error message");
}
}
配置文件
log4j.properties
log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] %d{yyyy-MM-dd HH:mm:ss} %l: %m%n
源码跟踪
初始化
跟踪getLogger方法 发现实际调用是LogManager
public static Logger getLogger(final String name) {
// Delegate the actual manufacturing of the logger to the logger repository.
return getLoggerRepository().getLogger(name);
}
查看LogManager发现 它有一个静态块在类加载时用来构建LoggerRepository
static {
// By default we use a DefaultRepositorySelector which always returns 'h'.
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
/** Search for the properties file log4j.properties in the CLASSPATH. */
String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
null);
// if there is no default init override, then get the resource
// specified by the user or the default config file.
if(override == null || "false".equalsIgnoreCase(override)) {
String configurationOptionStr = OptionConverter.getSystemProperty(
DEFAULT_CONFIGURATION_KEY,
null);
String configuratorClassName = OptionConverter.getSystemProperty(
CONFIGURATOR_CLASS_KEY,
null);
URL url = null;
// if the user has not specified the log4j.configuration
// property, we search first for the file "log4j.xml" and then
// "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 we have a non-null url, then delegate the rest of the
// configuration to the OptionConverter.selectAndConfigure
// method.
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.");
}
}
说明:创建RootLogger 日志级别为DEBUG, 把RootLogger作为参数用来初始化一个LoggerRepository对象h, 再创建一个DefaultRepositorySelector.
后边操作是查找配置文件并加载配置文件
OptionConverter.selectAndConfigure(url, configuratorClassName,
LogManager.getLoggerRepository());
点击查看执行代码如下
public
void doConfigure(Properties properties, LoggerRepository hierarchy) {
repository = hierarchy;
String value = properties.getProperty(LogLog.DEBUG_KEY);
if(value == null) {
value = properties.getProperty("log4j.configDebug");
if(value != null)
LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
}
if(value != null) {
LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
}
//
// if log4j.reset=true then
// reset hierarchy
String reset = properties.getProperty(RESET_KEY);
if (reset != null && OptionConverter.toBoolean(reset, false)) {
hierarchy.resetConfiguration();
}
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
properties);
if(thresholdStr != null) {
hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
(Level) Level.ALL));
LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
}
configureRootCategory(properties, hierarchy);
configureLoggerFactory(properties);
parseCatsAndRenderers(properties, hierarchy);
LogLog.debug("Finished configuring.");
// We don't want to hold references to appenders preventing their
// garbage collection.
registry.clear();
}
说明:
1、 配置hierarchy中的rootLogger(读取配置文件内容对默认的rootLogger,进行修改:日志级别、添加Appender).
2、配置LoggerFactory(读取配置文件是否配置有新的Factory类,如果有,则通过反射机制来创建该对象,并为该对象设置相关属性的值,数据来源:配置文件).
3、解析配置文件组建Logger对象并存放到LoggerRepostory的HashTable中.
4、清空构建logger对象的中间过程中存储Appender对象的HashTable集合
至此LoggerRepostory初始化完成,且代表log4j整个配置完成.
梳理了一个简单的关系图 方便了解
关系结构:(
RepositorySelector has-a LoggerRepository
LoggerRepository has-a Logger root 根Logger
LoggerRepository has-a Hashtable ht 存放其他Logger的集合
LoggerRepository has-a LoggerFactory defaultFactory 默认的loggerFactory
Logger has-a LoggerRepository
Logger has-a AppenderAttachableImpl aai AppenderAttachableImpl 是封装了appender集合的对象
)
核心对象:
Logger 输出日志对象 持有一个Appender的集合,当执行输出方法时,把消息封装为LoggingEvent 日志事件,并循环调用appender的subAppend方法。
LoggerFactory 用来构建Logger对象,
LoggerRepository 存放日志对象,提供相关获取日志操作的方法
LoggerManager 负责完成日志配置.
日志输出
logger.debug("This is a debug message");
方法定义在:Category类
public void debug(Object message) {
if(repository.isDisabled(Level.DEBUG_INT))
return;
if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG, message, null);
}
}
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
callAppenders(new LoggingEvent(fqcn, this, level, message, t));
}
public void callAppenders(LoggingEvent event) {
int writes = 0;
for(Category c = this; c != null; c=c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized(c) {
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
说明: 创建日志事件,并通知给Appender。 c.aai是appender的集合。
AppenderAttachableImpl类
public int appendLoopOnAppenders(LoggingEvent event) {
int size = 0;
Appender appender;
if(appenderList != null) {
size = appenderList.size();
for(int i = 0; i < size; i++) {
appender = (Appender) appenderList.elementAt(i);
appender.doAppend(event);
}
}
return size;
}
说明:遍历appenderList并调用appender的doAppend方法.
AppenderSkeleton类
abstract protected void append(LoggingEvent event);
说明:此处只是声明一个抽象方法,具体有子类实现.
WriterAppender类
public WriterAppender(Layout layout, OutputStream os) {
this(layout, new OutputStreamWriter(os));
}
public WriterAppender(Layout layout, Writer writer) {
this.layout = layout;
this.setWriter(writer);
}
public synchronized void setWriter(Writer writer) {
reset();
this.qw = new QuietWriter(writer, errorHandler);
//this.tp = new TracerPrintWriter(qw);
writeHeader();
}
public void append(LoggingEvent event) {
if(!checkEntryConditions()) {
return;
}
subAppend(event);
}
protected void subAppend(LoggingEvent event) {
this.qw.write(this.layout.format(event));
if(layout.ignoresThrowable()) {
String[] s = event.getThrowableStrRep();
if (s != null) {
int len = s.length;
for(int i = 0; i < len; i++) {
this.qw.write(s[i]);
this.qw.write(Layout.LINE_SEP);
}
}
}
if(shouldFlush(event)) {
this.qw.flush();
}
}
说明:该类实现了append(LoggingEvent event) 方法 具体调用的是subAppend(LoggingEvent event)方法。通过调用qw.write方法输出日志。
查看子类中具体的设置qw的方法,此处以ConsoleAppender为例:
ConsoleAppender类
public ConsoleAppender(Layout layout, String target) {
setLayout(layout);
setTarget(target);
activateOptions();
}
public void activateOptions() {
if (follow) {
if (target.equals(SYSTEM_ERR)) {
setWriter(createWriter(new SystemErrStream()));
} else {
setWriter(createWriter(new SystemOutStream()));
}
} else {
if (target.equals(SYSTEM_ERR)) {
setWriter(createWriter(System.err));
} else {
setWriter(createWriter(System.out));
}
}
super.activateOptions();
}
public synchronized void setWriter(Writer writer) {
reset();
this.qw = new QuietWriter(writer, errorHandler);
//this.tp = new TracerPrintWriter(qw);
writeHeader();
}
说明:最终logger.debug(“This is a debug message”)方法 实际是通过调用System.out进行Message的输出.
总结
相对于sel4j、logback, log4j相对结构简单,入门容易。 本文中有些细节描述不到位,比如:解析配置文件转换为Logger对象、Logger间的继承关系、LoggerRepostory中的事件监听等操作 由于太琐碎就没有在文中描述