slf4j的使用规范

一、结论:应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。代码中的使用格式如下:

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

private static final Logger LOGGER = LoggerFactory.getLogger(Abc.class);

LOGGER.info("订单{}开始取消订单,请求参数:{}", orderNo, object.toJSONString());{}可以读取到后面追加的参数并放置里面。

日志工具对象logger应声明为private static final:

1、声明为private是出于安全性考虑,防止logger对象被其他类非法使用。

2、声明为static是为了防止重复new出logger对象,同时防止logger被序列化,造成安全风险,出于资源利用的考虑,LOGGER的构造方法参数是Class,决定了LOGGER是根据类的结构来进行区分日志,所以一个类只要一个LOGGER就可以了,故static。

3、声明为final是因为在类的生命周期内无需变更logger,只是记录该类的信息。

 

二、SLF4J,全称为:The Simple Logging Facade for Java,官方解释为:The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks, such as java.util.logging, logback and log4j. SLF4J allows the end-user to plug in the desired logging framework at deployment time. Note that SLF4J-enabling your library/application implies the addition of only a single mandatory dependency, namely slf4j-api-1.7.21.jar.

slf4j是一系列的日志接口,而log4j logback是具体实现了的日志框架。SLF4J(Simple logging Facade for Java)不是一个真正的日志实现,而是一个抽象层,它允许你在后台使用任意一个日志类库。slf4j译为简单日志门面,是日志框架的抽象。而log4j和logback是众多日志框架中的几种。

 

三、log4j和logback的区别:logback是直接实现了slf4j的接口,是不消耗内存和计算开销的。而log4j不是对slf4j的原生实现,所以slf4j api在调用log4j时需要一个适配层(适配器模式)。Logback相对log4j而言有了相对多的改进。但是两者的用法几乎差别不大。下面是logback的优势:

•更快的执行速度

•充分的测试

•logback-classic非常自然的实现了SLF4J

•丰富的扩展文档

•可以使用使用XML配置文件或者Groovy

•自动重新载入配置文件

•优雅地从I/O错误中恢复

•自动清除旧的日志归档文件

•自动压缩归档日志文件

注意:如果单独使用slf4j,是无法在控制台输出日志的。也就是说我们在具体开发中,需要绑定一个具体的日志框架,也就是slf4j的实现,才能正常的使用slf4j。

 

四、总结如下:

1、slf4j是java的一个日志门面,实现了日志框架一些通用的api,log4j和logback是具体的日志框架。

2、他们可以单独的使用,也可以绑定slf4j一起使用。

单独使用。分别调用框架自己的方法来输出日志信息。绑定slf4j一起使用。调用slf4j的api来输入日志信息,具体使用与底层日志框架无关(需要底层框架的配置文件)。显然不推荐单独使用日志框架。假设项目中已经使用了log4j,而我们此时加载了一个类库,而这个类库依赖另一个日志框架。这个时候我们就需要维护两个日志框架,这是一个非常麻烦的事情。而使用了slf4j就不同了,由于应用调用的抽象层的api,与底层日志框架是无关的,因此可以任意更换日志框架。

 

五、使用slf4j绑定日志系统的优势:

1、软件工程的角度。抽象,解耦,便于维护。在你的开源或内部类库中使用SLF4J会使得它独立于任何一个特定的日志实现,这意味着不需要管理多个日志配置或者多个日志类库。

2、语法设计角度。slf4j有{}占位符,而log4j需要用“+”来连接字符串,既不利于阅读,同时消耗了内存(heap memory)。在SLF4J,我们不需要字符串连接而且不会导致暂时不需要的字符串消耗。取而代之的,我们在一个以占位符和以参数传递实际值的模板格式下写日志信息,同时提高了代码的可读性。

占位符(place holder),在代码中表示为“{}”的特性。占位符是一个非常类似于在String的format()方法中的%s,因为它会在运行时被某个提供的实际字符串所替换。这不仅降低了你代码中字符串连接次数,而且还节省了新建的String对象。即使你可能没需要那些对象,但这个依旧成立,取决于你的生产环境的日志级别,例如在DEBUG或者INFO级别的字符串连接。因为String对象是不可修改的并且它们建立在一个String池中,它们消耗堆内存( heap memory)而且大多数时间他们是不被需要的,例如当你的应用程序在生产环境以ERROR级别运行时候,一个String使用在DEBUG语句就是不被需要的。通过使用SLF4J,你可以在运行时延迟字符串的建立,这意味着只有需要的String对象才被建立。而如果你已经使用log4j,那么你已经对于在if条件中使用debug语句这种变通方案十分熟悉了,但SLF4J的占位符就比这个好用得多。

对trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式。 说明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。 正例:(条件)建设采用如下方式

if (logger.isDebugEnabled()) {

logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);

}

正例:(占位符)

logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

3、通过使用SLF4J的日志方法,你可以延迟构建日志信息(Srting)的开销,直到你真正需要,这对于内存和CPU都是高效的。

4、作为附注,更少的暂时的字符串意味着垃圾回收器(Garbage Collector)需要做更好的工作,这意味着你的应用程序有为更好的吞吐量和性能。

六、slf4j绑定logback

Springboot默认的日志框架为logback,pom添加依赖:

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

配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    
    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%thread]){faint} %clr(%-40.40logger{39}){cyan} %clr([%L]) %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
    <!-- Console 输出设置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 定义日志级别 -->
    <property name="log.level" value="INFO" />

    <!-- 定义日志文件名称,注意修改此处!!! -->
    <property name="log.name" value="shop" />

    <!-- 定义日志文件的存储地址 -->
    <!-- <property name="log.path" value="/app/wo_sale/servers/logs/${log.name}" /> -->
    <property name="log.path" value="logs/${log.name}" />

    <!-- 定义日志文件名格式化 -->
    <property name="log.timeFormat" value="yyyy-MM-dd" />
    
    <!-- 定义日志文件的输出格式。%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%logger{50} 表示logger名字最长50个字符,否则按照句点分割。%msg:日志消息,%n是换行符 -->
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />

    <!-- 定义日志文件保留天数 -->
    <property name="log.maxHistory" value="30" />

    <!-- 定义日志文件最大限制 -->
    <property name="log.maxFileSize" value="10MB" />

    <!-- 控制台输出 -->
    <!-- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender> -->

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件,并按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/${log.name}.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${log.level}</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--    滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 
                    %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <FileNamePattern>${log.path}/${log.name}.%d{${log.timeFormat}}-%i.log</FileNamePattern>
            <MaxHistory>${log.maxHistory}</MaxHistory>
            <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <MaxFileSize>${log.maxFileSize}</MaxFileSize>
            </TimeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件。仅记录错误日志 -->
    <appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error/${log.name}_error.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${log.path}/error/${log.name}_error.%d{${log.timeFormat}}-%i.log</FileNamePattern>
            <MaxHistory>${log.maxHistory}</MaxHistory>
            <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <MaxFileSize>${log.maxFileSize}</MaxFileSize>
            </TimeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 日志输出级别,root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
        要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 -->
    <root level="${log.level}">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
        <appender-ref ref="FILE-ERROR" />
    </root>
</configuration>

 

原文地址:https://www.cnblogs.com/wanghaichao/p/9214265.html