文章目录
情景
之所以想写这篇文章是因为经常看到一些相关联的问题:
- 怎么有这么多非本项目的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流程图
验证
上面我们说到LoggerConfig才是真正的主角,我们看看打印日志是个什么调用链, 中间会省略部分过程
ps: ReliabilityStrategy只是对LoggerConfig的一层包装
小结
代码解析完了,开篇的问题解答一下
- 怎么有这么多非本项目的log出现? 譬如引入了其他的sdk,他们又很无节操的打了很多日志。
==》因为设置了RootLogger,它是所有的Logger的默认配置 - 怎么去除不必要的包的日志?指定logger的名字为想要过去的包的名字,但不设置相应的appender
<Logger name="your.package.name" level="${level}" additivity="false" >
</Logger>
- 为什么logger的名字能对应到指定的package或者类呢?
这个就是本文的内容了

本文详细解析了log4j2中通过类名获取logger的过程,探讨Logger与LoggerConfig的关系。从LoggerFactory开始,经过LoggerContext和缓存,最终找到LoggerConfig的创建。解释了为何会存在非本项目的日志,以及如何去除不必要的包日志,同时揭示logger名称对应指定package或类的原理。

923

被折叠的 条评论
为什么被折叠?



