关于slf4j_log4j2源码学习心得

目录
  • 日志工厂获取Logger
  • 日志输出Logger.info
  • 异步日志写入
  • 异步日志上下文选择
  • locateContext定位选择日志上下文
  • 总结

日志工厂获取Logger

获取日志工厂_getILoggerFactory_

执行初始化performInitialization

绑定工厂bind

查找可能被绑定的StaticLoggerBinder类路径findPossibleStaticLoggerBinderPathSet

如果LoggerFactory类加载器为空则使用System类加载器,如果System类加载器为空则使用Bootstrap类加载器加载读取org/slf4j/impl/StaticLoggerBinder.class类资源路径

如果LoggerFactory类加载器不为空,使用loggerFactoryClassLoader类加载器读取org/slf4j/impl/StaticLoggerBinder.class类资源路径

获取StaticLoggerBinder单例_SINGLETON_

打印实际绑定的StaticLoggerBInder实例,标识初始化成功

此时日志工厂已经完成初始化,创建日志代替者工厂SubstituteLoggerFactory,获取替代者工厂中的Loggers遍历其名称从日志工厂中获取相应的Logger,将Logger设置为替代者委派对象setDelegate

重新播放替代者工厂事件队列中的事件

清楚替代者工厂事件队列,日志列表

使用StaticLoggerBinder获取日志工厂,log4j2的绑定着实现返回Log4jLoggerFactory

日志工厂中获取Logger,先获取日志上下文,从日志上下文中获取Logger

根据日志配置属性log4j2.loggerContextFactory获取日志上下文工厂

不存在日志上下文工厂配置则使用ProviderUtil提供者工具类读取SPI配置默认为Log4jContextFactory

日志上下文工厂构造器中创建日志上下文选择器createContextSelector,根据Log4jContextSelector属性配置获取日志上下文选择器实现,假定为异步实现:AsyncLoggerContextSelector

日志上下文工厂使用日志上下文选择器创建日志上下文AsyncLoggerContext,日志上下文构造器中创建AsyncLoggerDisruptor

如果日志上下文处于初始化状态,启动日志上下文,启动日志上下文中的loggerDisruptor

启动Disruptor

计算ringBufferSize,如果启用了Threadlocals(log4j2.enable.threadlocals)则默认4k,否则256k,读取AsyncLogger.RingBufferSize属性配置;如果size小于128则使用128修正

创建等待策略,如果属性名称以AsyncLogger.开头,读取AsyncLogger.Timeout,否则读取AsyncLoggerConfig.Timeout配置的超时时间,默认为10毫秒;读取AsyncLogger.WaitStrategy属性配置的策略类型,默认为TIMEOUT,根据类型创建等待策略,默认为超时类型:TimeoutBlockingWaitStrategy

创建单线程线程池_newSingleThreadExecutor_

创建异步队列满的处理策略AsyncQueueFullPolicyFactory.create,读取log4j2.AsyncQueueFullPolicy策略配置,默认策略为同步阻塞DefaultAsyncQueueFullPolicy,Discard类型为DiscardingAsyncQueueFullPolicy,否则读取用户自定义策略,获取类路径上的AsyncQueueFullPolicy实现类

创建Disruptor实例

为Disruptor实例绑定ExceptionHandler,读取AsyncLogger.ExceptionHandler属性配置加载实现类,默认为AsyncLoggerDefaultExceptionHandler

为Disruptor实例绑定事件handler:RingBufferLogEventHandler

启动Disruptor

提交线程BatchEventProcessor至线程池

向序列屏障提交序列号申请并等待,等待策略默认为Timeout,超时处理后进行线程yield资源释放

获取到有效序列号根据序列号获取数据RingBufferLogEvent

回调事件handle实例记录日志RingBufferLogEventHandler.onEvent

执行事件RingBufferLogEvent.execute(异步写日志)

启动父类日志上下文LoggerContext.start(),重新加载配置reconfigure,设置配置setConfiguration,启动配置config.start();

map.putIfAbsent("contextName", contextName);
config.start();
this.configuration = config;
  • 由日志上下文中获取日志getLogger,不存在则创建日志AsyncLogger
  • 返回异步日志实例

日志输出Logger.info

日志打印logger.info

MessageFactory2将字符串日志封装为Message实例:ParameterizedMessageFactory.newMessage

从缓存中获取RingBuffer日志事件转换器:AsyncLogger.logWithThreadLocalTranslator.getCachedTranslator

初始化RingBuffer日志事件转换器:AsyncLogger.initTranslator,RingBufferLogEventTranslator.setBasicValues设置基础值

发布日志事件转换器:RingBufferLogEventTranslator

异步日志Disruptor尝试发布tryPublish

Disruptor获取RingBuffer尝试发布

转换器将日志数据转换为日志事件:RingBufferLogEventTranslator.translateTo

根据序号sequence获取对应的事件实例RingBufferLoggerEvent并将转换器中的数据写入RingBufferLoggerEvent.setValues

发布消息序列号MultiProducerSequencer

等待策略发送唤醒信号TimeoutBlockingWaitStrategy.signalAllWhenBlocking

异步日志写入

BatchEventProcessor.eventHandler.onEvent->RingBufferLogEventHandler.onEvent->执行事件RingBufferLogEvent.execute

执行异步日志写入当前事件消息actualAsyncLog(this)

从可靠性策略工厂中获取可靠性策略,根据参数配置获取:log4j.ReliabilityStrategy,默认AwaitCompletion:AwaitCompletionReliabilityStrategy,写入日志strategy.log(AsyncLogger, event);

可靠性策略获取活跃的LoggerConfig,如果没取到,递归AsyncLogger的next节点获取

LoggerConfig写入日志log,走过滤器链过滤事件之后处理事件processLogEvent

调用Appender:callAppenders

调用AsyncLoggerConfigDisruptor的tryEnqueue,准备事件,将事件封装为Log4jLogEvent绑定至Log4jEventWrapper.event然后尝试发布

事件handle处理事件Log4jEventWrapperHandler.onEvent

异步调用Appender:asyncCallAppenders,调用父类callAppenders

遍历AppenderControl调用callAppender,调用callAppenderPreventRecursion

尝试appender tryCallAppender

调用Appender追加方法append追加日志,假定为RollingRandomAccessFileAppender配置实现,调用父类tryAppend

读取log4j2.enable.direct.encoders配置是否直接编译,默认为true,直接编译事件directEncodeEvent

获取布局Layout编译事件encode,假定为PatternLayout配置实现

如果布局的事件序列化类型eventSerializer不是Serializer2,调用父类encode,否则继续

从threadlocal中获取StringBuilder,不存在则创建,如果超出最大值则trim至最大值,最大值配置:log4j.layoutStringBuilder.maxSize,默认为2*1024,stringbuilder设置length为0

将事件序列化后放入StringBuilder:toSerializable

获取StringBuilder编译器编译StringBuilder至ByteBufferDestination

manager刷新flush,刷新至目的地,假定为RollingRandomAccessFileManager配置实现,写入目标文件

至此写入日志完成

异步日志上下文选择

通过管理器获取日志上下文LogManager.getContext(false)

public static LoggerContext getContext(final boolean currentContext) {
    // TODO: would it be a terrible idea to try and find the caller ClassLoader here?
    try {
        return factory.getContext(FQCN, null, null, currentContext, null, null);
    } catch (final IllegalStateException ex) {
        LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
        return new SimpleLoggerContextFactory().getContext(FQCN, null, null, currentContext, null, null);
    }
}

上下文根据选择器选择获取,我们假定是AsyncLoggerContextSelector类型,获取方式是其超类ClassLoaderContextSelector提供getContext实现

可以看到只传了一个参数currentContext,如果为true则会根据ContextAnchor.THREAD_CONTEXT当前线程对应的上下文,如果为空则获取默认的上下文DEFAULT_CONTEXT,如果默认为空则会创建并缓存

如果currentContext为false则会选择一个最匹配的恰当的上下文返回,如果ClassLoader不为空则按照指定的ClassLoader定位选择locateContext。否则按照当前的调用类的classloader进行定位选择

public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
            final URI configLocation) {
        if (currentContext) {
            final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
            if (ctx != null) {
                return ctx;
            }
            return getDefault();
        } else if (loader != null) {
            return locateContext(loader, configLocation);
        } else {
            final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
            if (clazz != null) {
                return locateContext(clazz.getClassLoader(), configLocation);
            }
            final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
            if (lc != null) {
                return lc;
            }
            return getDefault();
        }
    }

locateContext定位选择日志上下文

根据classloader的hashcode值获取对应的日志上下文,异步选择器实现添加了前缀

@Override
    protected String toContextMapKey(final ClassLoader loader) {
        // LOG4J2-666 ensure unique name across separate instances created by webapp classloaders
        return "AsyncContext@" + Integer.toHexString(System.identityHashCode(loader));
    }

如果对应的日志上下文为空则创建并缓存

如果configLocation为空则遍历classloader的父classloader获取对应的日志上下文返回,如果不存在则继续创建

如果configLocation不为空则直接根据configLocation创建日志上下文并按照classloader的hashcode值拼接的名称缓存至CONTEXT_MAP

如果CONTEXT_MAP缓存存在对应的日志上下文,并且不为空,则会判断configLocation是否一致equals方法,如果不一致则更新后返回

如果CONTEXT_MAP缓存存在对应的日志上下文,并且为空,则创建日志上下文以及日志上下文的弱引用WeakReference并缓存至

总结

对于异步日志,如果includeLocation没有指定,默认是关闭的状态,也就是不会记录日志调用时的堆栈位置信息:LoggerConfig.includeLocation。

如果记录日志时存在Throwable对象,则会记录至RingBufferLogEventTranslator.thrown。

thrown在日志事件反序列化后为null,thrownProxy可能不为null,thrownProxy是thrown的代理,即使用thrown为入参构造的代理实例

    // Note: for asynchronous loggers, includeLocation default is FALSE,
    // for synchronous loggers, includeLocation default is TRUE.
    protected static boolean includeLocation(final String includeLocationConfigValue) {
        if (includeLocationConfigValue == null) {
            final boolean sync = !AsyncLoggerContextSelector.isSelected();
            return sync;
        }
        return Boolean.parseBoolean(includeLocationConfigValue);
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot集成slf4j+log4j2的示例代码

    本文介绍了SpringBoot集成slf4j+log4j2的示例代码,分享给大家,具体如下: Maven依赖 <!--增加log4j2依赖↓--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency&g

  • 浅谈Java日志框架slf4j作用及其实现原理

    SLF4J是一个日志框架抽象层,底下绑定具体的日志框架,比如说Log4J,Logback,Java Logging API等.SLF4J也有自身的默认实现,但是我们还是主要以日志框架抽象层的身份使用SLF4J. 要使用SLF4J,得包含对"org.slf4j:slf4j-api"的依赖. 简单回顾门面模式 slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式, 门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用.用一

  • Java log4j详细教程

    一:Log4j入门简介学习 Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务器.NT的事件记录器.UNIX Syslog守护进程等:我们也可以控制每一条日志的输出格式:通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程.最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码. 此外,通过Log4j其他语言接口,您可以在C.C++..Net.PL/SQL程序中

  • 关于slf4j_log4j2源码学习心得

    目录 日志工厂获取Logger 日志输出Logger.info 异步日志写入 异步日志上下文选择 locateContext定位选择日志上下文 总结 日志工厂获取Logger 获取日志工厂_getILoggerFactory_ 执行初始化performInitialization 绑定工厂bind 查找可能被绑定的StaticLoggerBinder类路径findPossibleStaticLoggerBinderPathSet 如果LoggerFactory类加载器为空则使用System类加载

  • Python内建类型list源码学习

    目录 问题: 1 常用方法 小结: 题外话: 2 list的内部结构:PyListObject 3 尾部操作和头部操作 3.1 尾部操作 3.2 头部操作 4 浅拷贝和深拷贝 4.1 浅拷贝 4.2 深拷贝 4.3 直接赋值 4.4 小结 个人总结: TODO: 5 动态数组 5.1 容量调整 5.2 append() 5.3 insert() 5.4 pop() 5.5 remove() 6 一些问题 问题: “深入认识Python内建类型”这部分的内容会从源码角度为大家介绍Python中各种

  • Bootstrap源码学习笔记之bootstrap进度条

    基本样式 要实现进度条效果要使用两个容器,外容器使用"progress"样式,子容器使用"progress-bar"样式.例如: <div class="progress"> <div class="progress-bar" style="width:40%"></div> </div> progress样式主要设置进度条容器的背景色,容器高度.间距等,pr

  • Android源码学习之组合模式定义及应用

    组合模式定义: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. 将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性. 如上图所示(截取自<Head First De

  • 通过JDK源码学习InputStream详解

    概况 本文主要给大家介绍了通过JDK源码学习InputStream的相关内容,JDK 给我们提供了很多实用的输入流 xxxInputStream,而 InputStream 是所有字节输入流的抽象.包括 ByteArrayInputStream .FilterInputStream .BufferedInputStream .DataInputStream 和 PushbackInputStream 等等.下面话不多说了,来一起看看详细的介绍吧. 如何阅读JDK源码. 以看核心虚拟机(hotsp

  • vue 2.5.1 源码学习 之Vue.extend 和 data的合并策略

    1. 子类父类 2.Vue.extend()      //创建vue的子类 组件的语法器 Vue.extend(options) Profile().$mount('#app') // 挂在app上,并替换app 新建 initExend ==> Vue.extend 3. strat.data ==> if(!vm){子组件中data的值是一个方法function ==> mergeDataorFn()} // 数据的合并 ==> else {} //通过实例绑定的data 实

  • vue源码学习之Object.defineProperty对象属性监听

    本文介绍了vue源码学习之Object.defineProperty对象属性监听,分享给大家,具体如下: 参考版本 vue源码版本:0.11 相关 vue实现双向数据绑定的关键是 Object.defineProperty ,让我们先来看下这个函数. 在MDN上查看有关Object.defineProperty的解释. 我们先从最简单的开始: let a = {'b': 1}; Object.defineProperty(a, 'b', { enumerable: false, configur

  • 详解Vue源码学习之双向绑定

    原理 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter.Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器. 上面那段话是Vue官方文档中截取的,可以看到是使用Object.defineProperty实现对数据改变的监听.Vue主要使用了观

  • spring源码学习之bean的初始化以及循环引用

    实例化方法,把bean实例化,并且包装成BeanWrapper 1.点进这个方法里面. 这个方法是反射调用类中的 factoryMethod 方法. 这要知道@Bean 方法的原理, 实际上spring 会扫描有@bean 注解的方法, 然后把方法名称设置到 BeanDefinition 的 factoryMethod属性中, 接下来就会调到上面截图中的方法实现@Bean 方法的调用. 2. 有参构造函数的时候 determineConstructorsFromBeanPostProcessor

  • Spring源码学习之动态代理实现流程

    注:这里不阐述Spring和AOP的一些基本概念和用法,直接进入正题. 流程   Spring所管理的对象大体会经过确定实例化对象类型.推断构造方法创建对象(实例化).设置属性.初始化等等步骤.在对象初始化阶段,Spring为开发者提供了一个BeanPostProcessor接口,它会在对象初始化之前和初始化之后被调用(初始化,不是实例化,对应实例化的是InstantiationAwareBeanPostProcessor接口). public interface BeanPostProcess

随机推荐