serverBiatch / teamWiki

a team wiki for serverBiatches!
6 stars 0 forks source link

slf4j和log4j学习分享 #24

Open Joker-Qian opened 9 years ago

Joker-Qian commented 9 years ago

Slf4j与Log4j学习分享

1. slf4j

  首先看的是slf4j, 因为我对slf4j一直很好奇, 虽然看过许多介绍说什么slf4j是日志的门面, 但是我实在不明白 '门面' 是什么意思, 于是先来看它. 在官网下载了最新的 zip 包之后, 解压得到这样的一堆内容:    slf4j包   里面各种jar包一大堆, 经过一系列尝试之后, 知道了要用 slf4j + log4j 需要使用这三个包:   slf4j-api-1.7.12.jar, slf4j-log4j12-1.7.12.jar, log4j-1.2.17.jar   当使用slf4j + log4j之后, 日志配置和原来一模一样( 关于配置见[log4j] ), 但是在获取日志对象时发生了改变,

// 仅用log4j
import org.apache.log4j.Logger;
{
    private static Logger logger = Logger.getLogger(PUtils.class);
}

// 使用了slf4j之后
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
{
    private static Logger logger = LoggerFactory.getLogger(PUtils.class);
}

  查看源码之后得到类图如下: sl4j类图   LoggerFactory这个名字一看就知道肯定是获得Logger对象的工厂类了, 它里面有静态方法 getLogger(name : String) 这个方法其实是调用了 ILoggerFactory 的一个实现类的 getLogger() 方法, 这个具体的实现类就在每个不通的 slf4j-xxxx-1.7.12.jar 中;   在这个jar包中一定会在 org.slf4j.impl 这个路径下面有 StaticLoggerBinder 类, 它继承了api中的 LoggerFactoryBinder 接口, 里面做了一些验证和具体日志实现的初始化工作( 详情见[slf4j + log4j] );   然后有 Log4jLoggerAdapter 实现了api中的 Logger 接口, 它作为了真正进行日志记录工作的 log4j-Logger 的代理; 将日志消息做了简单处理之后, 就调用 log4j-Logger 来的方法进行日志记录;   这样, slf4j的层次就很清楚了:    slf4j结构图      slf4j-api是通用的日志接口, 这些接口有各种不同的实现策略, 每种策略同时是对应的具体的日志工具的代理, 这样我们只要在程序中使用slf4j-api给出的接口, 在根据需要选用代理和日志工具, 同时可以方便切换日志工具;

2. log4j

1.配置

  上回配置相关的介绍一点都没有, 这次一并补充, 在log4j.properties配置文件里, 配置主要分三种, 如图:      配置      虽然rootLogger也属于Logger, 但是rootLogger为必配项, rootLogger作为所有Logger的父亲, 所有的Logger都会继承它的输出源, 通常rootLogger配置为控制台输出:

log4j.rootLogger=trace,console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.Encoding=UTF-8
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d - %m%n

  如果自己配置的Logger并不想继承父Logger的输出源, 可添加下面的配置:

log4j.additivity.YourLogger=false

  配置中Appender的各项参数与选用的日志输出策略相关, 例如:

# 当使用 ConsoleAppender 时, 可以配置 Target 参数
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out

# 当使用 FileAppender 或 DailyRollingFileAppender 时, 需要 File 参数
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=./logs/fileAppender.log

# 当使用 JDBCAppender 时, 需要配置数据库相关参数
log4j.appender.MysqlJDBC=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.MysqlJDBC.URL=jdbc:mysql://localhost:3306/bangzi_test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
log4j.appender.MysqlJDBC.driver=com.mysql.jdbc.Driver
log4j.appender.MysqlJDBC.user=root
log4j.appender.MysqlJDBC.password=
log4j.appender.MysqlJDBC.sql=INSERT INTO logging (log_date, log_level, location, message) VALUES ('%d{yyyy-MM-dd HH:mm:ss}', '%-5p', '%C,%L', '%m')

  最常用的就是 DailyRollingFileAppender 了, 它的配置如下:

log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.Encoding=UTF-8                              // 设置编码格式
log4j.appender.file.datePattern='.'yyyyMMdd                     // 日志文件更新周期如 '.'yyyyMMddHHmm 表示每分钟更新一个log日志
log4j.appender.file.append=true                                 // 在日志文件末尾追加而不是覆盖
log4j.appender.file.layout=org.apache.log4j.PatternLayout       // 输出格式的实现策略
log4j.appender.file.layout.ConversionPattern=%d - %m%n          // 输出格式
log4j.appender.file.File=./logs/fileAppender.log                // 日志文件

  我尝试了一下JDBCAppender, 日志记录速度简直惊人的慢:

    public static void testMysqlJDBCLogger() {
        Logger logger = RootLogger.getLogger("MysqlJDBC");

        long start = new Date().getTime();
        for (int i = 0; i < 1000; i++) {
            logger.info("fdafdsa");
        }
        long end = new Date().getTime();
        logger.info("use Time : " + (end - start));

        // 2015-10-24 17:40:27,265 - use Time : 13592 ms
    }

  如果换成常用的文件记录, 时间只用了 33 ms , 从网上看资料说一般会为JDBCAppender配置连接池池, 如果使用HicariCP为它管理连接池, 记录速度应该会大大提升(待测).   关于日志输出格式Layout, 它也有许多种实现策略, 为了控制日志输出格式, 一般会采用PatternLayout可以自己指定布局, 关于其它各种布局方式, 以下为网上摘录 :

1.org.apache.log4j.HTMLLayout(以HTML表格形式布局), 
2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式), 
3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串), 
4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

1.HTMLLayout 选项 
        LocationInfo=true:默认值是false,输出java文件名称和行号 
        Title=my app file: 默认值是 Log4J Log Messages.
2.PatternLayout 选项 
        ConversionPattern=%m%n :指定怎样格式化指定的消息。 
3.XMLLayout 选项 
        LocationInfo=true:默认值是false,输出java文件和行号

  在自己选用布局格式时, 各种占位符含义如下:

-X号: X信息输出时左对齐; 
%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%r: 输出自应用启动到输出该log信息耗费的毫秒数 
%c: 输出日志信息所属的类目,通常就是所在类的全名 
%t: 输出产生该日志事件的线程名 
%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中
%%: 输出一个"%"字符 
%F: 输出日志消息产生时所在的文件名称 
%L: 输出代码中的行号 
%m: 输出代码中指定的消息,产生的日志具体信息 
%n: 输出一个回车换行符

可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如: 
1) c:指定输出 category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。 
2)%-20c:指定输出 category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。 
3)%.30c:指定输出 category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。 
4) .30c: 如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉

2.API

  首先当然需要获取日志对象:

    Logger logger1 = LogManager.getLogger("Hawk");
    Logger logger2 = Logger.getLogger("Hawk");
    Logger logger3 = RootLogger.getLogger("Hawk");

  Logger对外暴露了很多接口, 除了记录日志的接口如 info(), log(), l7dlog() ; 还能获得改日志对象的一些信息, 如 getName(), getLevel(), getParent() ; 甚至还能用 addAppender(), removeAppender() 手动添加或删除日志的输出源, 如果自己包装了日志事件 LoggingEvent 能够直接调用 callAppenders() 来记录日志; 各种接口看log4j的API就会很明白;   在slf4j中, Logger的代理在记录日志时, 调用的都是 log() 接口, 使用了Adapter的FQCN;   l7dlog() 这个接口我没有用过, 在使用它的之前, 必须先为该logger设置一个 ResourceBundle , 具体用处没有深入了解;

3.类图时序图

  结合上面的配置和API再来看一看类图, 核心是 Category, 它是 Logger 的父类, 它的对象里会持有配置给它的所有 Appender 的一个管理器的引用; 还有所有Logger的仓库 LoggerRepository; Appender 又会聚合 Layout;    log4j类图

  log4j的初始化时序图如下:   log4j初始时序图   

3. slf4j + log4j

  当选用log4j作为slf4j的具体日志实现时, 中间加入了代理层, 类图见[slf4j], 我们使用slf4j获得的 Logger 对象其中的接口就很简单了, 只有两种:    1. 用来记录日志, 例如: info(String format, Object arg), debug(String msg);    2. 用来判断对象是否记录该level的日志, 例如: isInfoEnabled(), isWarnEnabled();   相比Log4j的Logger, 这个实在是清晰舒爽多了;   而且在单独使用log4j时, 一般会先进行一下判断, 当等级不够时, 会少做许多日志内容处理操作, 提高一些效率;

    if(logger.isDebugEnabled())
        logger.debug(msg);

  而使用slf4j的接口就不必这样了, 它自己会先检测日志级别, 然后才拼接字符串;   同时slf4j使用 {} 作为占位符, 省的使用 + 连接字符串或是 String.format(format, Object...args), 并且能够自动解析参数中的数组, 打印其中的元素; 这个优点我个人觉得还好, 因为在项目中一般都会对日志进行一层封装, 这些操作其实都可以自己来实现;      使用slf4j后, 日志记录时调用过程如下:   slf4j日志时序图   

4. 总结

  待续

crazyjohn commented 9 years ago

类图时序图需要介绍啊,要不别人怎么看懂?是否有更详尽的介绍文档?

crazyjohn commented 9 years ago

还有一个框架来说,首先明白如何使用是最重要的,马上拿来就说源码,对自己对别人都不是一个更好的理解方式,因为看官听众根本脑中就无上下文

crazyjohn commented 9 years ago

还有就是不必分要苛求自己从架构角度去说一个东西,因为目前来说能力限制,自身本身就没理解到那个程度,那么就务实一步步来,先把使用说明搞好,自己懂怎么用,可以工程化,用起来有什么优点缺点,然后把使用经验分享给别人,自己懂了才能身教给别人。

Joker-Qian commented 9 years ago

嗯, 了解了, 今天就改正一下文档.