简介

  • 平时控制台输出的那些信息流就叫日志,它记录了程序调试、运行时发生的变化。
  • slf4j不是具体的日志框架,而是一系列的日志接口,也即是日志框架的抽象log4j和logback才是实现了的日志框架。
  • logback和log4j非常相似,但是logback在一些关键执行路径上性能提升10倍以上,而且初始化内存也更小。
  • 它们可以单独使用,也可以绑定slf4j一起使用。单独使用,分别调用自己的方法来输出日志信息。绑定使用,调用slf4j的api,具体使用与底层日志框架无关,日志框架提供的只是一些配置。
  • 推荐绑定,假设项目中已经使用了logback,而我们此时加载了一个使用log4j的类库进来,那我们这时候就需要维护两个日志框架,很麻烦。而使用绑定的话,由于调用的是抽象层的API,与底层日志框架是无关的,因此可以任意更换日志框架。

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    @Controller
    @RequestMapping("/")
    public class LoginController {
        private static final Logger logger=LoggerFactory.getLogger(LoginController.class);
    }
    
  • 要进行绑定的话就要引入依赖

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>
    
  • 包含以下必要jar包

    log4j-1.2.xx.jar
    slf4j-api-x.x.x.jar
    slf4j-log4j12-x.x.x.jar
    
  • <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.7</version>
    </dependency>
    
  • 包含以下必要jar包

    logback-classic-0.9.21.jar(目前已经有1.0.0)
    logback-core-0.9.21.jar(目前已经有1.0.0)
    slf4j-api-1.6.x.jar
    

    log4j2

  • spring 4.2.1开始推荐使用log4j2,即log4j 2.x。
  • 2.3以后的版本要求jdk1.7,2.11.1或更早就已经要求为1.8了
  • 依赖:

    <!--依赖管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-bom</artifactId> <!--引入官方依赖管理文件-->
                <version>2.9.1</version> <!--版本统一定义为2.9.1-->
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependency> <!--log4j2核心包-->
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency> <!--log4j2核心包-->
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency> <!--web项目需要-->
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-web</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency> <!--异步输出配置前置环境支持;
                    没有的话报错:Unable to invoke factory method in class
                    org.apache.logging.log4j.core.async.AsyncLoggerConfig-->
        <groupId>com.lmax</groupId>
        <artifactId>disruptor</artifactId>
        <version>3.4.2</version>
        <scope>runtime</scope>
    </dependency>
    <dependency> <!--用于与slf4j桥接-->
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- slf4j核心包,已包含在com.github.miemiedev:mybatis-paginator中-->
    <!--<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        一般都是匹配这个版本<version>1.7.25</version>
    </dependency>-->
    
  • 使用前需要在classpath(WEB-INFO和resources)下新建一个配置文件,但不支持.properties文件,只能为”.xml”,”.json”或者”.jsn”。
  • 系统选择配置文件的优先级(从先到后):
    • classpath下的名为log4j2-test.json 或者log4j2-test.jsn的文件
    • classpath下的名为log4j2-test.xml的文件
    • classpath下名为log4j2.json 或者log4j2.jsn的文件
    • classpath下名为log4j2.xml的文件
  • 如果写在别的地方,则需要在web.xml中配置路径

    <!-- log4j2配置文件路径 -->
    <context-param>
        <param-name>log4jConfiguration</param-name>
        <param-value>classpath:config/log4j2.xml</param-value>
    </context-param>
    
  • 我们一般默认使用log4j2.xml。默认配置文件:只向控制台打印error级别以上的日志

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>             
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="error">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggersration>
    
  • 配置文件实例

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
    status:指定log4j本身打印日志的级别,只对log4j本身的事件有效,
    off表示不记录,其余的记录有trace,debug,info,warn,error,fatal;
    monitorinterval:指定自动重新配置的监测间隔时间即更改日志配置文件不用重启程),
    单位s最小5s,0或负数表示不检测
    -->
    <configuration status="error"> 
    
        <!-- 常量定义 -->
        <properties>
            <property name="LOG_HOME">
            /home/work/log/${service_name} <!--service_name是maven打包传进来的-->
            </property>
            <property name="FORMAT">
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            </property>
        </properties>
    
        <!--定义所有的appender-->
        <appenders>
            <!--控制台-->
            <Console name="Console" target="SYSTEM_OUT"> <!--target默认即为SYSTEM_OUT-->
                <!--添加过滤器ThresholdFilter,控制台只输出level及以上信息
                onMatch="ACCEPT" onMismatch="DENY"意思是匹配就接受,否则直接拒绝-->
                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
                <!--输出日志的格式;可以直接pattern="S{FORMAT}" -->
                <PatternLayout pattern="[%d{HH:mm:ss.SSS}] [%-5p] %l - %m%n"/>
            </Console>
    
            <!--File:日志文件,打印出所有信息,名为log,下面引用这个appender时直接用log;
            fileName:指定输出日志的目的文件的全路径,log/为项目下log目录,./log/也一样;
            由于append为false,故每次运行日志都被更新,这个也挺有用的,适合临时测试用 -->
            <File name="log" fileName="log/test.log" append="false">
                <PatternLayout pattern="[%d{HH:mm:ss.SSS}] [%-5p] %l - %m%n"/>
            </File>
    
            <File name="ERROR" fileName="logs/error.log">
                <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
                <PatternLayout pattern="[%d{yyyy.MM.dd 'at' HH:mm:ss z}] [%-5p] %l - %m%n"/>
            </File>
    
            <!--RollingFile:定义超过指定大小自动删除旧的创建新的日志文件;
            RollingRandomAccessFile:功能相同,属性也完全一样,只是效率较高-->
            <RollingRandomAccessFile name="RollingFile" fileName="logs/web.log"
                         filePattern="logs/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.zip">
            <!--不要以.gz/.zip结尾文件就不会被压缩,建议为.zip-->
                <PatternLayout pattern="[%d{yyyy-MM-dd 'at' HH:mm:ss z}] [%-5p] %l - %m%n"/>
                <Policies>
                    <!--设置每天打包日志一次-->
                    <!-- TimeBasedTriggeringPolicy:基于时间的滚动策略;
                    interval:integer型,指定多久滚动一次,默认是1hour。
                    指定两次封存动作之间的时间间隔,
                    单位:以日志的命名精度来确定单位,比如yyyy-MM-dd-HH 单位为小时,
                    yyyy-MM-dd-HH-mm 单位为分钟。 -->
                    <!-- modulate:boolean型,说明是否对封存时间进行调制,
                    若modulate=true,则封存时间将以0点为边界进行偏移计算。
                    比如现在是早上3am,interval是4,那么第一次滚动是在4am,
                    接着是8am,12am…而不是7am。 -->
                    <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                    <!--SizeBasedTriggeringPolicy:基于指定文件大小的滚动策略;
                    size:定义每个日志文件的大小每次大小超过size,
                    则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
                    作为存档。单位推荐MB,数字和单位间推荐写个空格-->
                    <SizeBasedTriggeringPolicy size="2 MB"/>
                </Policies>
    
                <!--DefaultRolloverStrategy:指定同一个文件夹下最多有几个日志文件时
                    开始删除最旧的,创建新的(通过max属性,默认7个,超出7个的文件夹将被删除)。
                    compressionLevel:压缩等级,值为0-9,只在filePattern为zip时作用,
                    0表示不压缩,值打包为zip格式,9表示最高压缩比(个人测试1MB能压缩到4KB)-->
                <DefaultRolloverStrategy max="60" compressionLevel="9"/>
            </RollingFile>
        </appenders>
    
        <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效;
        root:根logger;logger:logger-->
        <loggers>
            <!-- 建立一个默认的root的logger,没有定义level的appender则以此为level -->
            <root level="trace">
                <!--指定该日志输出到哪个appender-->
                <appender-ref ref="RollingFile"/>
                <appender-ref ref="Console"/>
                <appender-ref ref="ERROR" />
                <appender-ref ref="log"/>
            </root>
    
            <!-- 异步根logger,跟上面root只能存在一个,不能两个都配置;
            不过log4j2依赖应该要到2.11.1才能使用这个-->
            <!-- 如果日志代码是正常代码逻辑的一部分货程序运行在单核单线程CPU上(这种情况
            使用多线程的就是傻),推荐使用普通logger,如果单纯只是为了记个日志,
            推荐使用异步logger,效能可能会有几十倍的提高。
            <AsyncRoot level="info">
                <AppenderRef ref="consoleAppender" />
            </AsyncRoot>
            -->
    
            <!--logger:单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
            以下是过滤掉spring和mybatis的一些无用的DEBUG信息;
            name:指定适用的类或者类所在包的全路径;
            appender-ref:指定日志输出的appender,默认继承root结点下定义的appender,
            如果指定了,那么会在指定的这appender和root的appender都输出,此时我们可以设置additivity="false",只在指定的appender中输出;但在代码实例定义了false居然没打印任何日志
            ,只有true才有打印,且只打印一次中-->
            <logger name="org.springframework" level="INFO"></logger>
            <logger name="org.mybatis" level="INFO"></logger>
    
            <!-- 定义一个异步logger,可以和普通logger共存 -->
            <AsyncLogger name="com.foo.Bar" level="info" additivity="false"> 
                <AppenderRef ref="AppenderName" />
                <AppenderRef ref="AppenderName1" />
            </AsyncLogger>
        </loggers>
    </configuration>
    
  • 参考文章
  • 参考文章
  • 参考文章
  • 参考文章
  • 涉及异步日志
  • 代码实例:D:/ideaprojects/thz/thz-parent/thz-manager-web/reourrces/config/log4j2.xml

    日志级别

  • 以下是log4j日志级别:
  • log4j定义了8个级别的log,优先级从高到低依次为:
    • OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL
    • log4j只建议用4个级别,ERROR、WARN、INFO、DEBUG

      为什么要打印日志

  • 开发时可以只输出到控制台,但是程序投入生产时就应该打印日志,供维护人员改bug。

    SpringBoot默认日志框架

  • SpringBoot默认使用Logback作为日志框架,默认配置error、warn和info级别的日志。
  • 日志输出内容元素:
    • 时间日期:精确到毫秒
    • 日志级别:ERROR, WARN, INFO, DEBUG or TRACE
    • 进程ID
    • 分隔符:— 标识实际日志的开始
    • 线程名:方括号括起来(可能会截断控制台输出)
    • Logger名:通常使用源代码的类名
    • 日志内容
  • 启用Logback原本需要引入下面依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    
  • 不过由于该依赖包含于spring-boot-starter,而spring-boot-starter包含于spring-boot-thymeleaf,所以只要引入后面其中一个即可。

日志配置

  • 一般都是写在日志配置文件中,很少在application.properties。命名优先推荐为logback-spring.xml(节点)、log4j-spring.xml、log4j2-spring.xml或ldgging.properties(键值对),这样被正确加载。默认放在resources下。
  • 没有配置文件的话会使用系统默认的配置文件。
  • 具体配置细节参见代码实例中的配置文件。
  • 其中logger结点:写了logger节点就要同时在代码里定义logger。
  • 不写logger节点是没有具体到类的日志的,写了之后才会有指定类的日志,一般要求每个controller中都会有一个logger属性。
  • log4j配置

    #输出级别最低为debug级别,后面为输出器名称
    log4j.rootLogger=DEBUG, Console, Tofile, Exception #看网上命名好像都喜欢大写开头
    
    #Console
    log4j.appender.Console=org.apache.log4j.ConsoleAppender
    log4j.appender.Console.layout=org.apache.log4j.PatternLayout # 可以灵活地指定布局模式
    log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n # 灵活指定布局模式
    
    #不是很懂,应该是打印sql语句吧
    log4j.logger.java.sql.ResultSet=INFO
    log4j.logger.org.apache=INFO
    log4j.logger.java.sql.Connection=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
    #File
    log4j.appender.Tofile=org.apache.log4j.DailyRollingFileAppender #每天产生一个日志文件
    log4j.appender.Tofile.layout=org.apache.log4j.PatternLayout
    log4j.appender.Tofile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n #日志格式
    log4j.appender.Tofile.File=logFile.%d{yyyy-MM-dd}.txt #路径,不能在前面加/,识别不了的;这么写就表示项目
    log4j.appender.Tofile.File=${webApp.root}/log_path/logFile.log //webapp根目录
    log4j.appender.Tofile.Append=true #将消息追加到指定文件中,默认为true,false,覆盖
    log4j.appender.Tofile.Encoding=UTF-8 #定义日志编码
    
    #还可以单独打印个异常日志出来
    log4j.appender.Exception = org.apache.log4j.DailyRollingFileAppender
    #当前目录的上一级目录,如果此配置文件在resources下,则当前目录为项目
    log4j.appender.Exception.File = ../logs/springmvc-mybatis-demo_error.log 
    log4j.appender.Exception.Append = true
    log4j.appender.Exception.Threshold = ERROR 
    log4j.appender.Exception.layout = org.apache.log4j.PatternLayout
    log4j.appender.Exception.layout.ConversionPattern = [sspringmvc_mybatis_demo][%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C.%M(%L) | %m%n
    
    • 语法:
log4j.appender.appenderName = fully.qualified.name.of.appender.class 
  log4j.appender.appenderName.option1 = value1 
  … 
  log4j.appender.appenderName.option = valueN 
  
  其中,Log4j提供的appender有以下几种: 
  org.apache.log4j.ConsoleAppender(控制台), 
  org.apache.log4j.FileAppender(文件), 
  org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
  
  org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件
  一般会配合log4j.appender.输出器name.MaxFileSize=500KB定义最大容量,
  超出则会再生成一个原先日志文件name+.1的文件来存放日志,还可以配合
  log4j.appender.OutFile.MaxHistory=30定义最大保留天数), 
  
  org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) 
  
   配置日志信息的格式(布局),其语法为:
  log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class 
  log4j.appender.appenderName.layout.option1 = value1 
  … 
  log4j.appender.appenderName.layout.option = valueN 
  
  其中,Log4j提供的layout有以下几种: 
  org.apache.log4j.HTMLLayout(以HTML表格形式布局), 
  org.apache.log4j.PatternLayout(可以灵活地指定布局模式), 
  org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串), 
  org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) 
  
  Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下: %m 输出代码中指定的消息
  %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 
  %r 输出自应用启动到输出该log信息耗费的毫秒数 
  %c 输出所属的类目,通常就是所在类的全名 
  %t 输出产生该日志事件的线程名 
  %n 输出一个回车换行符,Windows平台为“ ”,Unix平台为“ ” 
  %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss.SSS},输出类似:2002年10月18日 22:10:28,921 
  %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
  
  log4j.appender.appenderName.DatePattern=yyyy-MM-dd'.log'
  # DatePattern配合DailyRollingFileAppender使用
  # 每天滚动一次文件;若前面配置日志文件名为logFile.log,则当天生成的日志名为logFile.log
  # 到明天若有新日志生成则昨天的将更名为logFile.log昨日期.log,并生成新的logFile.log存今天日志
  # 注意,没有新日志的话过了一天也不会更名
  # yyyy-MM:每月,
  # yyyy-ww:每周,
  # yyyy-MM-dd:每天,
  # yyyy-MM-dd-a:每天两次,
  # yyyy-MM-dd-HH:每小时,
  # yyyy-MM-dd-HH-mm:每分钟 。
  • 示例

    log4j.rootLogger=DEBUG, Console, OutFile
    
    #Console
    log4j.appender.Console=org.apache.log4j.ConsoleAppender
    log4j.appender.Console.layout=org.apache.log4j.PatternLayout
    log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
    
    log4j.logger.java.sql.ResultSet=INFO
    log4j.logger.org.apache=INFO
    log4j.logger.java.sql.Connection=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
    #File
    # 每天产生一个日志文件
    log4j.appender.OutFile=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.OutFile.layout=org.apache.log4j.PatternLayout
    log4j.appender.OutFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
    #日志路径
    log4j.appender.OutFile.File=log_path/logFile.log
    #一分钟更名一次
    log4j.appender.OutFile.DatePattern=yyyy-MM-dd-HH-mm'.log'
    log4j.appender.OutFile.Append=true
    #日志最大容量,超出自然将原内容移到别的文件里
    log4j.appender.OutFile.MaxFileSize=500KB
    #最大保留天数
    log4j.appender.OutFile.MaxHistory=30
    
  • 参考文章
  • 参考文章
  • DatePattern参考文章

    slf4j的应用

  • Logger必须作为类的静态变量使用(最好加final关键字),如此不论这个类实例化多少个,大家用的都是同一个logger,它记录的只是当前类的日志,不是每个实例的日志。

    private static final Logger logger=LoggerFactory.getLogger(类名.class);
    //创建Logger对象,后面添加日志信息时要用到;这条语句并不会向控制台打印任何信息
    
  • 用占位符记录日志,降低代码中字符串连接次数,节省了新建String对象。这意味着只有需要的String对象才会被创建,可以在运行时延迟字符串的创建。

    //需要“{}”时用\\转义
    logger.debug("Set \\{} differs from {}","3"); //3被当做字符
    //多个占位符
    logger.error("{},{},{}",1,2,3);
    //结果
    2018-08-18 10:58:38 [cn.xm.exam.test.Slf4jTest]-[DEBUG] Set {} differs from 3
    2018-08-18 10:58:38 [cn.xm.exam.test.Slf4jTest]-[ERROR] error:1,2,3
    
  • 项目中一般是将捕捉到的异常作为日志记录的最后一个参数,而且要放在{}可以格式化的参数之外,而且只能放在最后一个,防止被{}转为e.toString(),放在中间也不会被打印错误信息。

    public class Slf4jTest {
        private static final Logger log=LoggerFactory.getLogger(Slf4jTest.class);
    
        public static void main(String[] args) {
            openFile("e://test.txt");
        }
    
        public static void openFile(String filePath){
            File file=new File(filePath);
            try {
                InputStream in=new FileInputStream(file);
            } catch (FileNotFoundException e) {
                log.error("cann't found file [{}]",filePath,e); //打印一行提示信息cannt...,再打印一段异常信息
            }
        }
    }
    
  • 结果

    21:16:28.364 [main] ERROR com.java1234.test.Slf4jTest - cann't found file [e://test.txt]
    java.io.FileNotFoundException: e:\test.txt (系统找不到指定的文件。)
        at java.io.FileInputStream.open0(Native Method)
        at java.io.FileInputStream.open(FileInputStream.java:195)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at com.java1234.test.Slf4jTest.openFile(Slf4jTest.java:21)
        at com.java1234.test.Slf4jTest.main(Slf4jTest.java:15)
    
  • 尽量不要使用e.getMessage(),有的异常不一定有message

    代码实例

  • D:/ideawork

    参考文章

  • 日志配置文件中各个属性意义详见笔记:Mybatis与Spring、SpringMvc整合