log4j2(三) 如何通過類名獲取到logger呢?logger與loggerConfig是什麼關係?-源碼解析

情景

之所以想寫這篇文章是因爲經常看到一些相關聯的問題:

  • 怎麼有這麼多非本項目的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流程圖

N
Y
N
N
Y
start
根據類的全名獲取LoggerConfig
LoggerConfig爲null?
返回LoggerConfig
結束
根據prefix獲取LoggerConfig
LoggerConfig爲null?
返回root LoggerConfig

驗證

上面我們說到LoggerConfig纔是真正的主角,我們看看打印日誌是個什麼調用鏈, 中間會省略部分過程

省略部分過程
HelloWorld.log.info
Log4jLogger.logger.logIfEnabled
AbstractLogger.logMessage
Logger/AsyncLogger.logMessage
Logger.privateConfig.loggerConfig.getReliabilityStrategy
ReliabilityStrategy.log
loggerConfig.log
結束

ps: ReliabilityStrategy只是對LoggerConfig的一層包裝

小結

代碼解析完了,開篇的問題解答一下

  • 怎麼有這麼多非本項目的log出現? 譬如引入了其他的sdk,他們又很無節操的打了很多日誌。
    ==》因爲設置了RootLogger,它是所有的Logger的默認配置
  • 怎麼去除不必要的包的日誌?指定logger的名字爲想要過去的包的名字,但不設置相應的appender
 <Logger name="your.package.name" level="${level}" additivity="false" >
 </Logger>
  • 爲什麼logger的名字能對應到指定的package或者類呢?
    這個就是本文的內容了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章