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

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

情景

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

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

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

解析

从例子说起

  • 打印日志的例子
  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. public class HelloWorld {
  4. public static void main(String[] args) {
  5. Logger logger = LoggerFactory.getLogger(HelloWorld.class);
  6. logger.info("Hello World");
  7. }
  8. }
  • 深入到LoggerFactory.getLogger方法
  1. public static Logger getLogger(Class<?> clazz) {
  2. Logger logger = getLogger(clazz.getName());
  3. if (DETECT_LOGGER_NAME_MISMATCH) {
  4. //省略部分打印信息的代码...
  5. }
  6. return logger;
  7. }

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

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

从LoggerFactory获取logger

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

从LoggerContext中获取logger

  • 在这里ILoggerFactory的实现类是:org.apache.logging.slf4j.Log4jLoggerFactory, 这个工厂类中没有getLogger方法,它会调用父类AbstractLoggerAdapter的getLogger方法,如下:
  1. public L getLogger(final String name) {
  2. //获取logger配置的上下文,这里不展开
  3. final LoggerContext context = getContext();
  4. //这里是做一层logger的缓存
  5. final ConcurrentMap<String, L> loggers = getLoggersInContext(context);
  6. //根据名字获取logger
  7. final L logger = loggers.get(name);
  8. if (logger != null) {
  9. return logger;
  10. }
  11. //没找到现成的logger,重新生成一个
  12. loggers.putIfAbsent(name, newLogger(name, context));
  13. return loggers.get(name);
  14. }
  • getLoggersInContext方法是用来获取context所对应的logger的内存缓存, 以下省略了用读写锁加解锁的代码,不是重点。
  1. /** * A map to store loggers for their given LoggerContexts. * context可能有多个,所需要找出的是对应context的缓存 */
  2. protected final Map<LoggerContext, ConcurrentMap<String, L>> registry = new WeakHashMap<>();
  3. public ConcurrentMap<String, L> getLoggersInContext(final LoggerContext context) {
  4. ConcurrentMap<String, L> loggers;
  5. //为了方便阅读分析,省略加锁代码...
  6. loggers = registry.get (context);
  7. //为了方便阅读分析,省略解锁代码...
  8. if (loggers != null) {
  9. return loggers;
  10. } else {
  11. //为了方便阅读分析,省略加锁代码...
  12. loggers = registry.get (context);
  13. if (loggers == null) {
  14. loggers = new ConcurrentHashMap<> ();
  15. registry.put (context, loggers);
  16. }
  17. return loggers;
  18. //为了方便阅读分析,省略解锁代码...
  19. }
  20. }
  • 主要是讲对logger做了缓存,那么缓存的logger是怎么生成的呢?入口从Log4jLoggerFactory.newLogger说起
  1. @Override
  2. protected Logger newLogger(final String name, final LoggerContext context) {
  3. final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
  4. //Log4jLogger是为了适配slf4j的接口做的一层适配,这里运用到了适配器的设计模式
  5. //这里的newLogger方法也就是对context的getLogger包装了一层
  6. return new Log4jLogger(context.getLogger(key), name);
  7. }
  8. //LoggerContext的getLogger方法
  9. public Logger getLogger(final String name) {
  10. return getLogger(name, null);
  11. }
  12. //LoggerContext的getLogger方法
  13. public Logger getLogger(final String name, final MessageFactory messageFactory) {
  14. //loggerRegistry的主要目的是对messageFactory, 和logger做了一层内存缓存,是这样一个结构:Map<factoryName, Map<classname, logger>>
  15. //这里就不做展开了
  16. Logger logger = loggerRegistry.getLogger(name, messageFactory);
  17. if (logger != null) {
  18. AbstractLogger.checkMessageFactory(logger, messageFactory);
  19. return logger;
  20. }
  21. //重点生成logger的实例
  22. logger = newInstance(this, name, messageFactory);
  23. loggerRegistry.putIfAbsent(name, messageFactory, logger);
  24. return loggerRegistry.getLogger(name, messageFactory);
  25. }

创建Logger

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

真正的主角LoggerConfig

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

获取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
  1. <Logger name="your.package.name" level="${level}" additivity="false" >
  2. </Logger>
  • 为什么logger的名字能对应到指定的package或者类呢?
    这个就是本文的内容了

相关文章