深入理解日志框架门面slf4j

Log日志框架学习-Slf4j

什么是slf4j

The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.

slf4j的全称是Simple Logging Facade for Java, 也就是简单的日志门面框架,只提供接口,没有具体的实现。具体的日志功能由具体的日志框架去实现,比如java.util.logging, logback, log4j,slf4j-simple等。

使用Slf4j有一个很大的好处是,当你想切换其他日志框架的时候,原来的代码是几乎不用改的

看一个简单的例子

引入相关的jar包,slf4j-api-xxx.jar 和 slf4j-simple-xxx.jar (这个是slf4j最简单的日志框架实现了)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zzh.log</groupId>
    <artifactId>log</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Test.class);
        logger.info("hahaahah");
    }
}

上面就是最简单的例子了。

认识一些基础的类
  1. Logger
    Logger是一个接口,包含了所有的打印方法,像trace()、debug()、info()、warn()、error()等,是日志框架的主要入口。
    LoggerLevel包括ERROR、WARN、INFO、DEBUG、TRACE
    LoggerLevel有什么作用呢,通常默认是日志级别是DEBUG,那就会过滤logger.trace()的日志输出;如果就日志级别设置为INFO,那就会过滤logger.debug()和logger.trace()的日志输出,一次类推,ERROR是最高级别,将会过滤出logger.error()所有的日志打印。
  2. ILoggerFactory
public interface ILoggerFactory {
    public Logger getLogger(String name);
}

实现ILoggerFactory这个接口,用来生成具体的Logger实现。

  1. LoggerFactory
    这个类会去类目录下去查找这个org/slf4j/impl/StaticLoggerBinder,并获得对应的ILoggerFactory, 再由ILoggerFactory去拿到对应的Logger。
  2. StaticLoggerBinder
    所有实现slf4j日志门面的日志框架必须实现这个类,因为在LoggerFactory这个类中写死从StaticLoggerBinder这个类中拿到ILoggerFactory。
  3. Marker 中文不好翻译,Marker是日志输出更加细粒度的控制,个人理解,其实Marker就是一个过滤器,在日志级别前,将一些不需要的日志输出给过滤掉。
    如果日志框架需要实现Marker的功能,需要实现MarkerFactoryBinder
  4. MDC全称是"Mapped Diagnostic Context“,线程映射表。实际是日志框架维护的一个全局的map,存储key-value,可以将键值的信息插入到日志信息中。
public class SimpleMDC {
  static public void main(String[] args) throws Exception {

    // You can put values in the MDC at any time. Before anything else
    // we put the first name
    MDC.put("first", "Dorothy");
    Logger logger = LoggerFactory.getLogger(SimpleMDC.class);
    
   }

}

源码分析

getLogger() :入口

LoggerFactory.getLogger(Test.class); 
    public static Logger getLogger(Class<?> clazz) {
        //1.获取对应的LOgger,接着进去看看
        Logger logger = getLogger(clazz.getName());
        //2. 检查日志器的名称是否匹配
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            }
        }
        return logger;
    }

getLogger :获取Logger

    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory(); //进去继续看
        return iLoggerFactory.getLogger(name);
    }

getILoggerFactory() : 获得ILoggerFactory

    public static ILoggerFactory getILoggerFactory() {
        // INITIALIZATION_STATE 初始化的状态
        // UNINITIALIZED还没初始化,就开始初始化
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    // 把INITIALIZATION_STATE状态设为正在初始化中
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    // 初始化工作
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            //如果初始化成功,则通过StaticLoggerBinder这个类获取对应的ILoggerFactory,这也是上面说到为什么一定要实现StaticLoggerBinder这个接口
            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");
    }

performInitialization():进行初始化工作

    private final static void performInitialization() {
        //绑定
        bind();
        //如果初始化成功则进行版本检查
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }

bind() : 绑定

    private final static void bind() {
        try {
            //存储所有实现了staticLoggerBinder的路径
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                //查找实现了staticLoggerBinder的路径
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                //打印是否有多个实现
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            // 将初始化状态设为成功
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 打印出来真实是将哪一个StaticLoggerBinder实现进行绑定
            reportActualBinding(staticLoggerBinderPathSet);
            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) {
            //这里的异常是找不到StaticLoggerBinder的getSingleton()方法
            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);
        }
    }

** versionSanityCheck()** : 版本检查

    private final static void versionSanityCheck() {
        try {
            //要求的版本
            String requested = StaticLoggerBinder.REQUESTED_API_VERSION;
            // 初始版本不匹配
            boolean match = false;
            //static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" }; 可以匹配的版本
            for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
                //说明版本匹配成功
                if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
                    match = true;
                }
            }
            //匹配不成功,打印错误日志
            if (!match) {
                Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
                                + Arrays.asList(API_COMPATIBILITY_LIST).toString());
                Util.report("See " + VERSION_MISMATCH + " for further details.");
            }
        } catch (java.lang.NoSuchFieldError nsfe) {
            // given our large user base and SLF4J's commitment to backward
            // compatibility, we cannot cry here. Only for implementations
            // which willingly declare a REQUESTED_API_VERSION field do we
            // emit compatibility warnings.
        } catch (Throwable e) {
            // we should never reach here
            Util.report("Unexpected problem occured during version sanity check", e);
        }
    }

findPossibleStaticLoggerBinderPathSet() :查找实现了taticLoggerBinder的路径集合

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        // 用来存储staticLoggerBinder路径的集合
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            //拿到类加载器
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            //STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
            if (loggerFactoryClassLoader == null) {
            //如果为空,说明为当前类加载器为启动类加载器
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                //在类路径下去查找这个类
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            //遍历
            while (paths.hasMoreElements()) {
                // 获取对应的URL资源
                URL path = paths.nextElement();
                // 将URL放入集合LinkedHashSet中
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

reportMultipleBindingAmbiguity() :报告是否有多个绑定冲突

    private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
        //判断Set集合里是否有多个StaticLoggerBinder类的URL
        if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
            Util.report("Class path contains multiple SLF4J bindings.");
            for (URL path : binderPathSet) {
                Util.report("Found binding in [" + path + "]");
            }
            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
        }
    }

reportActualBinding() : 报告实际绑定的StaticLoggerBinder

    private static void reportActualBinding(Set<URL> binderPathSet) {
        // binderPathSet can be null under Android
        if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
            Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
        }
    }

哈,就这样子啦,不正之处,多多指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 最近,新开发的一个项目遇到了一个log4j的配置问题,之前一直没怎么关注过日志框架,借助这个机会,好好了解下Jav...
    漂泊的胡萝卜阅读 1,552评论 2 24
  • 在项目开发过程中,我们可以通过 debug 查找问题。而在线上环境我们查找问题只能通过打印日志的方式查找问题。因此...
    Java架构阅读 3,456评论 2 41
  • 作为Java开发人员,对于日志记录框架一定非常熟悉。而且几乎在所有应用里面,一定会用到各种各样的日志框架用来记录程...
    意识流丶阅读 13,897评论 0 13
  • 对于Java的日志框架,你也许会经常看到这些名词: Log4j、Log4j2 Logback Slf4j JCL ...
    NoahU阅读 3,935评论 0 15
  • 1.SLF4J SLF4J全称 Simple Logging Facade for Java,它为Java下的日志...
    近路阅读 2,411评论 0 3