log4j是如何获取logger的

// 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关联,这样会导致他们的表现被改变。
至此,分析完毕。

简单总结一下:

  1. 找到合适的LoggerContextFactory,创建一个新的LoggerContext,此时configuration属性为初始默认配置
  2. LoggerContext启动start,并进行重新配置reconfig,查找合适的配置文件,解析生成新的Configuration
  3. 新的Configuration启动start,通过配置文件加载appender,loggerConfigs等等。同时处理旧的默认配置
  4. 上述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关联。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值