2.3.2 日志

日志是Spring非常重要的一个依赖,这是因为:1)它是唯一的强制性外部依赖;2)每个人都喜欢看到他们使用的工具输出一些信息;而且3)Spring集成了许多其他工具,它们都选择了各自的日志依赖。在一个集中的位置为整个应用程序甚至外部组件统一配置日志信息,往往是应用程序开发者追求的一个目标。这比想象的更加困难,因为有太多的日志框架可以选择。

Spring中强制使用的日志依赖是Jakarta Commons Logging API(JCL)。我们编译了JCL,而且使JCL的日志对象对于扩展Spring Framework的类可见。所有版本的Spring都使用相同的日志库对于用户来说很重要:迁移很简单,因为即使扩展了Spring的程序也保留了向后兼容性。我们是这样做的,使Spring中的某个模块显式的依赖于commons-logging(JCL的标准实现),然后让所有其他的模块在编译时依赖它。举例来说,如果您使用Maven,并且想知道从哪里获得了对commons-logging的依赖,那么依赖就是来自Spring,准确来说来自核心模块spring-core

commons-logging的好处是您不需要其他任何东西就能使您的程序工作。它有一个运行时发现算法,在众所周知的类路径中找寻其他的日志框架,并从中挑选一个它认为合适的来使用(或者如果需要,您可以告诉它用哪一个)。如果没有可用的,就使用JDK提供的相当不错的日志(java.util.logging,简称JUL)。重要的是,在大多数情况下,您会发现您的Spring应用程序已经可以工作,并且日志愉快地打印到了控制台上。

使用Log4J 1.2或2.x

Log4j 1.x已经寿终正寝,而且Log4j 2.3是最后一个Java 6兼容的版本,更新的Log4j 2.x版本需要Java 7+。

很多人由于配置和管理的目的使用Log4j。Log4j是高效且完善的,实际上我们构建和测试Spring时在运行时使用的也是它。Spring还提供了一些用于配置和初始化Log4j的工具,所以在一些模块中Log4j是可选的编译期依赖。

要使Log4j 1.2和默认的JCL依赖(commons-logging)一起工作,您只需要把Log4j放到类路径中,并为其提供一个配置文件(在类路径的根目录中的log4j.propertieslog4j.xml)。所以对于Maven用户来说,这是您的依赖声明:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

下面是一个用于打印日志到控制台的log4j.properties示例:

log4j.rootCategory=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n

log4j.category.org.springframework.beans.factory=DEBUG

要将Log4j 2.x与JCL配合使用,您只需将Log4j放在类路径中,并为其提供配置文件(log4j2.xml、log4j2.properties或其他支持的配置格式)。对于Maven用户来说,最小的必需依赖是:

<dependencies>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-jcl</artifactId>
        <version>2.6.2</version>
    </dependency>
</dependencies>

如果您还希望将SLF4J委派给Log4j,例如对于默认使用SLF4J的其他库,那么还需要以下依赖:

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.6.2</version>
  </dependency>
</dependencies>

这里是一个用于将日志打印到控制台的log4j2.xml例子:

<?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>
    <Logger name="org.springframework.beans.factory" level="DEBUG"/>
    <Root level="error">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

不要使用Commons Logging

不幸的是,尽管对最终用户来说很方便,但是标准的commons-loggingAPI中的运行时发现算法是有问题的。如果您想避免JCL的标准查找,基本上有两种方法可以将其关闭:

  1. spring-core模块中排除该依赖(因为它是唯一显式依赖于commons-logging的模块)
  2. 依赖一个特殊的commons-logging,其使用空的jar包替代该库(更多细节请见SLF4J FAQ

要排除commons-logging,将以下内容添加到您的dependencyManagement段中:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.12.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

现在,程序已经无法运行了,因为类路径中没有了JCP API的实现,所以,要修复它必须提供一个新的实现。在下一节中,我们向您展示如何使用SLF4J提供JCL的替代实现。

结合Log4j或Logback使用SLF4J

Java的简单日志外观(SLF4J,Simple Logging Facade for Java)是常常与Spring一起使用的其他库常用的API。SLF4J通常与Logback一起使用,它是SLF4J API的本地实现。

SLF4J可以绑定到许多常见的日志框架,包括Log4j。同时,SLF4J还可以充当其他日志框架和其本身之间的桥梁。所以要在Spring中使用SLF4J,您需要使用SLF4J-JCL桥替代commons-logging依赖。一旦您这样做了,Spring内部的日志调用都将会被转换为对SLF4J API的调用,所以如果您程序中的其他库调用了该API,那么您就有一个单一的地方配置和管理日志了。

一个常见的选择可能是桥接Spring到SLF4J,然后将SLF4J显式绑定到Log4J。您需要提供多个依赖(并且排除已有的commons-logging):JCL桥、绑定到Log4J的SLF4J以及Log4J本身。在Maven中您会这样做:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.12.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

SLF4J用户中更常见的选择是直接绑定到Logback,这样可以使用更少的步骤并产生更少的依赖关系。由于Logback直接实现了SLF4J,这样就省去了额外的绑定步骤,所以您只需要依赖两个库,即jcl-over-slf4jlogback

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.7</version>
    </dependency>
</dependencies>

使用JUL(java.util.logging)

Commons Logging默认情况下会委托给java.util.logging,前提是在类路径中没有检测到Log4j。所以没有特别的依赖要设置:只需在独立程序(在JDK级别具有自定义或默认的JUL设置)或应用程序服务器的日志系统(及其系统范围内的JUL设置)中,为了将日志输出到java.util.logging使用没有外部依赖的Spring。

WebSphere上的Commons Logging

Spring程序可能运行在本身提供JCL实现的容器上,例如IBM的Websphere应用服务器(WAS)。这本身不会导致问题,但会导致需要理解的两种不同情况:

在“父类优先”的ClassLoader委托模型(WAS默认模型)中,程序总是会取得服务器提供的Commons Logging版本,委托给WAS的日志子系统(实际上基于JUL)。程序提供的JCL变体,无论是标准的Commons Logging还是JCL-over-SLF4J桥,都会与任何本地包含的日志提供程序一起被忽略。

在“父类置后”的委托模型(常规Servlet容器的默认设置,但在WAS上需要显式配置)下,将选取程序提供的Commons Logging变体,使您能够设置程序中使用的本地包含的日志提供程序。在没有本地日志提供程序的情况下,常规的Commons Logging默认会委托给JUL,有效地记录到WebSphere的日志子系统中,就像在“父类优先”的场景中那样。

总之,我们建议在“父类置后”模型中部署Spring应用程序,因为它天生就允许本地提供程序以及服务器的日志子系统。


书籍推荐