一. 前言
1. 相关文档
2. 说明
- log4j可以使用log4j.xml 和 log4j.properties 两种配置文件, 以下源码解析皆基于log4j.properties 来展开
- 本文只讲核心代码, 不是主线的代码不展示出来
3. 代码准备
- 项目添加log4j依赖
- 添加log4j.properties
- 添加main方法测试
详情: log4j配置详解及源码 的1. log4j使用
二. 常用类图
1. Logger的类图
说明:
- 在log4j.properties中, 可以配置根logger 和 自定义logger. 根logger的实现类为RootLogger, 自定义logger的实现类为Logger
- Logger的属性都在父类Category中, 主要为 logger的名字, 输出日志等级, 父节点logger, AppenderAttachableImpl(本logger绑定的appender列表, appdender为日志输出目的地)
- logger的绑定关系: 类似链表结构, 自定义logger的最终父节点为rootLogger
- logger的执行顺序: 先遍历执行该logger的所有appender(appdender的日志等级>=当前输出日志等级才执行), 若当前logger的additive=false, 结束. 再执行其父类logger, 直至根logger, 在这之间遇到logger的additive=false就会提前结束
2. LogManager类图
说明:
- 日志框架初始化在LogManager中的static代码块中
- LogManager有一个重要属性RepositorySelector, 意为选择器, 作用是产生一个LoggerRepository
- LoggerRepository的业务实现类为Hierarchy, Hierarchy里面有一个重要属性Logger root, 就是根logger
3. Appender类图
说明:
- 常用Appender有 ConsoleAppender, RollingFileAppender, DailyRollingFileAppender, 如图红色框选部分
- Appender译为输出目的地, 它存储了日志要输出的等级, 格式, 位置等信息, 作为一个日志输出器
4. Layout类图
说明: 日志的输出格式. 实现类有
TTCCLayout (输出 耗时 + 线程名 + 日志级别 + logger名 + ndc + 日志内容)
EnhancedPatternLayout (PatternLayout 的增强版本, 不展开讲, 感兴趣的自行看下源码)
HTMLLayout (html的格式, 输出 耗时 + 线程名 + 日志内容 +ogger名 + 日志内容等 )
PatternLayout (通过设置模式, 个性化指定要输出的内容)
SimpleLayout (简易模式, 只输出 日志级别 + 日志内容)
XMLLayout (xml的格式, 输出 logger名 + 时间 +日志级别 + 线程名 + ndc + 日志内容等 )
5. WriterAppender结构图
三. 获取Logger对象(初始化)
初始化流程
说明:
- 通过Logger.getLogger(Class clazz) 或 Logger.getLogger(String name)进入
- 加载LogManager进jvm, 执行静态代码块执行初始化, 创建出RepositorySelector实例及LoggerRepository实例(Hierarchy)
- 调用OptionConverter.selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy)方法执行配置. (传递Hierarchy)
- 在OptionConverter中根据存在的配置文件类型创建Configurator实例(这里假设配置文件为log4j.properties), 执行Configurator的doConfigure(URL url, LoggerRepository repository) (传递Hierarchy)
- 在PropertyConfigurator完成所有组件配置.
- configureRootCategory(Properties props, LoggerRepository hierarchy) 配置根logger及其appdender等属性
- parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) 配置非根的logger及其appdender等属性, 渲染器等
- LogManager完成静态代码块后, 通过Hierarchy.getLogger(String name)方法获取出logger对象
入口:
入口: Logger.getLogger(Class clazz) 或 Logger.getLogger(String name)
public class LogTest {
public static void main(String[] args) {
// 获取logger对象
//Logger logger = Logger.getLogger(LogTest.class.getName());
Logger logger = Logger.getLogger(LogTest.class);
System.out.println(logger);
}
}
进入Logger的Logger getLogger(Class clazz)方法内:
public class Logger extends Category {
static public Logger getLogger(Class clazz) {
// 通过LogManager获取logger对象, 此时jvm加载LogManager
return LogManager.getLogger(clazz.getName());
}
}
jvm加载LogManager
执行LogManager的static代码块
public class Logger extends Category {
static {
// 核心逻辑: 初始化repositorySelector, 并设置选择器的LoggerRepository为Hierarchy, 创建根logger
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
// 重写(过时)
String override = OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null);
if (override == null || "false".equalsIgnoreCase(override)) {
String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
URL url = null;
// 核心逻辑: 先查找log4j.xml, 没有再查找log4j.properties文件
if (configurationOptionStr == null) {
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if (url == null) {
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
}
if (url != null) {
try {
// 核心逻辑: 读取配置, 完成logger初始化
OptionConverter.selectAndConfigure(url, configuratorClassName,
LogManager1.getLoggerRepository());
} catch (NoClassDefFoundError e) {}
}
}
}
}
说明:
- 初始化repositorySelector, 并设置选择器的LoggerRepository为Hierarchy, 创建根logger
- 先查找log4j.xml, 没有再查找log4j.properties文件
- 读取配置, 完成logger初始化 OptionConverter.selectAndConfigure(…);
进入OptionConverter的selectAndConfigure(…)方法中
public class OptionConverter {
static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
Configurator configurator = null;
String filename = url.getFile();
// 若配置文件为log4j.xml, 则创建DOMConfigurator来解析
if (clazz == null && filename != null && filename.endsWith(".xml")) {
clazz = "org.apache.log4j.xml.DOMConfigurator";
}
if (clazz != null) {
LogLog.debug("Preferred configurator class: " + clazz);
configurator = (Configurator) instantiateByClassName(clazz, Configurator.class, null);
if (configurator == null) {
LogLog.error("Could not instantiate configurator [" + clazz + "].");
return;
}
} else {
// 否则, 则创建PropertyConfigurator来解析
configurator = new PropertyConfigurator();
}
// 进行配置
configurator.doConfigure(url, hierarchy);
}
}
说明:
1.配置文件为log4j.xml, 则使用 DOMConfigurator来解析
2.配置文件为log4j.properties, 则使用PropertyConfigurator来解析
进入PropertyConfigurator的doConfigure(URL configURL, LoggerRepository hierarchy)方法
public class PropertyConfigurator implements Configurator {
// 几个属性解释
// registry注册器, 用于初始化过程中缓存 已完成初始化的appender, 初始化完成后会重置
protected Hashtable registry = new Hashtable(11);
// logger仓库, 存储hierarchy对象
private LoggerRepository repository;
// logger工厂, 使用来创建自定义的logger对象
protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
// 读取配置文件内容, 写入props中
Properties props = new Properties();
InputStream istream = null;
URLConnection uConn = null;
try {
uConn = configURL.openConnection();
uConn.setUseCaches(false);
istream = uConn.getInputStream();
props.load(istream);
} catch (Exception e) {} finally {}
// 继续进行配置
doConfigure(props, hierarchy);
}
}
进入PropertyConfigurator的doConfigure(Properties properties, LoggerRepository hierarchy)方法
public void doConfigure(Properties properties, LoggerRepository hierarchy) {
// 缓存hierarchy, 方便后续使用
repository = hierarchy;
// 判断配置中log4j.debug是否开启, 开启则后续会打印log4j项目内的debug级别的日志
String value = properties.getProperty(LogLog.DEBUG_KEY);
if (value == null) {
value = properties.getProperty("log4j.configDebug");
}
if (value != null) {
LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
}
// 判断配置中log4j.reset是否开启, 是则重置hierarchy
String reset = properties.getProperty(RESET_KEY);
if (reset != null && OptionConverter.toBoolean(reset, false)) {
hierarchy.resetConfiguration();
}
// 获取配置中的log4j.threshold, 即全局的日志级别, 设置后低于该级别的日志都不能输出, 不设置则默认为ALL
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
if (thresholdStr != null) {
hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
}
// 核心代码: 配置根logger及其appender
configureRootCategory(properties, hierarchy);
// 配置LoggerFactory
configureLoggerFactory(properties);
// 核心代码: 配置自定义的logger及其appender, 渲染器等
parseCatsAndRenderers(properties, hierarchy);
LogLog.debug("Finished configuring.");
// 去除registry缓存(该缓存作用: 缓存初始化过程中的appender), 以便不影响垃圾回收
registry.clear();
}
说明:
1. 对一些全局的属性进行配置, 如是否开启debug(log4j.debug), 是否重置(log4j.reset), 日志级别(log4j.threshold)
2. 执行核心代码: 配置根logger及其appender
3. 执行核心代码: 配置自定义的logger及其appender, 以及一些异常处理器等
进入PropertyConfigurator的configureRootCategory(…)方法中
void configureRootCategory(Properties props, LoggerRepository hierarchy) {
// 获取log4j.rootLogger的配置信息.
String effectiveFrefix = ROOT_LOGGER_PREFIX;
// OptionConverter.findAndSubst(..)方法是获取指定key前缀的配置, 赋值到props中, 后续经常使用
String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
if (value == null) {
// 没配置log4j.rootLogger, 则获取log4j.rootCategory
value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
effectiveFrefix = ROOT_CATEGORY_PREFIX;
}
if (value == null){
LogLog.debug("Could not find root logger information. Is this OK?");
} else {
// 获取出hierarchy中的根logger
Logger root = hierarchy.getRootLogger();
synchronized (root) {
// 核心代码: 解析根logger 及绑定它的appender (后续自定义的loggger也是走这个逻辑)
parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
}
}
}
说明:
1. 获取log4j.rootLogger的配置信息, 进入parseCategory(…)方法中, 解析根logger 及绑定它的appender
2. 后续自定义的loggger也是走PropertyConfigurator的parseCategory(…)方法
进入PropertyConfigurator的parseCategory(…)方法
void parseCategory(Properties props, Logger logger, String optionKey,
String loggerName, String value) {
// 将logger的配置信息按照逗号分隔, 示例: log4j.rootLogger=info,console,rollingFile, 分隔后得到info 和 console及rollingFile
StringTokenizer st = new StringTokenizer(value, ",");
if (!(value.startsWith(",") || value.equals(""))) {
if (!st.hasMoreTokens()){
return;
}
// 获取当前logger的日志级别level. 第一个参数
String levelStr = st.nextToken();
// 非法的level值判断
if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
if (loggerName.equals(INTERNAL_ROOT_NAME)) {
LogLog.warn("The root logger cannot be set to null.");
} else {
logger.setLevel(null);
}
} else {
// 设置logger的level值, 不填则设置为DEBUG
// (不填level这时第一个appenderName就被无效掉了, 示例log4j.rootLogger=console,rollingFile, 其中console就被丢弃了)
logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
}
LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
}
// 移除所有的appender
logger.removeAllAppenders();
Appender appender;
String appenderName;
// 遍历log4j.rootLogger中的appenderName
while (st.hasMoreTokens()) {
appenderName = st.nextToken().trim();
if (appenderName == null || appenderName.equals(",")){
continue;
}
// 核心代码: 根据appenderName解析出appender
appender = parseAppender(props, appenderName);
if (appender != null) {
// 将appender加入到 logger的AppenderAttachableImpl的appenderList中
logger.addAppender(appender);
}
}
}
说明:
1. 获取logger的配置, 设置日志级别level. (注: level值省略会导致第一个appdend丢失)
2. 执行核心代码: 根据appenderName解析出appender
3. 将appender加入到 logger的AppenderAttachableImpl的appenderList中
下面简单看一下logger.addAppender(appender);方法
// Category类的方法
AppenderAttachableImpl aai;
synchronized public void addAppender(Appender newAppender) {
if(aai == null) {
aai = new AppenderAttachableImpl();
}
aai.addAppender(newAppender);
repository.fireAddAppenderEvent(this, newAppender);
}
// AppenderAttachableImpl 类的addAppender
public class AppenderAttachableImpl implements AppenderAttachable {
protected Vector appenderList;
public void addAppender(Appender newAppender) {
if(newAppender == null){
return;
}
if(appenderList == null) {
appenderList = new Vector(1);
}
if(!appenderList.contains(newAppender))
appenderList.addElement(newAppender);
}
}
进入PropertyConfigurator的parseAppender(…)方法中
Appender parseAppender(Properties props, String appenderName) {
// 根据appenderName获取registry缓存中的appender, 若存在则直接放回
Appender appender = registryGet(appenderName);
if ((appender != null)) {
LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
return appender;
}
// 注. 这里就写死了配置文件中layout只能是小写的.
String prefix = "log4j.appender." + appenderName;
String layoutPrefix = prefix + ".layout";
// 通过配置文件中类的全限定名实例化appender对象. OptionConverter.instantiateByKey(....)该方法会经常使用到
appender = (Appender) OptionConverter.instantiateByKey(props, prefix, Appender.class, null);
if (appender == null) {
LogLog.error("Could not instantiate appender named \"" + appenderName + "\".");
return null;
}
// 设置appender的name
appender.setName(appenderName);
if (appender instanceof OptionHandler) {
if (appender.requiresLayout()) {
// 通过配置文件中类的全限定名实例化layout对象
Layout layout = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, null);
if (layout != null) {
// 设置appender的layout
appender.setLayout(layout);
LogLog.debug("Parsing layout options for \"" + appenderName + "\".");
// 反射设置layout属性(例如PatternLayout的conversionPattern属性), 初始化解析器(代码位置: org.apache.log4j.helpers.PatternParser.parse). 详情见[补充: PatternLayout的setConversionPattern及模式初始化]
PropertySetter.setProperties(layout, props, layoutPrefix + ".");
LogLog.debug("End of parsing for \"" + appenderName + "\".");
}
}
// 初始化异常处理器
final String errorHandlerPrefix = prefix + ".errorhandler";
String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
if (errorHandlerClass != null) {
ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
errorHandlerPrefix,
ErrorHandler.class,
null);
if (eh != null) {
// 设置appender的errorHandler
appender.setErrorHandler(eh);
LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");
parseErrorHandler(eh, errorHandlerPrefix, props, repository);
final Properties edited = new Properties();
final String[] keys = new String[]{
errorHandlerPrefix + "." + ROOT_REF,
errorHandlerPrefix + "." + LOGGER_REF,
errorHandlerPrefix + "." + APPENDER_REF_TAG
};
for (Iterator iter = props.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
int i = 0;
for (; i < keys.length; i++) {
if (keys[i].equals(entry.getKey())) {
break;
}
}
if (i == keys.length) {
edited.put(entry.getKey(), entry.getValue());
}
}
PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\".");
}
}
// 反射设置appender的其他属性(如encoding、threshold、maxFileSize、datePattern). PropertySetter.setProperties(...)该方法会经常使用到
PropertySetter.setProperties(appender, props, prefix + ".");
LogLog.debug("Parsed \"" + appenderName + "\" options.");
}
// 解析appender的Filter. 使用较少, 这里不展开讲
parseAppenderFilters(props, appenderName, appender);
// 加入registry缓存中
registryPut(appender);
return appender;
}
说明:
1.根据appenderName获取registry缓存中的appender, 若存在则直接放回(appender可能被其他logger就完成初始化了, )
2.初始化日志输出器appender对象
3.初始化布局layout对象, 并设置到appender中(配置文件的key中layout必须为小写)
4.初始化异常处理器errorHandler, 并设置到appender中
5.初始化过滤器filter, 并设置到appender中
补充: PatternLayout的setConversionPattern及模式初始化
public class PatternLayout extends Layout {
// 输出日志内容的buffer
private StringBuffer sbuf = new StringBuffer(BUF_SIZE);
// 设置的模式, 默认值%m%n
private String pattern;
// 模式转换器, 链式结构
private PatternConverter head;
// 实例化时
public PatternLayout() {
this("%m%n");
}
public PatternLayout(String pattern) {
this.pattern = pattern;
// 核心代码: parse()方法
head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse();
}
// 如果有layout.conversionPattern配置, 初始化时, 反射调用该方法, 完成模式转换器链初始化
public void setConversionPattern(String conversionPattern) {
pattern = conversionPattern;
// 核心代码: parse()方法
head = createPatternParser(conversionPattern).parse();
}
// 创建PatternParser实例, 该类左右是用来初始化 模式转换器链PatternConverter head
protected PatternParser createPatternParser(String pattern) {
return new PatternParser(pattern);
}
}
public class PatternParser {
// 临时模式转换器头结点
PatternConverter head;
// 临时模式转换器尾结点
PatternConverter tail;
// 设置的模式值
protected String pattern;
// 模式解析. 使用状态机模式逐字符解析pattern属性. 这里代码非常多, 不展开讲, 直接说逻辑
public PatternConverter parse() {
// 状态机模式, 需要进一步了解的请直接看源码, 下面贴出状态机的状态流转图
char c;
i = 0;
while (i < patternLength) {
c = pattern.charAt(i++);
switch (state) {
case LITERAL_STATE: ... break;
case CONVERTER_STATE: ... break;
case MIN_STATE: ... break;
case DOT_STATE: ... break;
case MAX_STATE: ... break;
}
}
...
return head;
}
protected void finalizeConverter(char c) {
// 解析%后的字母, 如c, C, d, F, l, L等等
PatternConverter pc = null;
switch (c) {
case 'c':
// 创建出PatternConverter的实现类CategoryPatternConverter
// extractPrecisionOption()方法用户获取pattern中{}内的值
pc = new CategoryPatternConverter(formattingInfo, extractPrecisionOption());
currentLiteral.setLength(0);
break;
//........
case 'X':
String xOpt = extractOption();
// 创建出PatternConverter的实现类MDCPatternConverter. mdc可以用来做日志链路追踪
// 链路追踪拓展: https://blog.csdn.net/Erica_java/article/details/128616137
pc = new MDCPatternConverter(formattingInfo, xOpt);
currentLiteral.setLength(0);
break;
default: ....;
}
// 将PatternConverter按照链式结果连接起来. 这里使用head和tail节点维护节点, 最终返回head到PatternLayout中
addConverter(pc);
}
}
状态机状态流转图
进入PropertyConfigurator的configureLoggerFactory(Properties props)方法中
protected void configureLoggerFactory(Properties props) {
// 获取配置文件loggerFactory全限定名, 创建loggerFactory实例
String factoryClassName = OptionConverter.findAndSubst("log4j.loggerFactory", props);
if (factoryClassName != null) {
LogLog.debug("Setting category factory to [" + factoryClassName + "].");
loggerFactory = (LoggerFactory)
OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory);
// 反射设置loggerFactory的属性
PropertySetter.setProperties(loggerFactory, props, "log4j.factory" + ".");
}
}
说明:
1.获取配置文件loggerFactory全限定名, 创建loggerFactory实例
2.反射设置loggerFactory的属性
注: 不配置时, loggerFactory使用默认值, 为DefaultCategoryFactory实例对象. loggerFactory用于创建自定义的logger对象
进入PropertyConfigurator的parseCatsAndRenderers(…)方法中
protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
// 遍历Properties中所有的key
Enumeration enumeration = props.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
// 解析所有log4j.category.开头和log4j.logger.开头的key
String loggerName = null;
if (key.startsWith(CATEGORY_PREFIX)) {
loggerName = key.substring(CATEGORY_PREFIX.length());
} else if (key.startsWith(LOGGER_PREFIX)) {
loggerName = key.substring(LOGGER_PREFIX.length());
}
String value = OptionConverter.findAndSubst(key, props);
// 核心代码: 创建出自定义logger对象, 绑定其与其他logger的关系
Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
synchronized (logger) {
// 核心代码: 解析非根logger 及绑定它的appender(这里和根logger解析是同一个方法)
parseCategory(props, logger, key, loggerName, value);
// 解析additivity属性, 设置到logger中
parseAdditivityForLogger(props, logger, loggerName);
}
} else if (key.startsWith(RENDERER_PREFIX)) {
// 解析所有log4j.renderer.开头的key, 初始化和绑定渲染器. 较少使用, 不展开讲
String renderedClass = key.substring(RENDERER_PREFIX.length());
String renderingClass = OptionConverter.findAndSubst(key, props);
if (hierarchy instanceof RendererSupport) {
RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
renderingClass);
}
} else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
// 解析所有log4j.throwableRenderer开头的异常渲染器, 初始化和绑定异常渲染器. 较少使用, 不展开讲
if (hierarchy instanceof ThrowableRendererSupport) {
ThrowableRenderer tr = (ThrowableRenderer)
OptionConverter.instantiateByKey(props, THROWABLE_RENDERER_PREFIX, ThrowableRenderer.class, null);
if (tr == null) {
LogLog.error("Could not instantiate throwableRenderer.");
} else {
PropertySetter setter = new PropertySetter(tr);
setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
}
}
}
}
}
进入Hierarchy的getLogger(String name, LoggerFactory factory)方法中
public Logger getLogger(String name, LoggerFactory factory) {
CategoryKey key = new CategoryKey(name);
Logger logger;
synchronized (ht) {
Object o = ht.get(key);
if (o == null) {
// 为空. 创建logger实例. 默认实现 Logger logger = new Logger(name)
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
// 写入Hierarchy的ht(Hashtable)缓存中
ht.put(key, logger);
// 绑定当前logger和父logger
updateParents(logger);
return logger;
} else if (o instanceof Logger) {
// Logger类型. 直接返回
return (Logger) o;
} else if (o instanceof ProvisionNode) {
// 临时节点类型. 先创建出该节点
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
// 写入Hierarchy的ht(Hashtable)缓存中
ht.put(key, logger);
// 找到创建logger之前临时存储的子节点, 重新绑定(子logger->旧parent => 子logger->当前logger->旧parent)
updateChildren((ProvisionNode) o, logger);
// 绑定当前logger和父logger
updateParents(logger);
return logger;
} else {
return null;
}
}
}
说明:
1.Hierarchy有个属性Hashtable ht, 用于缓存初始化时已创建的appender和临时节点ProvisionNode
临时节点ProvisionNode介绍: ht存储临时节点示例 com.clj -> [logger(com.clj.abc), logger(com.clj.def)]. 其中com.clj为待logger的name, 列表中元素为已实例化的logger(具体使用见Hierarchy.updateChildren(…)和Hierarchy.updateParents(.)方法)
2.若ht中未找到值, 则创建logger, 加入ht缓存, 绑定当前logger和父logger
3.若ht中找到值且为Logger类型, 直接返回
4.若ht中找到值且为ProvisionNode类型, 则创建logger, 加入ht缓存, 绑定当前logger和子logger, 绑定当前logger和父logger
进入Hierarchy的updateParents(Logger cat)方法中
final private void updateParents(Logger cat) {
// 示例logger的name = com.clj.abc, 假设初始化到当前只解析了 根logger和logger(com.clj.abc)
String name = cat.name;
int length = name.length();
// 标识是否找到父类了
boolean parentFound = false;
// 这里遍历出com.clj 和 com
for (int i = name.lastIndexOf('.', length - 1); i >= 0;
i = name.lastIndexOf('.', i - 1)) {
// 第一次遍历得到com.clj. 第二次遍历得到com
String substr = name.substring(0, i);
CategoryKey key = new CategoryKey(substr);
Object o = ht.get(key);
// Create a provision node for a future parent.
if (o == null) {
// ht中没缓存到, 则创建出临时节点, 临时节点存储的是当前的logger(com.clj.abc)
ProvisionNode pn = new ProvisionNode(cat);
// ht缓存临时节点. 第一次遍历(缓存了com.clj -> [logger(com.clj.abc)]), 第二次遍历(缓存了com -> [logger(com.clj.abc)])
ht.put(key, pn);
} else if (o instanceof Category) {
// 若ht中得到是Category类型, 直接绑定当前logger的父节点, 修改parentFound标识
parentFound = true;
cat.parent = (Category) o;
break;
} else if (o instanceof ProvisionNode) {
// 若ht中得到是ProvisionNode类型, 则将当前cat加入列表.
// 这里假设之前o的列表为 [logger(com.clj.def)], 添加后列表为 [logger(com.clj.def), logger(com.clj.abc)],
// 对应ht中的缓存键值对为com.clj -> [logger(com.clj.def), logger(com.clj.abc)]
((ProvisionNode) o).addElement(cat);
} else {
Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht.");
e.printStackTrace();
}
}
// 暂没找到父类, 那直接设置根logger为父类
if (!parentFound) {
cat.parent = root;
}
}
进入Hierarchy的updateChildren(ProvisionNode pn, Logger logger)方法中
final private void updateChildren(ProvisionNode pn, Logger logger) {
// 遍历临时节点中所有的子节点
final int last = pn.size();
for (int i = 0; i < last; i++) {
Logger l = (Logger) pn.elementAt(i);
// 若子节点的 原父logger 不是当前logger, 则设置子节点的 父节点为 当前logger, 设置当前logger的 父节点为 原父logger
// 示例: 当前logger(com.clj), 子节点logger(com.clj.abc), 子节点的父logger为 root, 即 logger(com.clj.abc) -> logger(root)
// 处理后为: logger(com.clj.abc) -> logger(com.clj) -> logger(root)
if (!l.parent.name.startsWith(logger.name)) {
logger.parent = l.parent;
l.parent = logger;
}
}
}
进入PropertyConfigurator的parseAdditivityForLogger(…)方法中
void parseAdditivityForLogger(Properties props, Logger cat, String loggerName) {
// 获取additivity属性(key为log4j.additivity.[loggerName])
String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, props);
LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]");
// 设置到logger的additive字段中(作用: 表示打印本logger日志后, 是否将日志添加到父级logger日志中处理, 默认值为true)
if ((value != null) && (!value.equals(""))) {
boolean additivity = OptionConverter.toBoolean(value, true);
LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity);
cat.setAdditivity(additivity);
}
}
说明:
1.获取additivity属性(key为log4j.additivity.[loggerName])
2.设置到logger的additive字段中
3.logger的additive字段作用: 打印本logger日志后, 是否将日志添加到父级logger日志中处理, 默认值为true
四. 执行日志打印
流程
说明:
- 从main方法出发, 这里示例是调用info()方法. (故后续都是判断info级别)
- 判断是否满足全局的level级别>=info, 满足则 再判断本logger的level级别是否>=info(本logger没有值则找其父类直至有值)
- 全局level或本体系的level有其一不满足>=info, 提前结束流程
- 执行本logger再递归父类logger, 执行其aai.appendLoopOnAppenders()方法 (注: 当前logger执行完会判断additive属性, 为false则不再递归父类, 该点在流程图第20步体现)
- 遍历当前logger的日志输出器appender, 执行其doAppend()方法
- 进入appender的公共父类AppenderSkeleton的doAppend()方法, 先判断当前appender的日志级别level是否>=info
- 当前appender日志级别level<info, 结束该appender的执行, 遍历下一个appender
- 递归执行本appender的过滤器执行链
- Filter中进行过滤判定
- 不满足过滤条件则结束该appender的执行, 遍历下一个appender
- 执行当前appender的append()方法. (这里假设实现类为RollingFileAppender)
- 进入appender的公共父类WriterAppender的append()方法, 执行子类RollingFileAppender的subAppend()方法
- 进入RollingFileAppender的subAppend()方法, 立即调用父类WriterAppender的subAppend()方法, 执行后再执行本类的滚动文件逻辑(该点在流程图第19步体现)
- 在WriterAppender的subAppend()方法中调用layout执行内容格式化
- layout完成输出内容格式化, 返回内容信息
- 调用QuietWriter将内容写入缓存
- 在WriterAppender中, 判断属性是否为立即输出, 是则调用QuietWriter写出内容
- QuietWriter将内容写出到文件中(若appender为ConsoleAppender则是输出到控制台)
- 回到RollingFileAppender的subAppend()方法中, 判断是否满足滚动文件逻辑, 是则执行rollOver()
- 遍历完当前logger后, 判断当前logger的additive属性, 为false则不再递归父类, 提前结束流程
入口
public class LogTest {
public static void main(String[] args) {
Logger logger = Logger.getLogger(LogTest.class);
// 入口. 这里示例为输出info级别的日志
logger.info("这是info级别的日志");
}
}
进入Category的info(Object message)方法
public void info(Object message) {
// 判断全局日志级别是否 >= info, 不满足则退出.
// (repository为初始化时 LogManager的repositorySelector属性的LoggerRepository属性 -> Hierarchy)
if (repository.isDisabled(Level.INFO_INT)) {
return;
}
// 获取本logger继承体系中有效的level, 判断是否满足日志级别是否 >= info, 不满足则退出.
if (Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) {
// 核心代码: 输出日志
forcedLog(FQCN, Level.INFO, message, null);
}
}
说明:
- 判断全局日志级别是否 >= info, 不满足则退出 (全局日志级别配置key为log4j.threshold, 存储在Hierarchy中)
- 获取本logger继承体系中有效的level, 判断是否满足日志级别是否 >= info, 不满足则退出.
进入Category的getEffectiveLevel()方法
public Level getEffectiveLevel() {
// 递归当前logger及其父类, 直到找到有效的level, 返回
for (Category c = this; c != null; c = c.parent) {
if (c.level != null) {
return c.level;
}
}
return null;
}
进入Category的forcedLog(…)方法
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
// 将要输出的基础日志内容,level,logger等封装到LoggingEvent中
callAppenders(new LoggingEvent(fqcn, this, level, message, t));
}
进入Category的callAppenders(LoggingEvent event)方法
public void callAppenders(LoggingEvent event) {
int writes = 0;
// 递归本logger及其继承体系的logger
for (Category c = this; c != null; c = c.parent) {
synchronized (c) {
// 核心代码: 获取logger属性AppenderAttachableImpl中的appenderList, 遍历执行appender
if (c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
// 若当前logger的additive为false, 则不再执行其父类logger, 结束日志输出
if (!c.additive) {
break;
}
}
}
// 本logger及其继承体系中, 一个appender都没有找到, 输出warn的警告日志(项目启动后只会执行一次)
if (writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
说明:
- 递归本logger及其继承体系的logger, 直至当前logger的additive属性为false
- 获取logger属性AppenderAttachableImpl中的appenderList, 遍历执行appender
- 若递归后一个appender都没有, 则输出warn的警告日志(项目启动后只能执行一次)
进入AppenderAttachableImpl的appendLoopOnAppenders(LoggingEvent event)方法中
public int appendLoopOnAppenders(LoggingEvent event) {
// 记录当前appender个数返回
int size = 0;
Appender appender;
if (appenderList != null) {
size = appenderList.size();
// 核心代码: 遍历执行每个appender的doAppend()方法
for (int i = 0; i < size; i++) {
appender = (Appender) appenderList.elementAt(i);
appender.doAppend(event);
}
}
return size;
}
进入AppenderSkeleton的doAppend(LoggingEvent event)方法中
注: 在这之后的示例使用Appender的实现类RollingFileAppender
注: 在这之后的示例使用Appender的实现类RollingFileAppender
注: 在这之后的示例使用Appender的实现类RollingFileAppender
public synchronized void doAppend(LoggingEvent event) {
if (closed) {
LogLog.error("Attempted to append to closed appender named [" + name + "].");
return;
}
// 判断当前appender的日志级别是否 >= 要输出的日志级别
if (!isAsSevereAsThreshold(event.getLevel())) {
return;
}
// 遍历过滤器链, 判断是否可以往下执行
Filter f = this.headFilter;
FILTER_LOOP:
while (f != null) {
switch (f.decide(event)) {
case Filter.DENY:
return;
case Filter.ACCEPT:
break FILTER_LOOP;
case Filter.NEUTRAL:
f = f.getNext();
}
}
// 执行子类append(LoggingEvent event)方法 (注: 在这之后的示例使用Appender的实现类RollingFileAppender)
this.append(event);
}
说明:
- 判断当前appender的日志级别是否 >= 要输出的日志级别, 不大于则退出
- 遍历过滤器链, 判断是否被拦截, 拦截则结束
- 执行子类append(LoggingEvent event)方法 (注: 在这之后的示例使用Appender的实现类RollingFileAppender)
进入WriterAppender的append(LoggingEvent event)方法中
注: 示例使用Appender的实现类RollingFileAppender
public void append(LoggingEvent event) {
// 判断QuietWriter和Layout是否为空, 为空则结束
if (!checkEntryConditions()) {
return;
}
// 核心代码: 进入RollingFileAppender的subAppend(LoggingEvent event)方法中
subAppend(event);
}
进入RollingFileAppender的subAppend(LoggingEvent event)方法中
protected void subAppend(LoggingEvent event) {
// 核心代码: 执行父类WriterAppender的subAppend(LoggingEvent event)方法
super.subAppend(event);
if(fileName != null && qw != null) {
// 超过文件最大size, 滚动文件
long size = ((CountingQuietWriter) qw).getCount();
if (size >= maxFileSize && size >= nextRollover) {
// 次核心代码: 滚动文件, 这里不展开讲.
// 拓展: 若使用的appender为DailyRollingFileAppender, 需自动删除过期文件, 请查看[\[log4j配置详解及源码\]](https://blog.csdn.net/Java_dragon95/article/details/129953654)-[自定义DailyRollingFileAppender实现类, 自动删除过期日志]
rollOver();
}
}
}
进入WriterAppender的subAppend(LoggingEvent event)方法中
protected QuietWriter qw;
protected void subAppend(LoggingEvent event) {
// 核心代码: 格式化输出内容, 将格式化后的内容写入QuietWriter (write时出现异常会使用异常处理器errorHandler)
// 假设Layout的实现类为PatternLayout
this.qw.write(this.layout.format(event));
// 处理的异常对象不包含在layout处理范围内, 则使用渲染器ThrowableRenderer渲染要输出的异常信息
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);
}
}
}
// immediateFlush=true, 则立即写出到文件 (bufferedIO=true时, immediateFlush会被置为false)
if (shouldFlush(event)) {
this.qw.flush();
}
}
说明:
- 使用Layout格式化输出内容
- 使用QuietWriter写入格式化后的日志内容, write时出现异常会使用异常处理器errorHandler
- 处理的异常对象不包含在layout处理范围内, 则使用渲染器ThrowableRenderer渲染要输出的异常信息
- immediateFlush=true, 则立即写出到文件 (bufferedIO=true时, immediateFlush会被置为false, 这是要达到buffer大小才会写出)
进入PatternLayout的format(LoggingEvent event)方法
注: 上面已假设Layout的实现类为PatternLayout
public String format(LoggingEvent event) {
// 重置buffer容量 (因为有些大日志输出时会将buffer的容量变得很大)
if (sbuf.capacity() > MAX_CAPACITY) {
sbuf = new StringBuffer(BUF_SIZE);
} else {
sbuf.setLength(0);
}
// 遍历模式转化器链, 获取一个个模式转换器, 格式化拼接好日志内容
PatternConverter c = head;
while (c != null) {
// 按照具体的PatternConverter实现类, 格式化当前转化器负责的内容, 拼接到sbuf中.
c.format(sbuf, event);
c = c.next;
}
// 返回最终的日志输出内容
return sbuf.toString();
}
进入父类PatternConverter的format(StringBuffer sbuf, LoggingEvent e)方法
注: 这里不列举所有子类重写的format方法
public abstract class PatternConverter {
// 下一个模式转换器
public PatternConverter next;
// 小数点左侧的值
int min = -1;
// 小数点右侧的值
int max = 0x7FFFFFFF;
// 左对齐标志
boolean leftAlign = false;
/**
* 转换出部分内容
*/
abstract protected String convert(LoggingEvent event);
public void format(StringBuffer sbuf, LoggingEvent e) {
// 执行子类的convert方法
String s = convert(e);
if (s == null) {
// 直接拼接min个空格
if (0 < min) {
spacePad(sbuf, min);
}
return;
}
int len = s.length();
// 实际内容大于 阈值, 保留指定阈值长度右侧内容
if (len > max) {
sbuf.append(s.substring(len - max));
} else if (len < min) {
// 左对齐, 先拼接内容再拼接多余空格
if (leftAlign) {
sbuf.append(s);
spacePad(sbuf, min - len);
}
// 右对齐, 先拼接多余空格再拼接内容
else {
spacePad(sbuf, min - len);
sbuf.append(s);
}
} else {
// 没有额外设置, 则直接拼接内容
sbuf.append(s);
}
}
static String[] SPACES = {" ", " ", " ", " ", " ", " "};
/**
* 有几个空格就拼接几个空格
*/
public void spacePad(StringBuffer sbuf, int length) {
while (length >= 32) {
sbuf.append(SPACES[5]);
length -= 32;
}
for (int i = 4; i >= 0; i--) {
if ((length & (1 << i)) != 0) {
sbuf.append(SPACES[i]);
}
}
}
}
进入QuietWriter的write(String string)方法中
public void write(String string) {
if (string != null) {
try {
// 写入日志
out.write(string);
} catch(Exception e) {
// 使用异常处理器处理异常. 该异常处理器就是appender初始化时appender内的异常处理器
errorHandler.error("Failed to write ["+string+"].", e, ErrorCode.WRITE_FAILURE);
}
}
}