log4j2(三) 如何通过类名获取到logger呢?logger与loggerConfig是什么关系?-源码解析

x33g5p2x  于2021-12-25 转载在 其他  
字(5.5k)|赞(0)|评价(0)|浏览(449)

情景

之所以想写这篇文章是因为经常看到一些相关联的问题:

  • 怎么有这么多非本项目的log出现? 譬如引入了其他的sdk,他们又很无节操的打了很多日志。
  • 怎么去除不必要的包的日志?
  • 为什么logger的名字能对应到指定的package或者类呢?

以上这些问题都是因为没搞明白,log4j是怎么去获取logger的,本文将通过 slf4j-log4j-impl 根据class去查找logger的过程来解答上述的疑问。

解析

从例子说起

  • 打印日志的例子
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}
  • 深入到LoggerFactory.getLogger方法
public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) {
           	//省略部分打印信息的代码...
        }
        return logger;
    }

这个DETECT_LOGGER_NAME_MISMATCH是什么意思呢,就是第一步中的参数并不是当前这个类的时候, 如果这个配置为true,则会打印一条类似下面的信息:

SLF4J: Detected logger name mismatch. Given name: "com.keven.demos.log.TestNameMismatch"; computed name: "com.keven.demos.log.AsyncLoggerDemo".
SLF4J: See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation

从LoggerFactory获取logger

  • 我们接着看getLogger方法:
//此处变成通过class的全名(package name + class name)获取logger
 public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        //省略非关键部分代码...
 }
 //这里就是说通过slf4j的实现类的loggerFactory获取logger
 //如何获取实现类的logger factory,这个在之前的文章中已经分析过了,有兴趣的同学可以看看
 //https://blog.csdn.net/sweetyi/article/details/104633321
 public static Logger getLogger(String name) {
      ILoggerFactory iLoggerFactory = getILoggerFactory();
      return iLoggerFactory.getLogger(name);
 }

从LoggerContext中获取logger

  • 在这里ILoggerFactory的实现类是:org.apache.logging.slf4j.Log4jLoggerFactory, 这个工厂类中没有getLogger方法,它会调用父类AbstractLoggerAdapter的getLogger方法,如下:
public L getLogger(final String name) {
		//获取logger配置的上下文,这里不展开
        final LoggerContext context = getContext();
        //这里是做一层logger的缓存
        final ConcurrentMap<String, L> loggers = getLoggersInContext(context);
        //根据名字获取logger
        final L logger = loggers.get(name);
        if (logger != null) {
            return logger;
        }
        //没找到现成的logger,重新生成一个
        loggers.putIfAbsent(name, newLogger(name, context));
        return loggers.get(name);
    }
  • getLoggersInContext方法是用来获取context所对应的logger的内存缓存, 以下省略了用读写锁加解锁的代码,不是重点。
/** * A map to store loggers for their given LoggerContexts. * context可能有多个,所需要找出的是对应context的缓存 */
    protected final Map<LoggerContext, ConcurrentMap<String, L>> registry = new WeakHashMap<>();

	public ConcurrentMap<String, L> getLoggersInContext(final LoggerContext context) {
        ConcurrentMap<String, L> loggers;
        //为了方便阅读分析,省略加锁代码...
        loggers = registry.get (context);
		//为了方便阅读分析,省略解锁代码...
        if (loggers != null) {
            return loggers;
        } else {
        	//为了方便阅读分析,省略加锁代码...
        	loggers = registry.get (context);
             if (loggers == null) {
                 loggers = new ConcurrentHashMap<> ();
                 registry.put (context, loggers);
             }
             return loggers;
			//为了方便阅读分析,省略解锁代码...
        }
    }
  • 主要是讲对logger做了缓存,那么缓存的logger是怎么生成的呢?入口从Log4jLoggerFactory.newLogger说起
@Override
    protected Logger newLogger(final String name, final LoggerContext context) {
        final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
        //Log4jLogger是为了适配slf4j的接口做的一层适配,这里运用到了适配器的设计模式
        //这里的newLogger方法也就是对context的getLogger包装了一层
        return new Log4jLogger(context.getLogger(key), name);
    }
	
	//LoggerContext的getLogger方法
	public Logger getLogger(final String name) {
        return getLogger(name, null);
    }
	
	//LoggerContext的getLogger方法
    public Logger getLogger(final String name, final MessageFactory messageFactory) {
    	//loggerRegistry的主要目的是对messageFactory, 和logger做了一层内存缓存,是这样一个结构:Map<factoryName, Map<classname, logger>>
    	//这里就不做展开了
        Logger logger = loggerRegistry.getLogger(name, messageFactory);
        if (logger != null) {
            AbstractLogger.checkMessageFactory(logger, messageFactory);
            return logger;
        }
        //重点生成logger的实例
        logger = newInstance(this, name, messageFactory);
        loggerRegistry.putIfAbsent(name, messageFactory, logger);
        return loggerRegistry.getLogger(name, messageFactory);
    }

创建Logger

  • 接上面newInstance方法:
protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
        return new Logger(ctx, name, messageFactory);
    }
	
    protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
        super(name, messageFactory);
        this.context = context;
        privateConfig = new PrivateConfig(context.getConfiguration(), this);
    }

真正的主角LoggerConfig

  • 你以为故事到这里就结束了么?我们看到,创建Logger的时候,创建了一个PrivateConfig,这个类的构造方法如下:你会发现它其实就是对LoggerConfig的一层包装
public PrivateConfig(final Configuration config, final Logger logger) {
            this.config = config;
            this.loggerConfig = config.getLoggerConfig(getName());
            this.loggerConfigLevel = this.loggerConfig.getLevel();
            this.intLevel = this.loggerConfigLevel.intLevel();
            this.logger = logger;
        }
  • 那么我们就看看怎么获取LoggerConfig呢?
public LoggerConfig getLoggerConfig(final String loggerName) {
    	//根据类的全名获取LoggerConfig
        LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
        if (loggerConfig != null) {
            return loggerConfig;
        }
        String substr = loggerName;
        //每次去掉名字中.后面的单词,然后尝试从已有的LoggerConfigs中获取config,也就是说可能根据包名找到LoggerConfig
        while ((substr = NameUtil.getSubName(substr)) != null) {
            loggerConfig = loggerConfigs.get(substr);
            if (loggerConfig != null) {
                return loggerConfig;
            }
        }
        //如果前面两种方法都没有找到就返回RootLoggerConfig
        return root;
    }

获取LoggerConfig流程图

NYNNYstart根据类的全名获取LoggerConfigLoggerConfig为null?返回LoggerConfig结束根据prefix获取LoggerConfigLoggerConfig为null?返回root LoggerConfig

验证

上面我们说到LoggerConfig才是真正的主角,我们看看打印日志是个什么调用链, 中间会省略部分过程
省略部分过程HelloWorld.log.infoLog4jLogger.logger.logIfEnabledAbstractLogger.logMessageLogger/AsyncLogger.logMessageLogger.privateConfig.loggerConfig.getReliabilityStrategyReliabilityStrategy.logloggerConfig.log结束

ps: ReliabilityStrategy只是对LoggerConfig的一层包装

小结

代码解析完了,开篇的问题解答一下

  • 怎么有这么多非本项目的log出现? 譬如引入了其他的sdk,他们又很无节操的打了很多日志。
    ==》因为设置了RootLogger,它是所有的Logger的默认配置
  • 怎么去除不必要的包的日志?指定logger的名字为想要过去的包的名字,但不设置相应的appender
<Logger name="your.package.name" level="${level}" additivity="false" >
 </Logger>
  • 为什么logger的名字能对应到指定的package或者类呢?
    这个就是本文的内容了

相关文章