// LogManager源码
public static Logger getLogger(final Class<?> clazz) {
final Class<?> cls = callerClass(clazz);
// 1. 先获取LoggerContext,然后通过LoggerContext获取logger
return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls));
}
// 成员变量
private static volatile LoggerContextFactory factory;
public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {
try {
// 2. 该factory是内部属性LoggerContextFactory,在静态代码块中初始化(扫描类路径以查找所有日志实现,但是只有一个会被使用)
return factory.getContext(FQCN, loader, null, currentContext);
} catch (final IllegalStateException ex) {
LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext);
}
}
// Log4jContextFactory (implements LoggerContextFactory)
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
final boolean currentContext) {
// 这里就跟到了下面那段断码
final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
if (externalContext != null && ctx.getExternalContext() == null) {
ctx.setExternalContext(externalContext);
}
if (ctx.getState() == LifeCycle.State.INITIALIZED) {
// 如果LoggerContext刚初始化完成,执行start方法,后续会分析
ctx.start();
}
return ctx;
}
// debug跟过来,到了ClassLoaderContextSelector,这里入参configLocation为null
protected LoggerContext createContext(final String name, final URI configLocation) {
// 3. 创建默认的LoggerContext,
return new LoggerContext(name, null, configLocation);
}
// LoggerContext,构造方法很简单,不看了,主要看实例化时初始化的一些变量以及一些静态变量
// 这个就是默认配置啦,每个LoggerContext被创建之初都会绑定默认配置,在reconfig方法中进行修改
private volatile Configuration configuration = new DefaultConfiguration();
// 题外话:在LoggerContext执行stop方法时,用于替代当前的配置文件,然后执行Log4J的收尾工作。好处是避免在收尾工作时,还有程序打印日志而报错
private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
// 空配置,其实就是一个空壳,参考文章https://blog.csdn.net/sweetyi/article/details/105147571
public NullConfiguration() {
super(null, ConfigurationSource.NULL_SOURCE);
setName(NULL_NAME);
final LoggerConfig root = getRootLogger();
// 停止日志输出
root.setLevel(Level.OFF);
}
以上就是LoggerContext的获取步骤,获取完之后执行start方法
public void start() {
// ***************略去了许多代码*****************
if (this.isInitialized() || this.isStopped()) {
reconfigure();
}
LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
}
// 这里的后续步骤其实就是去找配置,然后重新设置configuration
private void reconfigure(final URI configURI) {
final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
if (instance == null) {
LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
} else {
// 这个方法也还挺多操作的,下面简单看一下
setConfiguration(instance);
}
}
分析一下获取Configuration部分的代码
// ConfigurationFactory源码,部分代码略,这个部分之前文章简单分析过,这里不再赘述
private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) {
final boolean named = Strings.isNotEmpty(name);
final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
for (final ConfigurationFactory factory : getFactories()) {
String configName;
final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
final String [] types = factory.getSupportedTypes();
for (final String suffix : types) {
configName = named ? prefix + name + suffix : prefix + suffix;
final ConfigurationSource source = ConfigurationSource.fromResource(configName, loader);
if (source != null) {
// 找到了配置,然后主要看下这里做了什么(这里factory的实现以XmlConfigurationFactory为例)
return factory.getConfiguration(loggerContext, source);
}
}
}
return null;
}
public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
return new XmlConfiguration(loggerContext, source);
}
public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource configSource) {
super(loggerContext, configSource);
final File configFile = configSource.getFile();
// 略去中间一部分解析xml文件的代码
rootElement = document.getDocumentElement();
final Map<String, String> attrs = processAttributes(rootNode, rootElement);
final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
.withStatus(getDefaultStatus());
int monitorIntervalSeconds = 0;
for (final Map.Entry<String, String> entry : attrs.entrySet()) {
final String key = entry.getKey();
final String value = getStrSubstitutor().replace(entry.getValue());
if ("status".equalsIgnoreCase(key)) { // 指定log4j的内部StatusLogger的日志级别
statusConfig.withStatus(value);
}
// 略去中间一堆我不太了解的字段
else if ("monitorInterval".equalsIgnoreCase(key)) { // 配置监听的时间设置
monitorIntervalSeconds = Integer.parseInt(value);
} else if ("advertiser".equalsIgnoreCase(key)) {
createAdvertiser(value, configSource, buffer, "text/xml");
}
}
initializeWatchers(this, configSource, monitorIntervalSeconds); // 初始化配置监听
statusConfig.initialize();
} catch (final SAXException | IOException | ParserConfigurationException e) {
LOGGER.error("Error parsing " + configSource.getLocation(), e);
}
// 略,懒得看了,不是重点
}
获取到之后,重新设置configuration,之前该值是默认配置
public Configuration setConfiguration(final Configuration config) {
configLock.lock();
try {
final Configuration prev = this.configuration;
// ********略***************
// 这里就是根据配置文件去生成记录appenders,loggerConfigs(即Loggers)
config.start();
this.configuration = config;
// 根据新的配置文件去更新logger,这里因为之前是默认配置,不存在logger,所以实际并不会更新
updateLoggers();
if (prev != null) {
prev.removeListener(this);
prev.stop();
}
return prev;
} finally {
configLock.unlock();
}
}
// 截取一部分代码
else if (child.getName().equalsIgnoreCase("Appenders")) {
appenders = child.getObject();
} else if (child.getName().equalsIgnoreCase("Loggers")) {
final Loggers l = child.getObject();
loggerConfigs = l.getMap();
setLoggers = true;
if (l.getRoot() != null) {
root = l.getRoot();
setRoot = true;
}
附上官方文档关于Configuration的描述:
每一个LoggerContext都有一个活跃的Configuration。这个Configuration包含所有的Appender、上下文范围的Filter、LoggerConfig以及StrSubstitutor的引用。当重新配置时,将会有两个Configuration对象存在。一旦所有的Logger都已经被导向到新的Configuration上时,旧的Configuration就会被停止且丢弃。
以上就是LoggerContext准备就绪,开始获取Logger了
// LoggerContext
public Logger getLogger(final String name, final MessageFactory messageFactory) {
// 先检查是否已存在
Logger logger = loggerRegistry.getLogger(name, messageFactory);
if (logger != null) {
AbstractLogger.checkMessageFactory(logger, messageFactory);
return logger;
}
// 不存在则创建,并记录保持下来
logger = newInstance(this, name, messageFactory);
loggerRegistry.putIfAbsent(name, messageFactory, logger);
return loggerRegistry.getLogger(name, messageFactory);
}
protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
return new Logger(ctx, name, messageFactory);
}
下面就是创建的过程
// Logger
protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
super(name, messageFactory);
this.context = context;
privateConfig = new PrivateConfig(context.getConfiguration(), this);
}
/**
* 这里再看一下LoggerContext的getConfiguration方法,注释也说明了很多东西
* Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
*
* @return The current Configuration, never {@code null}, but may be
* {@link org.apache.logging.log4j.core.config.NullConfiguration}.
*/
public Configuration getConfiguration() {
return configuration;
}
public PrivateConfig(final Configuration config, final Logger logger) {
this.config = config;
// 看这里,可以看出整个配置中,最重要的属性就是loggerConfig
this.loggerConfig = config.getLoggerConfig(getName());
this.loggerConfigLevel = this.loggerConfig.getLevel();
this.intLevel = this.loggerConfigLevel.intLevel();
this.logger = logger;
}
public LoggerConfig getLoggerConfig(final String loggerName) {
// 这就是核心了,终于到这了
// 先直接找有没有这个对应名称的LoggerConfig
LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
if (loggerConfig != null) {
return loggerConfig;
}
// 如果没有,就按照报名结构对入参loggerName进行拆分,找是否有符合的父类LoggerConfig
String substr = loggerName;
while ((substr = NameUtil.getSubName(substr)) != null) {
loggerConfig = loggerConfigs.get(substr);
if (loggerConfig != null) {
return loggerConfig;
}
}
// 都找不到,那么就返回root loogerConfig
return root;
}
Logger创建完毕,附上官方文档关于Logger的描述:
Logger本身并不做转向的动作。它只是有一个名字并且和LoggerConfig关联。它继承了AbstractLogger并且实现了所需的方法。当配置被修改时,Logger可能变为与一个不同的LoggerConfig关联,这样会导致他们的表现被改变。
至此,分析完毕。
简单总结一下:
- 找到合适的LoggerContextFactory,创建一个新的LoggerContext,此时configuration属性为初始默认配置
- LoggerContext启动start,并进行重新配置reconfig,查找合适的配置文件,解析生成新的Configuration
- 新的Configuration启动start,通过配置文件加载appender,loggerConfigs等等。同时处理旧的默认配置
- 上述3点完成后,LoggerContext就算是处理完毕了,然后通过它获取Logger
常规配置文件加载logger模式下,1个LoggerContext包含一个1个实时有效的Configuration,1个Configuration(对应配置文件)记录了多个LoggerConfig信息,通过LoggerContext创建的Logger,1个Logger记录了一个Configuration,但是根据Logger的name从Configuration中仅获取一个LoggerConfig。
附上官方文档中的一段话:
应用程序要使用Log4j 2的API,需要从LogManager中获取一个有明确名称的Logger。LogManager将会定位到一个合适的LoggerContext并且从中获取Logger。如果Logger必须被创建,那么它会和包含这些信息的LogConfig相关联:a)与Logger相同的名称;b)父包的名称;c)根LoggerConfig。LoggerConfig对象根据配置中的Logger声明而创建。LoggerConfig与实际处理LogEvent事件的Appender关联。