Spring 中的日誌依賴

工程 | Dave Syer | 2009 年 12 月 04 日 | ...

本文討論了 Spring 在日誌方面的選擇以及開發者在使用 Spring 構建應用程式時可用的選項。本文釋出時間恰逢 Spring 3.0 即將釋出,這不是因為我們做了很多改動(儘管現在我們對依賴元資料更加謹慎),而是為了讓您可以就如何在應用程式中實現和配置日誌做出明智的決定。首先,我們簡要介紹 Spring 中的強制性依賴,然後更詳細地討論如何設定應用程式以使用一些常見日誌庫的示例。作為一個例子,我將使用 Maven Central 風格的 Artifact 命名約定來展示依賴配置。

Spring 依賴項和依賴 Spring

儘管 Spring 為各種企業及其他外部工具提供了整合和支援,但它有意將其強制性依賴項保持在最低限度:您不應該為了使用 Spring 處理簡單用例而需要定位和下載(即使是自動下載)大量 jar 庫。對於基本的依賴注入,只有一個強制性的外部依賴項,那就是日誌(有關日誌選項的更詳細描述,請參見下文)。如果您使用 Maven 進行依賴管理,甚至無需明確提供日誌依賴項。例如,要建立應用程式上下文並使用依賴注入配置應用程式,您的 Maven 依賴項將如下所示

<dependencies>
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.2.RELEASE</version>
      <scope>runtime</scope>
   </dependency>
</dependencies>

就是這樣。請注意,如果您不需要針對 Spring API 進行編譯,可以將 scope 宣告為 runtime,這通常適用於基本的依賴注入用例。

我們在上面的示例中使用了 Maven Central 的命名約定,因此它適用於 Maven Central 或 SpringSource S3 Maven 倉庫。要使用 S3 Maven 倉庫(例如用於里程碑版本或開發者快照),您需要在 Maven 配置中指定倉庫位置。對於正式釋出版本

<repositories>
   <repository>
      <id>com.springsource.repository.maven.release</id>
      <url>http://maven.springframework.org/release/</url>
      <snapshots><enabled>false</enabled></snapshots>
   </repository>
</repositories>

對於里程碑版本

<repositories>
   <repository>
      <id>com.springsource.repository.maven.milestone</id>
      <url>http://maven.springframework.org/milestone/</url>
      <snapshots><enabled>false</enabled></snapshots>
   </repository>
</repositories>

對於快照版本

<repositories>
   <repository>
      <id>com.springsource.repository.maven.snapshot</id>
      <url>http://maven.springframework.org/snapshot/</url>
      <snapshots><enabled>true</enabled></snapshots>
   </repository>
</repositories>

要使用 SpringSource EBR,您需要對依賴項使用不同的命名約定。名稱通常很容易猜到,例如在這種情況下是

<dependencies>
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.context</artifactId>
      <version>3.0.0.RELEASE</version>
      <scope>runtime</scope>
   </dependency>
</dependencies>

您還需要明確宣告倉庫的位置(只有 URL 重要)

<repositories>
   <repository>
      <id>com.springsource.repository.bundles.release</id>
      <url>http://repository.springsource.com/maven/bundles/release/</url>
   </repository>
</repositories>

如果您手動管理依賴項,上面倉庫宣告中的 URL 是無法直接瀏覽的,但在 <a href="http://www.springsource.com/repository">http://www.springsource.com/repository 有一個使用者介面,可用於搜尋和下載依賴項。它還提供了方便的 Maven 和 Ivy 配置程式碼片段,如果您使用這些工具,可以直接複製貼上。

如果您更喜歡使用 Ivy 來管理依賴項,那麼那裡也有類似的名稱和配置選項(請參考您的依賴管理系統的文件,或檢視一些示例程式碼 - Spring 本身在構建時就使用 Ivy 來管理依賴項)。

日誌

日誌對於 Spring 來說是一個非常重要的依賴項,原因如下:a) 它是唯一強制性的外部依賴項;b) 每個人都喜歡看到他們使用的工具輸出一些內容;c) Spring 集成了許多其他工具,這些工具也都選擇了自己的日誌依賴項。應用程式開發人員的目標之一通常是將整個應用程式(包括所有外部元件)的日誌配置集中在一個地方。這比想象的要困難,因為日誌框架的選擇太多了。

Spring 中強制性的日誌依賴項是 Jakarta Commons Logging API (JCL)。我們針對 JCL 進行編譯,並且還使得擴充套件 Spring Framework 的類可以訪問 JCL Log 物件。對使用者來說很重要的一點是,所有版本的 Spring 都使用相同的日誌庫:遷移很容易,因為即使對於擴充套件 Spring 的應用程式,我們也保留了向後相容性。我們這樣做的方式是讓 Spring 中的一個模組明確依賴於 commons-logging(JCL 的規範實現),然後在編譯時讓所有其他模組都依賴於它。例如,如果您使用 Maven,並想知道您是從哪裡獲取 commons-logging 依賴項的,那麼它來自 Spring,具體來說是來自名為 spring-core 的核心模組。

commons-logging 的好處在於您不需要其他任何東西就能讓您的應用程式工作。它有一個執行時發現演算法,會在類路徑上已知的位置查詢其他日誌框架,並使用它認為合適的那個(或者如果您需要,可以指定一個)。如果沒有其他可用的日誌框架,您也可以只從 JDK (java.util.logging,簡稱 JUL) 獲得相當不錯的日誌輸出。您會發現在大多數情況下,您的 Spring 應用程式開箱即用就能正常工作並將日誌愉快地輸出到控制檯,這很重要。

不使用 Commons Logging - Spring 框架

不幸的是,commons-logging 最糟糕的地方,也是導致它在新工具中不受歡迎的原因,恰恰是其執行時發現演算法。如果時光倒流,現在重新開始 Spring 專案,它會使用不同的日誌依賴項。首選很可能是 Simple Logging Facade for Java (SLF4J),許多在應用程式中與 Spring 一起使用的其他工具也使用了 SLF4J。

關閉 commons-logging 很簡單:只需確保它在執行時不在類路徑上即可。用 Maven 的術語來說,就是排除該依賴項,並且由於 Spring 依賴項的宣告方式,您只需要排除一次。

<dependencies>
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.2.RELEASE</version>
      <scope>runtime</scope>
      <exclusions>
         <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
         </exclusion>
      </exclusions>
   </dependency>
</dependencies>

現在,這個應用程式很可能已經無法正常工作,因為類路徑上沒有 JCL API 的實現。因此,為了解決這個問題,必須提供一個新的實現。在下一節中,我們將以 SLF4J 為例,展示如何提供 JCL 的替代實現。

使用 SLF4J

SLF4J 是一種更簡潔的依賴項,並且在執行時比 commons-logging 更高效,因為它使用編譯時繫結,而不是對它整合的其他日誌框架進行執行時發現。這也意味著您必須更明確地說明在執行時希望發生什麼,並據此宣告或配置。SLF4J 提供了與許多常見日誌框架的繫結,因此您通常可以選擇一個您已經在使用的框架,並繫結到它進行配置和管理。

SLF4J 提供了與許多常見日誌框架的繫結,包括 JCL,它也提供了反向功能:其他日誌框架與 SLF4J 之間的橋接。因此,要將 SLF4J 與 Spring 一起使用,您需要用 SLF4J-JCL 橋接替換 commons-logging 依賴項。完成此操作後,來自 Spring 內部的日誌呼叫將被轉換為對 SLF4J API 的日誌呼叫,因此如果應用程式中的其他庫也使用該 API,那麼您就可以在一個地方配置和管理日誌了。

一種常見的選擇可能是將 Spring 橋接到 SLF4J,然後提供從 SLF4J 到 Log4J 的顯式繫結。您需要提供 4 個依賴項(並排除現有的 commons-logging):橋接器、SLF4J API、到 Log4J 的繫結以及 Log4J 實現本身。在 Maven 中,您可以這樣做

<dependencies>
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.2.RELEASE</version>
      <scope>runtime</scope>
      <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.0</version>
      <scope>runtime</scope>
   </dependency>
   <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.0</version>
      <scope>runtime</scope>
   </dependency>
   <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.0</version>
      <scope>runtime</scope>
   </dependency>
   <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
      <scope>runtime</scope>
   </dependency>
</dependencies>

僅僅為了獲取一些日誌輸出,這看起來可能需要很多依賴項。確實如此,但它是可選的,並且在處理類載入器問題方面應該比普通的 commons-logging 表現更好,特別是在像 OSGi 平臺這樣的嚴格容器中。據說編譯時繫結而不是執行時繫結也能帶來效能上的好處。

在 SLF4J 使用者中,一個更常見的選擇是直接繫結到 Logback,這種方法步驟更少,生成的依賴項也更少。這省去了額外的繫結步驟,因為 Logback 直接實現了 SLF4J,所以您只需要依賴兩個庫而不是四個(jcl-over-slf4j 和 logback)。如果您這樣做,您可能還需要從其他外部依賴項(非 Spring)中排除 slf4j-api 依賴項,因為您只希望類路徑上存在該 API 的一個版本。

使用 Log4J

許多人使用 <a href="http://logging.apache.org/log4j">Log4j 作為日誌框架進行配置和管理。它高效且成熟,事實上,我們在構建和測試 Spring 時執行時使用的就是它。Spring 還提供了一些用於配置和初始化 Log4j 的工具類,因此在某些模組中它對 Log4j 有可選的編譯時依賴。

要讓 Log4j 與預設的 JCL 依賴項 (commons-logging) 一起工作,您只需將 Log4j 放到類路徑上,併為其提供一個配置檔案(在類路徑的根目錄下放置 log4j.properties 或 log4j.xml)。所以對於 Maven 使用者來說,這是您的依賴宣告

<dependencies>
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.2.RELEASE</version>
      <scope>runtime</scope>
   </dependency>
   <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
      <scope>runtime</scope>
   </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

使用原生 JCL 的執行時容器

許多人在本身提供了 JCL 實現的容器中執行 Spring 應用程式。IBM Websphere Application Server (WAS) 是典型的例子。這經常會導致問題,不幸的是,沒有放之四海而皆準的解決方案;在大多數情況下,僅僅從您的應用程式中排除 commons-logging 是不夠的。

需要明確的是:報告的問題通常與 JCL 本身無關,甚至與 commons-logging 也無關:相反,它們與將 commons-logging 繫結到另一個框架(通常是 Log4J)有關。這可能會失敗,因為 commons-logging 在某些容器中發現的舊版本(1.0)與大多數人現在使用的現代版本(1.1)之間改變了執行時發現的方式。Spring 沒有使用 JCL API 的任何非尋常部分,所以在 Spring 內部不會出現問題,但是一旦 Spring 或您的應用程式嘗試進行任何日誌記錄,您可能會發現與 Log4J 的繫結無法正常工作。

在這種使用 WAS 的情況下,最簡單的做法是反轉類載入器層次結構(IBM 稱之為“父級優先”的相反:“parent last”),以便應用程式控制 JCL 依賴項,而不是容器。這個選項並非總是可用,但公共領域有很多其他替代方法的建議,具體效果取決於容器的確切版本和功能集。

(注意:Spring 和 slf4j 的版本已從原文更新,以便可以直接複製貼上。)

獲取 Spring 電子報

訂閱 Spring 電子報,保持聯絡

訂閱

搶先一步

VMware 提供培訓和認證,助您加速前進。

瞭解更多

獲取支援

Tanzu Spring 透過一份簡單的訂閱,為 OpenJDK™、Spring 和 Apache Tomcat® 提供支援和二進位制檔案。

瞭解更多

近期活動

檢視 Spring 社群的所有近期活動。

檢視全部