日志框架

前言

日志对于一个系统的重要性不言而喻。运行时调试,线上问题排查。java经过多年的发展,SLF4J框架已经占据了半边江山了。

SLF4J概述

SLF4J(Simple logging Facade for Java),意思为简单日志门面,它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接口方法进行调用即可,由于它只是一个接口,并不是一个具体的可以直接单独使用的日志框架,所以最终日志的格式、记录级别、输出方式等都要通过接口绑定的具体的日志系统来实现,这些具体的日志系统就有log4j,logback,java.util.logging等,它们才实现了具体的日志系统的功能。

slf4j绑定原理

slf4j-api是如何绑定通过接口绑定的具体的日志系统来实现的呢?先从以下代码查看

//使用slf4j的常成代码
org.slf4j.Logger LOG = LoggerFactory.getLogger("some-logger-name");


//根据LoggerFactory.getLogger深入
//LoggerFactory::getILoggerFactory
//LoggerFactory::performInitialization
//LoggerFactory::bind

private final static void bind() {
   
      //查询路径下所有的org/slf4j/impl/StaticLoggerBinder.class
      Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
      //选择路径下的第一个StaticLoggerBinder进行加载
      StaticLoggerBinder.getSingleton();
}

可见slf4j-api利用《类加载机制》来绑定具体的日志系统实现,我们只要简单的类就可以轻松绑定

package org.slf4j.impl;

import org.slf4j.ILoggerFactory;
import org.slf4j.spi.LoggerFactoryBinder;

public class StaticLoggerBinder implements LoggerFactoryBinder {

    public static StaticLoggerBinder   getSingleton(){
        return new StaticLoggerBinder();
    }

    @Override
    public ILoggerFactory getLoggerFactory() {
        return null;
    }

    @Override
    public String getLoggerFactoryClassStr() {
        return "yoyoyo";
    }
}

Logbak原理

logback由三个部分组成,logback-core, logback-classic, logback-access。其中logback-core是其他两个模块的基础。logback一般不能单独使用需要和slf4j配合使用,slf4j定义日志接口,具体实现由logback提供。

logback加载原理

根据slf4j绑定原理,在logback-classic包中发现StaticLoggerBinder

// slf4j会在classpath中寻找org/slf4j/impl/StaticLoggerBinder.class
// 静态调用SINGLETON.init()
// 上下文初始化器 new ContextInitializer(defaultLoggerContext).autoConfig() 

public void autoConfig() throws JoranException {

        // logback也会在classpath中寻找配置文件,
        // 先找logback.configurationFile
        // 没有则找logback.groovy,
        // 若logback.groovy也没有,则找logback-test.xml,
        // 若logback-test.xml还是没有,则找logback.xml
        URL url = findURLOfDefaultConfigurationFile(true);
        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);
            }
        }
}

logback 支持采用 xml、grovy 和 SPI 的方式配置文件后面分析下xml 文件配置的方式。logback 依赖于 Joran(一个成熟的,灵活的并且强大的配置框架 ),本质上是采用 SAX 方式解析 XML。

1. AppenderAction解析appender标签,将appender放入appenderBag(hashmap)
2. LoggerAction解析logger标签,根据name创建logger对象,并建立父子关系
3. AppenderRefAction解析appender-ref标签,将appender绑定logger

其中logger的父子级创建如下所示

//LoggerAction.begin
//loggerContext.getLogger(loggerName)
//1. 如果获取的是 root logger,直接返回;
//2. 如果获取的是 loggerCache 中缓存的 logger,直接返回;
//3. 循环获取 logger name 中包含的所有 logger,如果不存在就创建并放入缓存;
//4. 返回 logger name 对应的 logger。
public final Logger getLogger(final String name) {
   
        // 如果获取的是root logger,直接返回
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }

        int i = 0;
        Logger logger = root;

        // 在loggerCache中缓存着已经创建的logger,如果存在,直接返回
        Logger childLogger = (Logger) loggerCache.get(name);
        if (childLogger != null) {
            return childLogger;
        }

        // 如果还找不到,就需要创建
        // 注意,要获取以cn.zzs.logback.LogbackTest为名的logger,名为cn、cn.zzs、cn.zzs.logback的logger不存在的话也会被创建
        // 例 cn.zzs 的父亲是 cn
        // cn的父亲是root
        String childName;
        while (true) {
            // 从起始位置i开始,获取“.”的位置
            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
            // 截取logger的名字
            if (h == -1) {
                childName = name;
            } else {
                childName = name.substring(0, h);
            }
            // 修改起始位置,以获取下一个“.”的位置
            i = h + 1;
            synchronized (logger) {
                // 判断当前logger是否存在以childName命名的子级
                childLogger = logger.getChildByName(childName);
                if (childLogger == null) {
                    // 通过当前logger来创建以childName命名的子级
                    childLogger = logger.createChildByName(childName);
                    // 放入缓存
                    loggerCache.put(childName, childLogger);
                    // logger总数量+1
                    incSize();
                }
            }
            // 当前logger修改为子级logger
            logger = childLogger;
            // 如果当前logger是最后一个,则跳出循环
            if (h == -1) {
                return childLogger;
            }
        }
}

logback日志输出

代码中关于日志的使用非常简单

//如果配置文件中"com.LogTest",通过缓存就可以拿到配置文件中的logger
//如果不存在,使用默认配置创建,com.LogTest-> com -> root的logger
1. Logger logger = LoggerFactory.getLogger("com.LogTest");
//使用logger打印日志
2. logger.debug("yoyo");


//logger.debug
//Logger.filterAndLog_0_Or3Plus
//主要功能是过滤日志,发现日想等级<logger想要的等级,我就过滤
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                    final Throwable t) {
        // 使用TurboFilter过滤当前日志,判断是否通过
        final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);
        //  返回NEUTRAL表示没有TurboFilter,即无需过滤
        if (decision == FilterReply.NEUTRAL) {
            // 如果需要打印日志的等级小于有效日志等级,则直接返回
            if (effectiveLevelInt > level.levelInt) {
                return;
            }
        } else if (decision == FilterReply.DENY) {
            // 如果不通过,则不打印日志,直接返回
            return;
        }
        // 创建LoggingEvent
        buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
    }


//logger.buildLoggingEventAndAppend
//Logger.callAppenders
//additive = false 向父级Appender传递
public void callAppenders(ILoggingEvent event) {
        int writes = 0;
        // 通知LoggingEvent给当前logger的持有的和继承的appender处理日志事件
        for (Logger l = this; l != null; l = l.parent) {
            writes += l.appendLoopOnAppenders(event);
            // 如果设置了logger的additivity=false,则不会继续查找父级的appender
            // 如果没有设置,则会一直查找到root logger
            if (!l.additive) {
                break;
            }
        }
        // 当前logger未设置appender,在控制台打印提醒
        if (writes == 0) {
            loggerContext.noAppenderDefinedWarning(this);
        }
}

 

主要参考

Java日志框架SLF4J和log4j以及logback的联系和区别

日志那些事儿——Logback源码解析

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值