天天看點

Spring 切換日志系統

      • 為何需要切換
      • 如何進行操作
        • Gradle建構器配置
        • Maven建構器配置
      • 結果示例
      • 容器日志

為何需要切換

由于曆史原因,Spring最開始在core包中引入的是commons-logging(JCL标準實作)的日志系統,官方考慮到相容問題,在後續的Spring版本中并未予以替換,而是繼續沿用。如果考慮到性能、效率,應該自行進行替換,在項目中明确指定使用的日志架構,進而在編譯時就指定日志架構。

commons-logging日志系統是基于運作發現算法(常見的方式就是每次使用org.apache.commons.logging.LogFactory.getLogger(xxx),就會啟動一次發現流程),擷取最适合的日志系統進行日志記錄,其效率要低于使用SLF4J,在編譯時明确指定日志系統的方式,目前常用的日志架構有logback、log4j、log4j2等。

動态綁定請參考:http://blog.csdn.net/yycdaizi/article/details/8276265

官方說明:

The mandatory logging dependency in Spring is the Jakarta Commons Logging API (JCL). We compile against JCL and we also make JCL Log objects visible for classes that extend the Spring Framework. It’s important to users that all versions of Spring use the same logging library: migration is easy because backwards compatibility is preserved even with applications that extend Spring. The way we do this is to make one of the modules in Spring depend explicitly on commons-logging (the canonical implementation of JCL), and then make all the other modules depend on that at compile time. If you are using Maven for example, and wondering where you picked up the dependency on commons-logging, then it is from Spring and specifically from the central module called spring-core.

Spring中強制使用的是Jakarta Commons Logging API (JCL)日志系統。我們基于JCL進行編譯,建構JCL日志對象,這些同時也對擴充自Spring類可見的。對于使用者而言,確定不同版本的Spring使用相同的日志系統是非常重要的–代碼遷移需要確定逆向相容性。我們之是以這樣做,是為了在Spring的一個包中明确的依賴于commons-logging(JCL權威實作),而其他包就基于這個包進行建構編譯。如果你使用maven,你可以發現commons-logging以來自Spring-core包。

The nice thing about commons-logging is that you don’t need anything else to make your application work. It has a runtime discovery algorithm that looks for other logging frameworks in well known places on the classpath and uses one that it thinks is appropriate (or you can tell it which one if you need to). If nothing else is available you get pretty nice looking logs just from the JDK (java.util.logging or JUL for short). You should find that your Spring application works and logs happily to the console out of the box in most situations, and that’s important.

使用commons-logging的好處是,你不需要做其他額外事情就可以讓程式正常工作。它有運作時的發現算法,能夠在運作時從classpath自動發現其他日志架構,并自行挑選其中一個合适的,或者你自行指定一個。如果在運作時沒有發現任何其他日志架構,則commons-loggin會直接使用JDK的日志系統(java.util.logging或JUL)。

Unfortunately, the runtime discovery algorithm in commons-logging, while convenient for the end-user, is problematic. If we could turn back the clock and start Spring now as a new project it would use a different logging dependency. The first choice would probably be the Simple Logging Facade for Java ( SLF4J), which is also used by a lot of other tools that people use with Spring inside their applications.

非常不幸的是,對于終端使用者而言,commons-logging的運作時發現算法是合适的,但對于其他使用場景,卻是問題重重。如果時間可以重來,讓我們重新選擇一個不同的日志系統,我們可能會選擇SLF4J。

如何進行操作

以Log4J為例:

1. 使用SLF4J-JCL橋接包替換commons-logging包。確定Spring架構使用的logging call能夠轉換為SLF4J的API

2. 引入SLF4J API包

3. 引入SLF4J-Log4J橋接包,以使得SLF4J使用Log4J進行日志記錄

4. 引入Log4J API包

以Logback為例:

1. 使用SLF4J-JCL橋接包替換commons-logging包。確定Spring架構使用的logging call能夠轉換為SLF4J的API

2. 引入SLF4J API包

3. 引入Logback包

官方說明

There are basically two ways to switch off commons-logging:

  • Exclude the dependency from the spring-core module (as it is the only module that explicitly depends on commons-logging)
  • Depend on a special commons-logging dependency that replaces the library with an empty jar
兩步驟切換日志系統
  • 從spring-core包中排除掉commons-logging(Spring-core是唯一依賴該包的)
  • 依賴一個特殊的空的commons-logging包

SLF4J is a cleaner dependency and more efficient at runtime than commons-logging because it uses compile-time bindings instead of runtime discovery of the other logging frameworks it integrates. This also means that you have to be more explicit about what you want to happen at runtime, and declare it or configure it accordingly. SLF4J provides bindings to many common logging frameworks, so you can usually choose one that you already use, and bind to that for configuration and management.

SLF4J是編譯時綁定其他日志架構,因而比起commons-logging的運作時發現方式效率要高。這也意味着,你不得不明确指定、明确聲明和配置好運作時的日志系統。SLF4J支援目前多種通用日志架構,你可以綁定一種你目前使用的日志架構。

SLF4J provides bindings to many common logging frameworks, including JCL, and it also does the reverse: bridges between other logging frameworks and itself. So to use SLF4J with Spring you need to replace the commons-logging dependency with the SLF4J-JCL bridge. Once you have done that then logging calls from within Spring will be translated into logging calls to the SLF4J API, so if other libraries in your application use that API, then you have a single place to configure and manage logging.

SLF4J支援綁定包括JCL在内的多種通用日志架構:橋接不同日志架構和自身。如果想在Spring中使用SLF4J,你需要使用SLF4J-JCL替換commons-logging。一旦你替換完成,Spring的日志記錄将轉換為SLF4J API, 是以在你的應用中如果其他包也是用這些API, 那麼你隻需要在一個地方進行配置日志參數即可。

A common choice might be to bridge Spring to SLF4J, and then provide explicit binding from SLF4J to Log4J. You need to supply 4 dependencies (and exclude the existing commons-logging): the bridge, the SLF4J API, the binding to Log4J, and the Log4J implementation itself.

一個通常的選擇是把Spring日志橋接到SLF4J,然後再明确将SLF4J綁定到Log4J,你需要提供4個依賴包(不包含commons-logging包):JCL橋接器(SLF4J-JCL)、SLF4J API、Log4J橋接器(SLF4J-Log4J)、Log4J包。

That might seem like a lot of dependencies just to get some logging. Well it is, but it is optional, and it should behave better than the vanilla commons-logging with respect to classloader issues, notably if you are in a strict container like an OSGi platform. Allegedly there is also a performance benefit because the bindings are at compile-time not runtime.

這似乎引入了很多依賴包卻獲得相同的日志記錄,事實的确如此,但它的效果是要好于直接使用commons-logging方式,因為commons-logging有classloader問題,特别是當你在資源有限的平台時,例如OSGI。另一方面是性能問題,橋接是在編譯階段,而不是運作階段。

A more common choice amongst SLF4J users, which uses fewer steps and generates fewer dependencies, is to bind directly to Logback. This removes the extra binding step because Logback implements SLF4J directly, so you only need to depend on two libraries not four ( jcl-over-slf4j and logback). If you do that you might also need to exclude the slf4j-api dependency from other external dependencies (not Spring), because you only want one version of that API on the classpath.

在SLF4J圈子,另一個常用的選擇是綁定Logback,它使用了更少的步驟,并産生更少的依賴。使用Logback可以去除綁定步驟,因為它是直接實作自SLF4J,是以你隻需要依賴2個jar包(jcl-over-slf4j和logback)。如果你這樣做,你還是需要将其他包中(非Spring)的slf4j-api依賴去除,因為你隻需要一個版本的SLF4J API

注:實際應該需要引入3個依賴(SLF4J-JCL、SLF4J API、Logback)

Gradle建構器配置

//将commons-logging包從目前依賴中剔除
configurations {
    compile.exclude group:'commons-logging'
    testCompile.exclude group:'commons-logging'
}

//引入日志橋接包和slf4j
def logger = [
            "org.slf4j:jcl-over-slf4j:$slf4j_version",
            "org.slf4j:slf4j-api:$slf4j_version",
            "org.slf4j:slf4j-log4j12:$slf4j_version",
            "log4j:log4j:$log4j_version",
    ]
compile logger
testCompile logger
           

Maven建構器配置

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.5.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.5.8</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.14</version>
    </dependency>
</dependencies>
           

注意:如果在依賴中剔除了common-loggin包,但又不引入其他日志系統,則Spring會在啟動的時候報如下錯誤:

java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory

結果示例

bavatinolabdeMacBook-Pro:webflow bavatinolab$ ./gradlew -q dependencies --configuration compile

------------------------------------------------------------
Root project
------------------------------------------------------------

compile - Dependencies for source set 'main'.
+--- org.springframework:spring-context:..RELEASE
|    +--- org.springframework:spring-aop:..RELEASE
|    |    +--- org.springframework:spring-beans:..RELEASE
|    |    |    \--- org.springframework:spring-core:..RELEASE
|    |    \--- org.springframework:spring-core:..RELEASE
|    +--- org.springframework:spring-beans:..RELEASE (*)
|    +--- org.springframework:spring-core:4.3.4.RELEASE
|    \--- org.springframework:spring-expression:4.3.4.RELEASE
|         \--- org.springframework:spring-core:4.3.4.RELEASE
+--- org.springframework:spring-test:4.3.4.RELEASE
|    \--- org.springframework:spring-core:4.3.4.RELEASE
+--- org.springframework:spring-tx:4.3.4.RELEASE
|    +--- org.springframework:spring-beans:4.3.4.RELEASE (*)
|    \--- org.springframework:spring-core:..RELEASE
+--- org.springframework:spring-web:..RELEASE
|    +--- org.springframework:spring-aop:..RELEASE (*)
|    +--- org.springframework:spring-beans:4.3.4.RELEASE (*)
|    +--- org.springframework:spring-context:..RELEASE (*)
|    \--- org.springframework:spring-core:4.3.4.RELEASE
+--- org.springframework:spring-webmvc:4.3.4.RELEASE
|    +--- org.springframework:spring-aop:4.3.4.RELEASE (*)
|    +--- org.springframework:spring-beans:..RELEASE (*)
|    +--- org.springframework:spring-context:4.3.4.RELEASE (*)
|    +--- org.springframework:spring-core:..RELEASE
|    +--- org.springframework:spring-expression:..RELEASE (*)
|    \--- org.springframework:spring-web:4.3.4.RELEASE (*)
+--- org.slf4j:jcl-over-slf4j:.
|    \--- org.slf4j:slf4j-api:.
+--- org.slf4j:slf4j-api:.
+--- org.slf4j:slf4j-log4j12:.
|    +--- org.slf4j:slf4j-api:.
|    \--- log4j:log4j:.
+--- log4j:log4j:.
+--- junit:junit:
|    \--- org.hamcrest:hamcrest-core:
+--- com.google.protobuf:protobuf-java:.
+--- com.google.protobuf:protobuf-java-util:.
|    +--- com.google.protobuf:protobuf-java:.
|    +--- com.google.guava:guava:
|    \--- com.google.code.gson:gson:
+--- org.mockito:mockito-all:.-beta
\--- org.testng:testng:.
     +--- com.google.inject:guice:
     |    +--- javax.inject:javax.inject:
     |    +--- aopalliance:aopalliance:
     |    \--- com.google.guava:guava:. -> 
     +--- org.beanshell:bsh:b4
     +--- org.apache.ant:ant:.
     |    \--- org.apache.ant:ant-launcher:.
     +--- com.beust:jcommander:
     +--- org.yaml:snakeyaml:
     \--- junit:junit: ->  (*)

(*) - dependencies omitted (listed previously)
           
Spring 切換日志系統

可以看到,LogFactory已經被替換為SLF4J的實作。

Spring 切換日志系統

Slf4j使用的是靜态綁定

容器日志

某些web容器自身攜帶有JCL的日志系統實作,某些容器比如WAS,會使用父類優先的類加載機制,造成JCL使用的是容器的版本,而不是應用的中指定的commons-logging版本,因而會出現綁定失敗等意料之外問題。

解決辦法是修改類加載層級,例如was中調整為“parent last”,確定容器的JCL優先加載。

官方說明

Many people run their Spring applications in a container that itself provides an implementation of JCL. IBM Websphere Application Server (WAS) is the archetype. This often causes problems, and unfortunately there is no silver bullet solution; simply excluding commons-logging from your application is not enough in most situations.

很多人将他們的Spring應用運作在某些自身就已經提供了JCL實作的容器裡。IBM Websphere應用伺服器(WAS)就是典型。這通常會引起問題,而且非常不幸,這個沒有銀彈方案。簡單的從應用中剔除掉commons-logging包不是最優方案。

To be clear about this: the problems reported are usually not with JCL perse, or even with commons-logging: rather they are to do with binding commons-logging to another framework (often Log4J). This can fail because commons-logging changed the way they do the runtime discovery in between the older versions (1.0) found in some containers and the modern versions that most people use now (1.1). Spring does not use any unusual parts of the JCL API, so nothing breaks there, but as soon as Spring or your application tries to do any logging you can find that the bindings to Log4J are not working.

需要明确的是:這個問題不是由于JCL或commons-logging引起,即便他們想把commons-logging綁定到其他架構(常常是Log4J)。引起失敗的原因是,容器中的舊版本commons-logging(1.0)和目前應用中的新版本commons-logging(1.1)沖突。Spring并沒有使用任何不确定的JCL API,是以問題不會發生在Spring。但是當代碼進行日志記錄的時候,就會發現綁定到Log4J失效。

In such cases with WAS the easiest thing to do is to invert the class loader hierarchy (IBM calls it “parent last”) so that the application controls the JCL dependency, not the container. That option isn’t always open, but there are plenty of other suggestions in the public domain for alternative approaches, and your mileage may vary depending on the exact version and feature set of the container.

如果在WAS中出現這種問題,一種簡便做法是,颠倒WAS的類加載層次(IBM稱之為“父親後置”),使得應用控制JCL的依賴,而不是容器控制。