日志框架Log4j的学习小记

2022-08-11 16:59:36 浏览数 (1)

Java项目的框架基本就是slf4j,slf4j提供了一套规范,也就是门面,而至于后边是如何实现的只要按照人家定义的接口去做就行了。常见的日志框架又springboot自带的logback,还有异步的日志框架log4j,当然还有一些大佬自己做日志框架的。这里作者大概的看了一下日志框架的代码。稍微讲解一下日志框架是怎么做的。

首先说的就是slf4j这个门面,之前在重构这本书中就提到要善于抽象代码形成主体结构。然后在定制化的工作中进行重载等工作,Slf4j就将日志的一般方法进行定义。并提供了一些默认的日志打印方法。

代码语言:javascript复制
public static Logger logger= LoggerFactory.getLogger("123");

通过上述代码,我们进行跟踪。

通过对代码的查看,这块在获取日志的时候首先设置采用log4j作为日志框架,如果出现异常则采用默认的日志框架。

当项目中采用log4j作为日志框架的时候,getlogger如下:

而在logmanger中的静态方法中则进行了日志框架配置文件的解析

而对于方法logger.debug()来说最终走的方法也就是log4jloggerAdapter提供的debug方法。

通过跟踪,发现日志的打印采用多线程。

通过查看代码,我们发现这里打印日志的时候将日志写到list列表中,然后通过Dispatcher线程将对list中的列表进行处理。而在AsynAppender初始化的时候就将Dispatcher线程进行启动。

通过上述的代码阅读,我们大概了解logger的一般过程,但是我们的asynappender是什么时候添加到我们的logger对象中的?我们怀着问题找到我们当时解析log4j.xml的文件。一直跟踪,最后找到如下的代码:

基于上述解析,我们大概的说一下logger日志的基本过程。首先通过slfj是一个默认的门面框架。一般又logback和log4j的实现,log4j采用新线程来处理日志,所有的日志通过list进行缓存,log4j异步线程会从list中获取日志的内容然后根据策略写到不同的文件或者网络接口或者控制台等。在初始化的时候,slfj会先判断是否存在log4j,在不存在情况下才会走默认的其他日志框架。确认使用log4j之后,会通过logmanager的静态方法块解析log4j.xml文件,并据此初始化log4j的日志线程,并启动该日志线程。不同的logger对应不同的名称,最终通过hashTable进行缓存,当然这块也可以采用concurentHashMap,日志的打印通过list进行汇总,然后日志的异步线程结合日志的策略进行不同方式的展示。

说到这里,可能就有一个疑问了,我们的配置文件中好像有个配置。可以指定这个log4j.xml文件的地址和名称,如下所示,于是乎我们在之前的那个代码写死的是不是我们理解的不对。

代码语言:javascript复制
logging.config=classpath:config/log4j2.xml

跟着代码,我们发现在Spring的监听器中有这样的代码。

代码语言:javascript复制
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
//这里的logginSystem经过这里的处理就变成了Log4J2LoggingSystem,此类中包含我们上边说的logmanger部分,也就是解析xml的部分,也就是创建线程。初次之外这里的log4j2LogginSystem也是我们上次说的RunTime回调定义的地方。
        this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
//前置初始化
        this.loggingSystem.beforeInitialize();
    }
代码语言:javascript复制


//从配置文件中拿到logging.config并进行LoggingSystem的初始化
    private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
        LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
        String logConfig = environment.getProperty("logging.config");
        if (this.ignoreLogConfig(logConfig)) {
            system.initialize(initializationContext, (String)null, logFile);
        } else {
            try {
                ResourceUtils.getURL(logConfig).openStream().close();
//这块在初始化的时候对配置文件进行解析,并对logger进行注册。
                system.initialize(initializationContext, logConfig, logFile);
            } catch (Exception var7) {
                System.err.println("Logging system failed to initialize using configuration from '"   logConfig   "'");
                var7.printStackTrace(System.err);
                throw new IllegalStateException(var7);
            }
        }
    }
//加载配置文件 ,location为配置文件中的日志xml文件地址
    protected void loadConfiguration(String location, LogFile logFile) {
        Assert.notNull(location, "Location must not be null");
        try {
//拿到logmanger中的日志
            LoggerContext ctx = this.getLoggerContext();
            URL url = ResourceUtils.getURL(location);
            ConfigurationSource source = this.getConfigurationSource(url);
            ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
        } catch (Exception var6) {
            throw new IllegalStateException("Could not initialize Log4J2 logging from "   location, var6);
        }
    }

这块写的有点乱,作者本人也迷迷糊糊的。想着大概的过程应该是这样的,但是遇到了不同的logger注册器,有hashtable、concurentHashMap以及hashMap,有点乱。一句话还是没深刻的理解吧,大概的过程咋算是大概的说了一下了。对于动态修改日志级别的这种问题,因为我们的java是引用传值,因此我们我们直接从logmanager中获取指定名称的logger,然后修改日志level即可。当然日志的缓存队列我们可以采用阻塞链表来做。

由于作者水平有限,分析的不是很透彻,作者本人也是知道一个大概的方向,细节上也是迷迷糊糊,惭愧!如对大家有一定的误导,还望见谅!

0 人点赞