日志之logback

日志之logback

1.1 简介

师出同门,与log4j一样,logback也是由Ceki Gülcü开发的开源日志组件,可以说是log4j的改进版;在现如今的项目中,logback的出现次数越来越多,是目前主流首选的日志记录工具。

1.2 logback结构

日志之logback

日志之logback

日志之logback

logback分成三个模块:logback-core,logback- classic,logback-access。

logback-core提供了logBack的核心功能,是另外两个组件的基础;

logback-classic模块实现了SLF4J API;

logback-access模块与Servlet容器集成提供Http来访问日志的功能。

1.3 使用

首先,需要在应用的pom.xml中添加依赖:

<!--slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.20</version>
</dependency>

<!-- logback -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.7</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-access</artifactId>
    <version>1.1.7</version>
</dependency>

其次,声明测试代码:

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class slf4j_logbackDemo {
    Logger logger=  LoggerFactory.getLogger(slf4j_logbackDemo.class);

    @Test
    public void test() {
        logger.debug("debug message");
        logger.info("info message");
        logger.warn("warning message");
        logger.error("error message");
        logger.warn("login message");
    }
}

最后,在classpath下声明配置文件:logback.xml;

<!--每天生成一个文件,归档文件保存30天:-->
<configuration >

    <!--设置自定义pattern属性-->
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%-5level] [%thread] [%logger] %msg%n"/>

    <!--控制台输出日志-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--设置控制台输出日志的格式-->
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--滚动记录日志文件:-->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--当天生成的日志文件名称:-->
        <file>e:/log.out</file>
        <!--根据时间来记录日志文件:-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--归档日志文件的名称:-->
            <fileNamePattern>testLog-%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--归档文件保存30天-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!--生成的日志信息格式-->
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--根root logger-->
    <root level="DEBUG">
        <!--设置根logger的日志输出目的地-->
        <appender-ref ref="FILE" />
        <appender-ref ref="CONSOLE" />
    </root>

</configuration>

通过以上步骤,logback就可以正常的运行了。

1.4 配置文件详解

(1)configuration:配置根节点

日志之logback

<configuration scan="true" scanPeriod="60" debug="false"></configuration>  

scan:程序运行时配置文件被修改,是否重新加载。true,重新加载;false,不重新加载;默认为true;

scanPeriod:监测配置文件被修改的时间间隔,scan属性必须设置为true才可生效;默认为1分钟,默认单位是毫秒;

debug:是否打印logback程序运行的日志信息。true,打印;false,不打印;默认为false;

(2)property:属性变量

<configuration scan="true" scanPeriod="60" debug="false">
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%-5level] [%logger] %msg%n" ></property>
</configuration>  

name:变量的名称,可以随意起名,但建议名字要简明直译;

value:变量的值;

在配置文件中,我们可以用 ${} 的方式来使用,将变量引入到其他节点中去。如果有多处使用相同的内容,便可使用属性变量的方式进行统一,减少很多不必要的代码;

(3)logger:日志对象

logger分为2种,一种是普通日志对象,另一种是根日志对象。对于大部分应用来说,只设置根日志对象即可。
在java日志系统中,无论是log4j还是logback,他们的日志对象体系都是呈现“树”的形式,根日志对象为最顶层节点,其余包或者类中的日志对象都继承于根日志节点;

对于普通日志对象来说,我们可以设置某一个包或者某一个类的日志级别,还可以单独设置日志的输出目的地;

<configuration scan="true" scanPeriod="60" debug="false">   
    <logger name="java.sql" level="debug" addtivity="true">
        <appender-ref ref="CONSOLE" />
    </logger>   
</configuration>  

name:用来指定此logger属于哪个包或者哪个类;

level:用来指定此logger的日志打印级别;

addtivity:是否向上传递日志打印信息。之前说了,logger对象呈现一个树的结构,根logger是树的顶端,下面的子logger的addtivity属性如果设置为true则会向上传递打印信息,出现日志重复打印的现象;

appender-ref:日志输出目的地,将此logger所打印的日志交给此appender;

值得注意的是,上面的例子中,如果此logger没有指定appender,而且addtivity也设置为true,那么此logger对应的日志信息只会打印一遍,是由root来完成的;但是如果addtivity设置成false,那么此logger将不会输出任何日志信息;

(3)logger<root>:根日志对象

<root>也是日志对象中的一种,但它位于logger体系中的最顶层。当一个类中的logger对象进行打印请求时,如果配置文件中没有为该类单独指定日志对象,那么都会交给root根日志对象来完成;

<root>节点中只有一个level属性,还可以单独指定日志输除目的地<apender-ref>;

<configuration scan="true" scanPeriod="60" debug="false">   
    <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

(4)appender:日志输出目的地

日志之logback

与log4j中的appender一样,logback中的<appender>节点也同样负责日志输出的目的地。

appender中有2个必填属性–name和class。name为<appender>节点的名称,class为<appender>的全限定类名,也就是日志输出目的地的处理类。此外,我们还可以在<appender>中单独指定日志的格式,设置日志过滤器等操作;

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">  
    <encoder>  
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>  
    </encoder>  
</appender>

在logback中,主要有以下三种日志目的地处理类:

①ch.qos.logback.core.ConsoleAppender

将日志输出到控制台,可以在其节点中设置<encoder>子节点,设置日志输出的格式;

例子:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">  
    <encoder>  
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>  
    </encoder>  
</appender>

②ch.qos.logback.core.FileAppender

将日志输出到具体的磁盘文件中,可以单独指定具体的位置,也可以设置日志的输出格式;

例子:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">  
    <file>e:/log.out</file>  
    <append>true</append>  
    <prudent>false</prudent>
    <encoder>  
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>  
    </encoder>  
</appender> 

子节点<append>:新增的日志是否以追加到文件结尾的方式写入到log.out文件中,true为追加,fasle为清空现存文件写入;
子节点<prudent>:日志是否被安全的写入磁盘文件,默认为false。如果为true,则效率低下;

③ch.qos.logback.core.rolling.RollingFileAppender

滚动记录日志,当符合<rollingPolicy>节点中设置的条件时,会将现有日志移到新的文件中去。<rollingPolicy>节点中可设置的条件为:文件的大小、时间等;

例子:
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>e:/log.out</file>
    <append>true</append>  
    <prudent>false</prudent>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>testLog-%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

④ch.qos.logback.classic.AsyncAppender

异步记录日志,内部通过使用缓存的方式来实现异步打印,将日志打印事件event放入缓存中。具体数据结构为BlockingQueue;

例子:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">  
    <file>e:/log.out</file>  
    <append>true</append>  
    <prudent>false</prudent>
    <encoder>  
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>  
    </encoder>  
</appender> 
<appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">  
    <discardingThreshold>0</discardingThreshold>  
    <queueSize>512</queueSize>  
    <appender-ref ref ="FILE"/>  
</appender>  

子节点<queueSize>:指的是BlockingQueue的队列容量大小,默认为256个;
子节点<discardingThreshold>:如果BlockingQueue中还剩余20%的容量,那么程序会丢弃TRACE、DEBUG和INFO级别的日志打印事件event,只保留WARN和ERROR级别的。为了保留所有的日志打印事件,可以将该值设置为0。

(5)rollingPolicy

日志文件的滚动策略,与RollingFileAppender搭配使用,当日志文件发生变动时决定RollingFileAppender的行为;

在节点中有一个class属性,可选的值为TimeBasedRollingPolicy、FixedWindowRollingPolicy、TriggeringPolicy;

其中ch.qos.logback.core.rolling.TimeBasedRollingPolicy表示根据时间制定日志文件的滚动策略;

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>testLog-%d{yyyy-MM-dd}.log</fileNamePattern>
    <maxHistory>30</maxHistory>
</rollingPolicy>

ch.qos.logback.core.rolling.FixedWindowRollingPolicy表示如果日志文件大小超过指定范围时,会根据文件名拆分成多个文件;

<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">   
   <fileNamePattern>tests.%i.log.zip</fileNamePattern>   
   <minIndex>1</minIndex>   
   <maxIndex>3</maxIndex>   
 </rollingPolicy>

(6)triggeringPolicy

与节点一样,节点也属于日志滚动策略中的一种。

ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy表示根据日志文件大小,超过制定大小会触发日志滚动;

<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">   
   <maxFileSize>5MB</maxFileSize>   
 </triggeringPolicy>

(7)encoder

日志格式化节点,负责格式化日志信息。只负责了两件事情,第一负责将日志信息转换成字节数组,第二将字节数组写到输出流当中去;

在中使用来设置对应的格式;

<encoder>   
    <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>   
</encoder>

%logger:表示输出logger名称,后面跟一个{0}表示:只输出logger最右边点符号之后的字符串;例如:com.jiaboyan.test —> test;

%d{HH:mm:ss.SSS}:表示格式化日期输出,14:06:49.812;

%line:输出执行日志请求的行号。

%thread:表示产生日志的线程名;

%level:输出日志级别;

%method:输出执行日志请求的方法名;

%class:输出日志打印类的全限定名,后面跟{0}表示,含义为全限定类名最右边点符号之后的字符串。例如:com.jiaboyan.test —> test;

%-4relative含义:符号减号“-”是左对齐 标志,接着是可选的最小宽度 修饰符,用十进制数表示。relative是输出从程序启动到创建日志记录的时间,单位是毫秒;

%msg:表示应用程序提供需要打印的日志信息;

%n:表示换行符;

1.5 logback性能测试

接下来,我们对logback进行下性能测试,分为同步和异步;

(1)测试代码:(单线程、同步)

public class slf4j_logbackDemo {

    Logger logger =  LoggerFactory.getLogger(slf4j_logbackDemo.class);

    @Test
    public void test() {
        long start = System.currentTimeMillis();
        for(int x=0;x<50;x++) {
            long startss = System.currentTimeMillis();
            for (int y = 0; y < 10000; y++) {
                logger.info("Info Message!");
            }
            System.out.print(System.currentTimeMillis()-startss+" ");
        }
        System.out.print(System.currentTimeMillis()-start+" ");
    }
}

配置文件:(注意日志输出的格式,只有简单的日志信息)

在笔者测试前,并没有对日志格式化做太多的关注。在测试完成后,发现日志格式化对性能的影响很大,所以此次的测试为了节约时间,就只输出日志信息,并没有具体的时间、logger对象、日志级别等信息;

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>e:/log.out</file>
        <append>true</append>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="FILE" />
    </root>
</configuration>

结果:(单位毫秒)

182 86 34 31 44 38 30 31 45 57 33 29 35 42 46 43 48 40 40 45 50 40 44 53 65 38 59 32 26 29 32 30 34 32 34 23 26 20 20 21 20 22 20 22 21 20 23 22 20 23 

总耗时:1900

(2)测试代码:(多线程、同步)

public class slf4j_logbackDemo {

    Logger logger =  LoggerFactory.getLogger(slf4j_logbackDemo.class);

     @Test
    public void testThread() throws InterruptedException {
        long start = System.currentTimeMillis();
        final CountDownLatch countDownLatch = new CountDownLatch(50);
        for(int x= 0;x<50;x++){
            new Thread(new Runnable() {
                public void run() {
                    for (int y = 0; y < 10000; y++) {
                        logger.info("Info Message!");
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long time = System.currentTimeMillis() - start;
        System.out.println(time);
    }
}

配置文件:(同上)

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>e:/log.out</file>
        <append>true</append>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="FILE" />
    </root>
</configuration>

结果:(单位毫秒)

总耗时:2229

使用了CountDownLatch对象,可以计算出50个线程执行完毕后所花费的总时间。可以看出,由于线程创建的原因,在多线程的情况下并没有体现出更优秀的性能。

当然,还有一方面因素就是日志总量太小,不足以体现出多线程的优势。

(3)测试代码:(单线程、异步)

public class slf4j_logbackDemo {

    Logger logger =  LoggerFactory.getLogger(slf4j_logbackDemo.class);

     @Test
    public void test() throws InterruptedException {
        long start = System.currentTimeMillis();
        for(int x=0;x<50;x++) {
            long startss = System.currentTimeMillis();
            for (int y = 0; y < 10000; y++) {
                logger.info("Info Message!");
            }
            System.out.print(System.currentTimeMillis()-startss+" ");
        }
        System.out.print(System.currentTimeMillis()-start+" ");
    }
}

配置文件:(同上)

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>e:/log.out</file>
        <append>true</append>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">  
        <discardingThreshold>0</discardingThreshold>  
        <queueSize>512</queueSize>  
        <appender-ref ref ="FILE"/>  
    </appender> 
    
    <root level="info">
        <appender-ref ref="ASYNC" />
    </root>
</configuration>

结果:(单位毫秒)

183 123 48 54 45 40 45 47 48 48 44 79 51 56 57 91 116 79 95 126 53 73 47 62 50 49 42 43 39 42 41 37 28 30 28 30 26 34 34 39 29 31 31 32 31 32 32 32 34 32 

总耗时:2619

奇怪的是,在异步情况下,logback并没有体现出应有的性能优势。后来继续通过测试发现,日志信息的输出量、格式化影响很大。

在调整之前的日志文件大小为7.15M,在调整之后为19M。调整之前的格式为%msg%n,调整之后的为%d{HH:mm:ss.SSS} [%-5level] [%thread] [%logger] %msg%n;

在调整后进行测试,同步下24760ms,异步下10921ms,性能提升一倍;

(4)测试代码:(多线程、异步)

public class slf4j_logbackDemo {

    Logger logger =  LoggerFactory.getLogger(slf4j_logbackDemo.class);

     @Test
    public void test() throws InterruptedException {
        long start = System.currentTimeMillis();
        final CountDownLatch countDownLatch = new CountDownLatch(50);
        for(int x= 0;x<50;x++){
            new Thread(new Runnable() {
                public void run() {
                    for (int y = 0; y < 10000; y++) {
                        logger.info("Info Message!");
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long time = System.currentTimeMillis() - start;
        System.out.println(time);
    }
}

配置文件:(同上)

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>e:/log.out</file>
        <append>true</append>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">  
        <discardingThreshold>0</discardingThreshold>  
        <queueSize>512</queueSize>  
        <appender-ref ref ="FILE"/>  
    </appender> 
    
    <root level="info">
        <appender-ref ref="ASYNC" />
    </root>
</configuration>

结果:(单位毫秒)

总耗时:2780