netty4----日志框架的检查
https://segmentfault.com/a/1190000005797595
Netty是一个简化Java NIO编程的网络框架。就像人要吃饭一样,框架也要打日志。
Netty不像大多数框架,默认支持某一种日志实现。相反,Netty本身实现了一套日志机制,但这套日志机制并不会真正去打日志。相反,Netty自身的日志机制更像一个日志包装层。
日志框架检测顺序
Netty在启动的时候,会自动去检测当前Java进程的classpath下是否已经有其它的日志框架。
检查的顺序是:
先检查是否有slf4j,如果没有则检查是否有Log4j,如果上面两个都没有,则默认使用JDK自带的日志框架JDK Logging。
JDK的Logging就不用费事去检测了,直接拿来用了,因为它是JDK自带的。
注意到虽然Netty支持Common Logging,但在Netty本文所用的4.10.Final版本的代码里,没有去检测Common Logging,即使有支持Common Logging的代码存在。
日志框架检测细节
在Netty自身的代码里面,如果需要打日志,会通过以下代码来获得一个logger,以io.netty.bootstrap.Bootstrap这个类为例,读者可以翻开这个类瞧一瞧。
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);要知道Netty是怎么得到logger的,关键就在于这个InternalLoggerFactory类了,可以看出来,所有的logger都是通过这个工厂类产生的。
翻开InternalLoggerFactory类的代码,可以看到类中有一个静态初始化块
    private static volatile InternalLoggerFactory defaultFactory;
    static {
        final String name = InternalLoggerFactory.class.getName();
        InternalLoggerFactory f;
        try {
            f = new Slf4JLoggerFactory(true);
            f.newInstance(name).debug("Using SLF4J as the default logging framework");
            defaultFactory = f;
        } catch (Throwable t1) {
            try {
                f = new Log4JLoggerFactory();
                f.newInstance(name).debug("Using Log4J as the default logging framework");
            } catch (Throwable t2) {
                f = new JdkLoggerFactory();
                f.newInstance(name).debug("Using java.util.logging as the default logging framework");
            }
        }
        defaultFactory = f;
    }  Javaer们都知道,类的初始化块会在类第一次被使用的时候执行。那么什么时候称之为第一次被使用呢?比如说,静态方法被调用,静态变量被访问,或者调用构造函数。
当调用InternalLoggerFactory.getInstance(Bootstrap.class)之前,上面的静态块会被调用,而Netty对于当前应用所使用的日志框架的检测,就是在这短短的20几行代码里面实现。
首先从代码整体上可以看到,一个try-catch,在catch里面又嵌套了一个try-catch,这正好体现了日志框架的检测顺序:先检测SLF4J,后检测Log4J,都没有的话,就直接使用JDK Logging
检测SLF4J
在f = new Slf4JLoggerFactory(true);这里开始检测SLF4J是否存在。
public class Slf4JLoggerFactory extends InternalLoggerFactory {
    public Slf4JLoggerFactory() {
    }
    Slf4JLoggerFactory(boolean failIfNOP) {
        assert failIfNOP; // Should be always called with true.
        // SFL4J writes it error messages to System.err. Capture them so that the user does not see such a message on
        // the console during automatic detection.
        final StringBuffer buf = new StringBuffer();
        final PrintStream err = System.err;
        try {
            System.setErr(new PrintStream(new OutputStream() {
                @Override
                public void write(int b) {
                    buf.append((char) b);
                }
            }, true, "US-ASCII"));
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
        try {
            if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory) {
                throw new NoClassDefFoundError(buf.toString());
            } else {
                err.print(buf.toString());
                err.flush();
            }
        } finally {
            System.setErr(err);
        }
    }
    @Override
    public InternalLogger newInstance(String name) {
        return new Slf4JLogger(LoggerFactory.getLogger(name));
    }
}  在这里可以看到Slf4JLoggerFactory是InternalLoggerFactory的一个子类实现。
如果应用的classpath下存在slf4j相关的jar包,那么当slf4j的日志框架初始化的时候,如果产生了什么错误,将会通过System.err输出;
对于Netty来讲,即使slf4j初始化失败,它也不愿让用户看到错误输出,因为对netty来说,slf4j初始化失败并不代表netty不能选择其它日志框架;
所以可以从上面代码中看到,一开始先把System.err给替换掉,让err输出被重定向到一个StringBuffer,如下代码所示:
        // SFL4J writes it error messages to System.err. Capture them so that the user does not see such a message on
        // the console during automatic detection.
        final StringBuffer buf = new StringBuffer();
        final PrintStream err = System.err;
        try {
            System.setErr(new PrintStream(new OutputStream() {
                @Override
                public void write(int b) {
                    buf.append((char) b);
                }
            }, true, "US-ASCII"));
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }我们已经明白上面这段代码,就是为了重定向err输出,不让用户轻易看到。接下来看这些代码:
        try {
            if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory) {
                throw new NoClassDefFoundError(buf.toString());
            } else {
                err.print(buf.toString());
                err.flush();
            }
        } finally {
            System.setErr(err);
        }首先可以看到一个try-finally结构,finally块里把System.err复位了,也就是说在初始化SLF4J之后,无论发生什么事,都应该把System.err复位。
接下来看try块里面的代码:
            if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory) {
                throw new NoClassDefFoundError(buf.toString());
            } else {
                err.print(buf.toString());
                err.flush();
            }解释这些代码之前,我们先要认识到,SLF4J其实是一个日志门面(facade),它可以充当Log4j, Logback等日志框架的包装器。因此你的应用除了要有slf4j的依赖包,还要有其它具体的日志实现框架的依赖。例如下面是我的maven依赖,依赖了slf4j还有Logback。
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>  如果只有slf4j,而没有logback,那么LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory就会为true,然后代码就会抛出NoClassDefFoundError。
如果连slf4j本身都没有呢?那么运行到LoggerFactory.getLoggerFactory()就已经抛出异常了,因为找不到这个LoggerFactory类。
以上便是检测SLF4J的整个过程。
检测Log4J
如果需要检测Log4J,则说明检测不到SLF4J的存在,或者是SLF4J不可以使用。
回到InternalLoggerFactory代码里:
        try {
            f = new Slf4JLoggerFactory(true);
            f.newInstance(name).debug("Using SLF4J as the default logging framework");
            defaultFactory = f;
        } catch (Throwable t1) {
            try {
                f = new Log4JLoggerFactory();
                f.newInstance(name).debug("Using Log4J as the default logging framework");
            } catch (Throwable t2) {
                f = new JdkLoggerFactory();
                f.newInstance(name).debug("Using java.util.logging as the default logging framework");
            }
        }  Log4J的检测很简单,很直接,直接在newInstance()方法里加载org.apache.log4j.Logger;类,如果加载不到,直接抛异常,然后转而直接使用JDK Logging。
public class Log4JLoggerFactory extends InternalLoggerFactory {
    @Override
    public InternalLogger newInstance(String name) {
        return new Log4JLogger(Logger.getLogger(name));
    }
}  兼容性
日志级别
Netty的内部日志机制也自定义了日志打印级别,像日志的layout或者appender,则没有自己定义,完全交给底层的日志框架去做。
public enum InternalLogLevel {
    /**
     * 'TRACE' log level.
     */
    TRACE,
    /**
     * 'DEBUG' log level.
     */
    DEBUG,
    /**
     * 'INFO' log level.
     */
    INFO,
    /**
     * 'WARN' log level.
     */
    WARN,
    /**
     * 'ERROR' log level.
     */
    ERROR
}  这里会面临日志打印级别的兼容性问题,因为SLF4J,Log4J,以及JDK Logging,都有自己的日志打印级别,比如说JDK Logging,它的日志打印级别是这样的:
- SEVERE (highest value) 
- WARNING 
- INFO 
- CONFIG 
- FINE 
- FINER 
- FINEST (lowest value) 
不仅数目对不上,而且名称也没对上。Netty采用的方式是,按级别的高低来匹配,比如Netty的DEBUG将会对应到JDK的FINE,以此做到级别的对应关系和兼容性。
消息格式化
SLF4J的Logger会有这样一种打日志的方式,采用占位的方式,举个例子:、
logger.info("Hello, I m {}, I m the president of {}","Obama","America");上面这行代码的日志输出结果是:
Hello, I m Obama, I m the president of America可以看到,{}大括号是一个占位符,其内容将会被后面的参数所代替。
但Log4J和JDK Logging并不支持这种占位的日志打印方式,因此Netty又自己搞了一下,让它的InternalLogger可以以占位的方式格式化日志输出信息。
详情可以参考io.netty.util.internal.logging.MessageFormatter这个类,到这里就不再展开了。
netty4----日志框架的检查的更多相关文章
- SLF4J其实只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖Log4j、logback等日志框架的。
		小结: 1.加层: 每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性. 为了解决这个问题,就是在日志框架和应用程序之间架设一个 ... 
- 日志框架 log4j2 全解析
		概述 logging翻译为日志记录 那问题是什么是日志? 日志实际上是日记的一种,用于记录某个时间点发生了什么事情,比如大学老师的教学日志,工作日志等 为什么要记录日志? 在实际生活中记录日志主要为了 ... 
- 日志框架之2 slf4j+logback实现日志架构 · 远观钱途
		如何从缤纷复杂的日志系统世界筛选出适合自己的日志框架以及slf4j+logback的组合美妙之处?此文可能有帮助 logback介绍 Logback是由log4j创始人设计的另一个开源日志组件,官方网 ... 
- 解读ASP.NET 5 & MVC6系列(9):日志框架
		框架介绍 在之前的.NET中,微软还没有提供过像样的日志框架,目前能用的一些框架比如Log4Net.NLog.CommonLogging使用起来多多少少都有些费劲,和java的SLF4J根本无法相比. ... 
- Java日志框架:SLF4J,Common-Logging,Log4J,Logback说明
		Log4j Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务 器.NT的事件记录器.UNIX Syslog守护进程等 ... 
- java日志框架slf4j与log4j
		日志记录自然是非常重要的,但恐怕能记住slf4j与log4j等日志框架配置的人就很少了,这个东西不难,只是配置好后很少会去动它,开发新项目一般也是从其他项目拷贝,或者参照文档 废话不多说,先说log4 ... 
- Moon转告给你一个比Log4net更好日志框架--TracerX Logger 及其对应的日志查看器
		一.介绍 TracerX logger是一个易于上手,且拥有众多高级特性的.NET日志框架. 它能够发送输出结果到多目的地(循环文件.事件日志等....).它也能生成文本和二进制文件.它拥有一个强大的 ... 
- lombok+slf4j+logback  SLF4J和Logback日志框架详解
		maven 包依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lomb ... 
- log4net 日志框架的配置
		log4net 日志框架的配置——静态文件(一) 添加对log4net程序集的引用 选择程序集文件添加引用即可,需要注意的是需要添加相应程序版本的程序集,如果你的应用是基于.netFramework2 ... 
随机推荐
- PHP 使用header函数设置HTTP头的示例方法 表头 (xlsx下载)
			转载 http://justcoding.iteye.com/blog/601117/ //定义编码header( 'Content-Type:text/html;charset=utf-8 '); ... 
- tesseract .net 中使用历程
			最近在看文字识别的实例,也查询很多文章,最后还是选定开源的引擎(tesseract3.0.1) 最开始找到的是用微软Office的一个组件实现的,个人感觉不是我想要的(要开源啊才是王道) http:/ ... 
- HDU 2187 - 悼念512汶川大地震遇难同胞——老人是真饿了 - [大水题]
			讲真,这么水的题,我都不怎么好意思扔到博客上来,但是没办法啊,我总得证明一下今天上午我不是在寝室里瞎玩浪费掉的…… 思路就是,把米按单价从小到大排个序,便宜的买的越多越好,直到钱花光为止……我真的都不 ... 
- java重载和重载的区别
			重载 public class A{ public void test(){} public void test(int num){} public void test(Str ... 
- python 线程,GIL 和 ctypes(转)
			原文:http://zhuoqiang.me/python-thread-gil-and-ctypes.html GIL 与 Python 线程的纠葛 GIL 是什么东西?它对我们的 python 程 ... 
- 有关线程安全的探讨--final、static、单例、线程安全
			我的代码中已经多次使用了线程,然后还非常喜欢使用据说是线程不安全的静态方法,然后又看到很多地方最容易提的问题就是这个东西线程不安全 于是我不免产生了以下几个亟待解决的问题: 什么样的代码是天生线程 ... 
- SqlServer 凭据
			一.理解索引的结构 索引在数据库中的作用类似于目录在书籍中的作用,用来提高查找信息的速度.使用索引查找数据,无需对整表进行扫描,可以快速找到所需数据.微软的SQL SERVER提供了两种索引:聚集索引 ... 
- 虚拟机中实现Linux与Windows之间的文件传输
			虚拟机中实现Linux与Windows之间的文件传输 标签: linux 2016年06月28日 11:17:37 2092人阅读 评论(0) 收藏 举报 分类: linux(2) 一.配置环 ... 
- [py]django前台处理后端返回的各类数据
			参考 要完成的任务 返回str 返回list 返回arr 前端遍历 关键字 if for语句处理str list dict - 遍历字典 for语句 {% for key, value in info ... 
- end=‘’
			print('----------------') a = ['aa','bb','cc','dd','ee'] for i in range(len(a)): print(i,a[i])#默认换行 ... 
