slf4j

slf4j - simple log facade for java

门面模式

slf4j是门面模式的典型应用
门面模式的核心是“外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用
image.png
门面模式的核心为Facade即门面对象,门面对象核心:

  • 知道所有子角色的功能和责任
  • 将客户端发来的请求委派到子系统中,没有实际业务逻辑
  • 不参与子系统内业务逻辑的实现

    为什么要使用slf4j

    举个栗子
    我们的系统使用了logback这个日志系统,我们的系统又使用了A.jar,A.jar中使用的日志系统为log4j,我们的系统又使用了B.jar,B.jar中使用的日志系统为slf4j-simple,这样我们的系统就必须同时支持和维护logback/log4j/slf4j-simple三种日志框架,非常不便。
    为了解决上述的问题,可以通过引入一个适配层,由适配层决定使用哪一种日志系统,而调用端只用打印日志即可,不需要关心如何打印日志,slf4j就是这种适配层。
    slf4j只是一个日至标准,并不是日志系统的具体实现,只做一下两件事情:

  • 提供日志接口

  • 提供获取具体日志对象的方法

    slf4j-simple、logback都是slf4j的具体实现,log4j并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12来实现slf4j。

slf4j好处

  1. 解耦合日志和项目
    slf4j是一个适配器,我们通过slf4j的日志方法统一打印日志时,可以忽略其他日志的具体方法,当系统切换日志源时不需要更改代码。
  2. 节省内存
    log4j这些传统的日志系统里面没有占位符的概念,需要打印信息的时候,不得不创建无用String对象来进行输出信息的拼接。

    在项目中使用slf4j

    1. 导入相关依赖

    1. <dependency>
    2. <groupId>org.slf4j</groupId>
    3. <artifactId>slf4j-api</artifactId>
    4. <version>1.7.13</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.slf4j</groupId>
    8. <artifactId>slf4j-log4j12</artifactId>
    9. <version>1.7.13</version>
    10. </dependency>
    1. <!-- 引入log4j日志依赖 -->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-log4j</artifactId>
    5. <version>1.3.8.RELEASE</version>
    6. </dependency>

    2. 创建Logger对象

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

/**

  • @author liuwenxiu
  • @Description:
  • @date 2022-7-6 18:39 */ public class LogTest { private static final Logger logger = LoggerFactory.getLogger(LogTest.class);

    @Test public void testLog() {

    1. logger.error("错误信息为:{}","Something error happen...");

    }

}

  1. > 注意点:项目中的Logger对象可能不止一个,注意在导包的时候导入**org.slf4jLogger对象**
  2. <a name="zRHHf"></a>
  3. #### 对象修饰符注意点
  4. 在创建对象时一般会使用**private static final**来修饰日志对象,原因如下
  5. - private => 防止其他类使用当前类的日志对象
  6. - static => 让每个类中的日志对象只生成一份,日志对象是属于类的,不属于具体的实例
  7. - final => 避免日志对象在运行时被修改
  8. <a name="TSjo1"></a>
  9. ## slf4j实现原理
  10. ```java
  11. Logger logger = LoggerFactory.getLogger(LogTest.class);
  1. public static Logger getLogger(Class<?> clazz) {
  2. Logger logger = getLogger(clazz.getName());
  3. //...省略部分代码的展示
  4. return logger;
  5. }
  6. // 此步骤就是根据Class对象获取其全限定名然后调用下面的方法
  1. public static Logger getLogger(String name) {
  2. ILoggerFactory iLoggerFactory = getILoggerFactory();
  3. return iLoggerFactory.getLogger(name);
  4. }
  1. public static ILoggerFactory getILoggerFactory() {
  2. if (INITIALIZATION_STATE == UNINITIALIZED) {
  3. INITIALIZATION_STATE = ONGOING_INITIALIZATION;
  4. performInitialization();
  5. }
  6. //...省略部分代码的展示
  7. }
  8. /*
  9. if(初始化状态 = 未初始化) {
  10. 初始化状态 = 正在进行初始化;
  11. 执行初始化();
  12. }
  13. */
  1. private final static void performInitialization() {
  2. bind();
  3. if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
  4. versionSanityCheck();
  5. }
  6. }
  1. Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
  1. private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
  2. // use Set instead of list in order to deal with bug #138
  3. // LinkedHashSet appropriate here because it preserves insertion order during iteration
  4. Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
  5. try {
  6. ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
  7. Enumeration<URL> paths;
  8. if (loggerFactoryClassLoader == null) {
  9. paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
  10. } else {
  11. paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
  12. }
  13. while (paths.hasMoreElements()) {
  14. URL path = paths.nextElement();
  15. staticLoggerBinderPathSet.add(path);
  16. }
  17. } catch (IOException ioe) {
  18. Util.report("Error getting resources from path", ioe);
  19. }
  20. return staticLoggerBinderPathSet;
  21. }
  22. // 核心代码是第11行
  23. // 在getLogger时会去classpath下找STATIC_LOGGER_BINDER_PATH
  24. // STATIC_LOGGER_BINDER_PATH的值是org/slf4j/impl/StaticLoggerBinder.class
  25. // 即所有slf4j的实现在提供的jar包路径下都存在上述的这个类
  26. // 我们不能避免在系统中同时引入多个slf4j的实现,所以接收的地方是一个Set。

image.png

Log4j

Log4J 是 Apache 的一个开源项目(官网 http://jakarta.apache.org/log4j),通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、GUI 组件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程,方便项目的调试。

Log4j的组成

  • Loggers(日志记录器) - 控制日志的输出级别与日志是否输出
  • Appenders(输出端) - 指定日志的输出方式,输出到控制台、文件等
  • Layout(日志格式化器) - 控制日志信息的输出格式

    日志级别

    | 日志级别 | 含义 | | —- | —- | | OFF | 最高日志级别,关闭左右日志 | | FATAL | 将会导致应用程序退出的错误 | | ERROR | 发生错误事件,仍不影响系统的继续运行 | | WARN | 警告,即潜在的错误情形 | | INFO | 一般用于粗粒度级别上,强调应用程序的运行全程 | | DEBUG | 一般用于细粒度级别上,对调试应用程序非常有帮助 | | ALL | 最低等级,打开所有日志记录 |

注:一般只使用4个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG

Appender

输出端类型 作用
ConsoleAppender 将日志输出到控制台
FileAppender 将日志输出到文件中
DailyRollingFileAppender 将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender 把日志信息保存到数据库中

Layout

格式化器类型 作用
HTMLLayout 格式化日志输出为HTML表格形式
SimpleLayout 简单的日志输出格式化,打印的日志为(info-message)
PatternLayout 最强大的格式化日期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式。

slf4j和log4j的区别

  • log4j(log for java)
    Apache的一个开源项目,可以灵活地记录日志信息,通过log4j的配置文件灵活配置日志的记录格式、记录级别、输出格式,而不需要修改已有的日志记录代码。
  • slf4j(simple log facade for java)
    slf4j不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,slf4j是一个用于日志系统的简单Facade,允许用户在部署其应用时所希望的日志系统。

可以将log4j看做一个完整的日志库,而slf4j是一个日志库的规范接口。

log4j2的配置文件

基础信息

配置文件的格式:xml形式/json格式
配置文件的位置:log4j2默认会在classpath目录下寻找log4j2.xml、log4j2.json、log4j.jsn等名称的文件
系统选择配置文件的优先级从先到后如下所示:

  1. log4j2-test.json或者log4j2-test.jsn文件
  2. log4j2-test.xml文件
  3. log4j2.json或log4j2.jsn文件
  4. log4j2.xml文件

    一般在开发时默认使用log4j2.xml进行命名

配置示例

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
  3. <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
  4. <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
  5. <configuration status="WARN" monitorInterval="30">
  6. <!--先定义所有的appender-->
  7. <appenders>
  8. <!--这个输出控制台的配置-->
  9. <console name="Console" target="SYSTEM_OUT">
  10. <!--输出日志的格式-->
  11. <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
  12. </console>
  13. <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
  14. <File name="log" fileName="log/test.log" append="false">
  15. <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
  16. </File>
  17. <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
  18. <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
  19. filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
  20. <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
  21. <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
  22. <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
  23. <Policies>
  24. <TimeBasedTriggeringPolicy/>
  25. <SizeBasedTriggeringPolicy size="100 MB"/>
  26. </Policies>
  27. </RollingFile>
  28. <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
  29. filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
  30. <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
  31. <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
  32. <Policies>
  33. <TimeBasedTriggeringPolicy/>
  34. <SizeBasedTriggeringPolicy size="100 MB"/>
  35. </Policies>
  36. <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
  37. <DefaultRolloverStrategy max="20"/>
  38. </RollingFile>
  39. <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
  40. filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
  41. <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
  42. <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
  43. <Policies>
  44. <TimeBasedTriggeringPolicy/>
  45. <SizeBasedTriggeringPolicy size="100 MB"/>
  46. </Policies>
  47. </RollingFile>
  48. </appenders>
  49. <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
  50. <loggers>
  51. <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
  52. <logger name="org.springframework" level="INFO"></logger>
  53. <logger name="org.mybatis" level="INFO"></logger>
  54. <root level="all">
  55. <appender-ref ref="Console"/>
  56. <appender-ref ref="RollingFileInfo"/>
  57. <appender-ref ref="RollingFileWarn"/>
  58. <appender-ref ref="RollingFileError"/>
  59. </root>
  60. </loggers>
  61. </configuration>

配置信息

Configuration节点

子节点:Appenders和Loggers
属性:status和monitorinterval

  • status:指定log4j本身打印日志的级别
  • monitorinterval:指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s

    Appenders节点

    子节点:Console、RollingFile、File

    Console节点

    子节点:ThresholdFilter、PatternLayout
    属性:name、target

  • name:指定Appender的名字

  • target:SYSTEM_OUT或SYSTAEM_ERR,一般只设置默认SYSTEM_OUT

    ThresholdFilter节点

    临界值过滤器,过滤掉低于指定临界值的日志
    属性:onMatch、onMismatch

  • onMatch:表示匹配设定的日志级别后是DENY还是ACCEPT

  • onMismatch:表示不匹配设定的日志级别是DENY、ACCEPT还是NEUTRAL

    PatternLayout节点

    属性:charset、pattern

  • charset:设置语言,常用”UTF-8”

  • pattern:输出格式,不设置默认为%m%n

    File节点

    定义输出到指定位置文件的Appender
    子节点:PatternLayout
    属性:name、fileName

  • fileName:指定输出日志的目的文件带全路径的文件名

    RollingFile节点

    定义超过指定大小自动删除旧的创建新的Appender
    子节点:ThresholdFilter、PatternLayout、Policies、DefaultRolloverStrategy
    属性:name、fileName、filePattern

  • filePattern:指定新建日志文件的名称格式

    Policies节点

    子节点:TimeBasedTriggeringPolicy、SizeBasedTriggeringPolicy

  • TimeBasedTriggeringPolicy:基于时间的滚动策略
    interval属性用来指定多久滚动一次,默认是1小时
    modulate=true用来调整时间:比如现在是3:00am,interval是4,那么第一次滚动在4:00am,接着是8:00am,12:00am…而不是7:00am

  • SizeBasedTriggeringPolicy:基于指定文件大小的滚动策略
    size属性用来指定每个日志文件的大小

    DefaultRolloverStrategy节点

    指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)

    Loggers节点

    子节点:Root、Logger

    Root节点

    指定项目的根日志,如果没有单独指定Logger,默认使用该Root日志输出
    level:日志输出级别,共有八个级别,从低到高为:ALL

    Logger节点

    单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等
    属性:level、name

  • name:用来指定该Logger所适用的类或者类所在的包的全路径,继承自Root节点

子节点:AppenderRef

  • 指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root。如果指定了,会在指定的这个Appender和Root的Appender中都会输出。如果设置Logger的additivity=”false”则只会在自定义的Appender中输出。

    配置参数详解

    %d{HH:mm:ss.SSS} 表示输出到毫秒的时间
    %t 输出当前线程名称
    %-5level 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
    %logger 输出logger名称,因为Root Logger没有名称,所以没有输出
    %msg 日志文本
    %n 换行
    其他常用的占位符有:
    %F 输出所在的类文件名,如Log4j2Test.java
    %L 输出行号
    %M 输出所在方法名
    %l 输出语句所在的行数, 包括类名、方法名、文件名、行数

    日志Level

    | 级别 | 含义 | | —- | —- | | ALL | 最低级别,用于打开所有的日志记录 | | Trace | 追踪,程序推进下可以写个trace输出 | | Debug | 一般用于细粒度级别上,对调试应用程序非常有帮助 | | Info | 一般用于粗粒度级别上,强调应用程序的运行全程 | | Warn | 输出警告及warn以下级别的日志 | | Error | 输出错误信息日志 | | Fatal | 输出每个严重的错误事件将会导致应用程序退出的日志 | | OFF | 最高级别,关闭所有日志记录 |