slf4j 之 ch.qos.logback 源码阅读

前言

最近有个项目再写日志的时候我原本想封装一个自己的Logger来实现一个方法 ,日志多条输出。成功到是没问题。实现就是加了一个facade来做。业务部分调用facade来,facade来调用多个logger输出到不同日志中。但是里面有个配置%C 或者%class %L出现了问题。在日志中输出到了facade类对象的调用部分。不是实际业务部分代码调用。这让我比较麻烦。所以打算看一下源码并分析一下问题所在。
通过一个简单的代码作为切入口

	public static void main(String[] args) {
		ILoggerFactory factory = LoggerFactory.getILoggerFactory();
		Logger log = factory.getLogger("ll");
		log.info("sss");
	}

其中核心代码
LoggerFactory.getILoggerFactory() 获取logger工厂

Logger log = factory.getLogger(“ll”)与获取logger对象获取
说明一下logback的实现有很多这里我用的是:参见maven引用

<dependencies>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.28</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
			<version>1.2.3</version>
		</dependency>
	</dependencies>

LoggerFactory

先看LoggerFactory的实现
根据调用主要为
LoggerFactory.getILoggerFactory() ;方法
源码如下:

  public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

可以看出这是一个双检查的单例模式来获取logger工厂。
其中主要核心方法有两行

   1.    performInitialization(); //私有内部方法。可以看出就是一个初始化的方法
   2.    return StaticLoggerBinder.getSingleton().getLoggerFactory();//而这条才是最关键的核心获取LoggerFactory的实现方法。

先了解一下内部初始化,看看工厂再返回LoggerFactory前都做了些什么

  private final static void performInitialization() {
        bind(); // 核心前置方法,初始化的第一步
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck(); //一个版本检查的方法,这里这次也不说了。
        }
    }

关于这里已经多次出现常量
INITIALIZATION_STATE 如字意 初始化状态。
目前可知主要的有

  • UNINITIALIZED 未初始化
  • ONGOING_INITIALIZATION 即将初始化
  • SUCCESSFUL_INITIALIZATION 初始化成功
  • 其他目前不重要

看一下bind() 方法

说明一下这里的主要核心代码都在try里面。catch不作为主要阅读地方
 private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            判断一下是不是android 当然不是了
            if (!isAndroid()) {
         //   进行一个findPossibleStaticLoggerBinderPathSet 的方法调用。其实这个方法就是获取slf4j的主要实现类,“org/slf4j/impl/StaticLoggerBinder.class” 是否存在
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//报告一下找的结果。当工程中出现多个这个类的时候会报错,反正意思就是只能有一个这个类
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            //这个需要注意一下,StaticLoggerBinder类作为获取loggerFactory时核心的,只要这个类初始化完成就算前期初始化已经完成了一大半
            StaticLoggerBinder.getSingleton();
          //设置一下初始状态为successful
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            //这里是在报告一下 初始化StaticLoggerBinder.getSingleton()的结果
            reportActualBinding(staticLoggerBinderPathSet);
            //这里是修复并替换一下另外的logger ,具体意义没空看下次补。这次不管了
            fixSubstituteLoggers();
            //这里也是跟上面的差不多。没空看意义不知。下次补
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
           //异常处理不管了。。有兴趣自己看
        } catch (NoClassDefFoundError ncde) {
           String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

关于bind方法完成了。这里的主要就是找那个核心工厂创造类StaticLoggerBinder 并在这里对StaticLoggerBinder进行了初始化调用。里面一些私有方法只是做了简单意义说明,具体实现还是可以看的。比如findPossibleStaticLoggerBinderPathSet 就是一个的ClassLoader加载方式。看看顺便可以复习一下双亲委派。

StaticLoggerBinder

看一下StaticLoggerBinder 类
这个类代码不多一次就贴上再说,还是大家请看汉子说明,那是我的理解

public class StaticLoggerBinder implements LoggerFactoryBinder {

    /**
     * Declare the version of the SLF4J API this implementation is compiled
     * against. The value of this field is usually modified with each release.
     */
    // to avoid constant folding by the compiler, this field must *not* be final
    public static String REQUESTED_API_VERSION = "1.7.16"; // !final

    final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";

    /**
     * The unique instance of this class.
     * 单例模式不用解释了。硬汉式的我喜欢。嘎嘎嘎就是new
     */
    private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

    private static Object KEY = new Object();
//静态初始化方法,再上面那个类的bind方法中调用了这个类那么这个初始化方法就会被执行,那么这个方法是首要被阅读的
    static {
        SINGLETON.init();
    }

    private boolean initialized = false;
    //这里需要知道这个LoggerContext 是下一波的关键。
    private LoggerContext defaultLoggerContext = new LoggerContext();
    //这个也是下一波的关键。首先可以知道这个ContextSelectorStaticBinder  也是一个单例
    private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
// 单例 私有的构造函数
    private StaticLoggerBinder() {
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
    }
//单例 对外开放的获取实例的方法。
    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    /**
     * Package access for testing purposes.
     * 重置方法,这几乎没人用过啊。,
     */
    static void reset() {
        SINGLETON = new StaticLoggerBinder();
        SINGLETON.init();
    }

    /**
     * Package access for testing purposes.
     * 这个是关键核心方法了牵扯到下一步的获取iloggerfactory
     */
    void init() {
        try {
            try {
            //这啥意思?!也没返回也没啥的, 那么可以肯定的是再ContextInitializer这里肯定修改了defaultLoggerContext 这个引用的一些值了。那么先记着一会再看。
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // logback-292
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            //这个contextSelectorBinder 的初始化。刚才说了这个是下一步的关键了。
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            initialized = true;
        } catch (Exception t) { // see LOGBACK-1159
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }
//接口中的两个实现方法这个作为主要的需要看
    public ILoggerFactory getLoggerFactory() {
    //当init方法全部完成后会设置这个表示为true,要是设置完成了就不会返回这个defaultLoggerContext ,当没有设置完成就是再contextSelectorBinder init出现异常了那么就返回这个默认的defaultLoggerContext。
        if (!initialized) {
            return defaultLoggerContext;
        }
// init完了当然不是null了。也算一个双重检查吧
        if (contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
        }
        //返回contextSelectorBinder getContextSelector的 getLoggerContext
        return contextSelectorBinder.getContextSelector().getLoggerContext();
    }

    public String getLoggerFactoryClassStr() {
        return contextSelectorBinder.getClass().getName();
    }

}

大致完成这个类说明了几个问题。contextSelectorBinder 初始化失败会返回一个defaultLoggerContext 若成功则返回自己的.getContextSelector().getLoggerContext()。而再init方法中的 new ContextInitializer(defaultLoggerContext).autoConfig();到底对这个defaultLoggerContext都干了些什么呢。那么我们来看下一个

ContextInitializer

在上个类中对defaultLoggerContext 都干了些什么呢。通过调用可以看到构造函数的传值和autoConfig();的调用。
那就看看这两个主要方法:

   public ContextInitializer(LoggerContext loggerContext) {
        this.loggerContext = loggerContext;
    }

鸡毛没干就是赋值了,那关键就是再autoConfig中了

    public void autoConfig() throws JoranException {
    //大致看了一下。StatusListenerConfigHelper 这个类里面用到了loggerContext 对他添加了一些监听,意思就是看看有没有什么变化什么的。这里就不在详说了,那么就可以知道这里的loggerContext 完成installIfAsked后就会有状态监听了。
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        //这个方法有意思。就是获取配置文件在哪里,配置文件都可以叫什么名字。都在这个方法里面获取的。我们都知道在ide里面没有配置文件的时候就会打印红色的,有了配置文件就打黑色的格式文件。那么这里就是那块设置的关键了。
        URL url = findURLOfDefaultConfigurationFile(true);
        //通常我们都是有配置文件的,那么这URL就肯定不是null
        if (url != null) {
        //配置文件的应用了,就是解析你的配置文件什么的
            configureByResource(url);
        } else {
            Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
            if (c != null) {
                try {
                    c.setContext(loggerContext);
                    c.configure(loggerContext);
                } catch (Exception e) {
                    throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
                                    .getCanonicalName() : "null"), e);
                }
            } else {
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(loggerContext);
                basicConfigurator.configure(loggerContext);
            }
        }
    }

这个类,主要就是加载配置文件为loggerContext增加监听状态了。
其中需要注意的是 configureByResource(url); 方法
方法源码如下。

   public void configureByResource(URL url) throws JoranException {
        if (url == null) {
            throw new IllegalArgumentException("URL argument cannot be null");
        }
        final String urlString = url.toString();
        if (urlString.endsWith("groovy")) {
            if (EnvUtil.isGroovyAvailable()) {
                // avoid directly referring to GafferConfigurator so as to avoid
                // loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
                GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
            } else {
                StatusManager sm = loggerContext.getStatusManager();
                sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
            }
        } else if (urlString.endsWith("xml")) {
        //这里是一个点
        //主要看看你是什么类型的配置文件怎么加载,通常都使用的是xml类型的可以看到代码中
        //JoranConfigurator  这个类这里就不贴代码了。有兴趣可以自己看。这里其实是装载了很多action 但是其中最重要的就是对配置文件中appender的加载。
        //这里不是再而对于appender的加载时再包 ch.qos.logback.core下。
            JoranConfigurator configurator = new JoranConfigurator();
            
            configurator.setContext(loggerContext);
            configurator.doConfigure(url);
        } else {
            throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
        }
    }

ContextSelectorStaticBinder

这个类方法也不多

再StaticLoggerBinder 的方法init中对进行了init方法 contextSelectorBinder.init(defaultLoggerContext, KEY);
看下到底干了什么

  public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
                    IllegalAccessException, InvocationTargetException {
                    // 一个key的操作就是为了安全。无视吧
        if (this.key == null) {
            this.key = key;
        } else if (this.key != key) {
            throw new IllegalAccessException("Only certain classes can access this method.");
        }
//这里是看一下是获取一个环节变量叫"logback.ContextSelector" 看看你是否有设置。
        String contextSelectorStr = OptionHelper.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR);
        //关于这个的设置就是来看就是动态的设置 ContextSelector 。一般我没设置。具体功能回头再说,只看默认的
        if (contextSelectorStr == null) {
        //默认的是 DefaultContextSelector 这个对象有时一个关键了。
            contextSelector = new DefaultContextSelector(defaultLoggerContext);
        } else if (contextSelectorStr.equals("JNDI")) {
            // if jndi is specified, let's use the appropriate class
            contextSelector = new ContextJNDISelector(defaultLoggerContext);
        } else {
            contextSelector = dynamicalContextSelector(defaultLoggerContext, contextSelectorStr);
        }
    }

关于contextSelectorBinder.getContextSelector().getLoggerContext() 其实就是在上段代码中对contextSelector 的初始化操作。主要是看你的"logback.ContextSelector" 配置。默认的就是DefaultContextSelector 那么关键就在于 DefaultContextSelector 中的.getLoggerContext()都干了什么

DefaultContextSelector

这个类太无聊了。就是默认的一堆
主要都是以下方法

   private LoggerContext defaultLoggerContext;

    public DefaultContextSelector(LoggerContext context) {
        this.defaultLoggerContext = context;
    }

    public LoggerContext getLoggerContext() {
        return getDefaultLoggerContext();
    }

    public LoggerContext getDefaultLoggerContext() {
        return defaultLoggerContext;
    }

饶了一大圈。当你不指定"logback.ContextSelector" 就是用的是这个默认的。而默认的这个就是 再
StaticLoggerBinder中的

    private LoggerContext defaultLoggerContext = new LoggerContext();

new的这个对象
那么饶了一大圈。若是采用最基本和默认的方法获取 ILoggerFactory其实就是 获取了一个 LoggerContext 的defaultLoggerContext 对象。

LoggerContext

public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle

通过类声明就可以知道了这玩意就是个ILoggerFactory的实现类
而开始的代码调用

	ILoggerFactory factory = LoggerFactory.getILoggerFactory();

其实获得的就是一个LoggerContext 的实例。
这部分Logger类工厂获取 LoggerContext对象算是告一段落
附上类图:
在这里插入图片描述

Logger

而我们的第二部是获取Logger对象
,上面几步已经知道了 LoggerContext是关键那么通过LoggerContext 的getLogger就可以知道如何获取的Logger了
这里的这个方法是LoggerContext类中的

  public final Logger getLogger(final String name) {
//第一点这个名词不能是空的,通常都是类名或者配置文件中的appender的名字
        if (name == null) {
            throw new IllegalArgumentException("name argument cannot be null");
        }

        // if we are asking for the root logger, then let us return it without
        // wasting time
        
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }

        int i = 0;
        Logger logger = root;

        // check if the desired logger exists, if it does, return it
        // without further ado.
       //判断是否已经有了。比如说某个类的
        Logger childLogger = (Logger) loggerCache.get(name);
        // if we have the child, then let us return it without wasting time
        //有就直接返回
        if (childLogger != null) {
            return childLogger;
        }
//下面是创建Logger的关键
        // if the desired logger does not exist, them create all the loggers
        // in between as well (if they don't already exist)
        String childName;
        //自旋的开始
        while (true) {
        //对类名的处理主要是内部类。大概知道啥意思就好了。内部类不都带$的么。这里就是把这个内部类分开获取类名
            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
            if (h == -1) {
                childName = name;
            } else {
                childName = name.substring(0, h);
            }
            // move i left of the last point
            i = h + 1;
            //加锁进行初始化
            synchronized (logger) {
                childLogger = logger.getChildByName(childName);
                if (childLogger == null) {
                //这里这个create 方法作为主要的方法。
                //而Logger 需要注意的是这对象还是一个树。一级一级的。这里不但创建了logger的对象还需要维护一下树的长度什么的。
                    childLogger = logger.createChildByName(childName);
                    loggerCache.put(childName, childLogger);
                    incSize();
                }
            }
            logger = childLogger;
            if (h == -1) {
                return childLogger;
            }
        }
    }

这时看看Logger中的 createChildByName 方法

    Logger createChildByName(final String childName) {
        int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
        if (i_index != -1) {
            throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
                            + " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
        }

        if (childrenList == null) {
            childrenList = new CopyOnWriteArrayList<Logger>();
        }
        Logger childLogger;
        //就是维护内部的一个列表其实,这个newlogger是关键了。
        childLogger = new Logger(childName, this, this.loggerContext);
        childrenList.add(childLogger);
        childLogger.effectiveLevelInt = this.effectiveLevelInt;
        return childLogger;
    }
    //具体的 new Logger 反而没有什么过多的内容
      Logger(String name, Logger parent, LoggerContext loggerContext) {
        this.name = name;
        this.parent = parent;
        this.loggerContext = loggerContext;
    }

具体执行的log.info到底干了些什么呢?

   public void info(String msg) {
        filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, msg, null, null);
    }
    private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                    final Throwable t) {

        final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);

        if (decision == FilterReply.NEUTRAL) {
            if (effectiveLevelInt > level.levelInt) {
                return;
            }
        } else if (decision == FilterReply.DENY) {
            return;
        }
//其中这句为关键写日志代码
        buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
    }
    //构建loggingevent 一个事件对象。并调用 appenders进行输出。
   private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                    final Throwable t) {
                    // 构造日志事件对象
        LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
        le.setMarker(marker);
        //派发事件
        callAppenders(le);
    }
// 派发事件
  public void callAppenders(ILoggingEvent event) {
  
        int writes = 0;
  // 可以看出这个是树形结构。从当前点一直往父类找并输出。
  //那么这个主要方法就是appendLoopOnAppenders 
        for (Logger l = this; l != null; l = l.parent) {
            writes += l.appendLoopOnAppenders(event);
            if (!l.additive) {
                break;
            }
        }
        //要是配置文件中没有appender或者没有加载到appender会执行下面方法
        // No appenders in hierarchy
        if (writes == 0) {
            loggerContext.noAppenderDefinedWarning(this);
        }
    }
//其实这里调用了就是这个方法。
    private int appendLoopOnAppenders(ILoggingEvent event) {
        if (aai != null) {
            return aai.appendLoopOnAppenders(event);
        } else {
            return 0;
        }
    }
    //关于aai对象可以看到再logger声明的时候就有了
      transient private AppenderAttachableImpl<ILoggingEvent> aai; 
      //是一个AppenderAttachableImpl 的实现类而
    // 而通过aai.appendLoopOnAppenders(event);方法可以看到其实AppenderAttachableImpl 里面保存了全部配置文件中的appender而再操作时对他们全部进行添加
    
    

AppenderAttachableImpl

中的appendLoopOnAppenders 方法

  //可以看到循环调用了不同的Appender的进行了doAppend方法
   public int appendLoopOnAppenders(E e) {
        int size = 0;
        final Appender<E>[] appenderArray = appenderList.asTypedArray();
        final int len = appenderArray.length;
        for (int i = 0; i < len; i++) {
            appenderArray[i].doAppend(e);
            size++;
        }
        return size;
    }

对于appender主要看一下OutputStreamAppender 就知道Append都干了些什么了

OutputStreamAppender

protected void append(E eventObject) {
        if (!isStarted()) {
            return;
        }
        subAppend(eventObject);
    }
protected void subAppend(E event) {
        if (!isStarted()) {
            return;
        }
        try {
            // this step avoids LBCLASSIC-139
            if (event instanceof DeferredProcessingAware) {
                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
            }  
            //这个encode方法就是再输出前干的最后一件事情了吧。看看这个encoder
            byte[] byteArray = this.encoder.encode(event);
            //可以看到这个就是输出日志了。
            writeBytes(byteArray);

        } catch (IOException ioe) {
            // as soon as an exception occurs, move to non-started state
            // and add a single ErrorStatus to the SM.
            this.started = false;
            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
        }
    }

那么这里的 this.encoder.encode(event); 就成为了一个点,需要知道这个encoder是怎么来的了
再这个类里面还有一段初始化的代码

//大致可以判断出来在加载完appender后还对layour也进行了装载而且这个默认的encoder就是LayoutWrappingEncoder 这个类
public void setLayout(Layout<E> layout) {
        addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
        addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
        addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
        LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
        lwe.setLayout(layout);
        lwe.setContext(context);
        this.encoder = lwe;
    }

LayoutWrappingEncoder

这个类的encod方法

   public byte[] encode(E event) {
   //原来这个方法调用的是 OutputStreamAppender  中setleyout中传入的leyout进行的操作。
        String txt = layout.doLayout(event);
        //然后调用内部方法转成字节数组
        return convertToBytes(txt);
    }

通过debug发现了一个最简单的配置里面的layout是 PatternLayout这个类对象。

PatternLayout

这个不贴代码了。里面标明了再配置文件中各种配置对应的解析方法,这里只看一下我关系的打印调用类的那个方法

  defaultConverterMap.put("C", ClassOfCallerConverter.class.getName());
        defaultConverterMap.put("class", ClassOfCallerConverter.class.getName());

给的都是ClassOfCallerConverter 这个类。进去看看。

ClassOfCallerConverter

 protected String getFullyQualifiedName(ILoggingEvent event) {
        StackTraceElement[] cda = event.getCallerData();
        if (cda != null && cda.length > 0) {
            return cda[0].getClassName();
        } else {
            return CallerData.NA;
        }
    }

这个类方法其实调用的是这个loggingevent对象本身的getCallerData方法来实现的。反过来看看这个

LoggingEvent

public StackTraceElement[] getCallerData() {
        if (callerDataArray == null) {
            callerDataArray = CallerData
                            .extract(new Throwable(), fqnOfLoggerClass, loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
        }
        return callerDataArray;
    }
   //这里CallerData.extract  作为工具类的就是用Throwable对象中获取堆栈然后再 ClassOfCallerConverter.getFullyQualifiedName 中获取第一个就是调用的类名了。

总结一下流程。

其中牵扯到很多的类而且关于appender 与 layout的读取与设置都没有再这里说出来。但是对于日志的使用和加载和输出整体流程大概是知道了。
就是先创建类工厂,再创建类工厂的时候就把配置什么都加载了。然后获取logger对象。而调用logger的输出时,则是创建了一个loggingevent的对象,在下来就是对着这个event一顿的格式化。最后转字节数组输出。
经过阅读。我们知道了logback支持的配置文件类型。不光是xml
也知道了再appender中layout中那些配置都是如何实现的。以及logger的数据模型是树状的等等了。
在说一下我需求的实现。其实就是再调用Appender的时候把我自己的layout设置进去。而我的layout只用把那个类名称处理的地方ClassOfCallerConverter这个类获取其中的栈的第二个类名就是我实际调用的的地方了。那么这就明白了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值