Story:松哥最近在調試測試環境的時候,希望在一個Tomcat中部署多個Java Web工程時,遇到一個極不明顯的問題(日志內容太多,troubleshouting的時候找了半天才看到),后來才知道是Log4j的問題。
問題表現為:
- 在一個tomcat中配置了3個工程,當重啟tomcat后,只有第一個project1能正常訪問,其它project2、project3都訪問不了;(初步斷定是Java工程的問題,也可能是tomcat的server.xml配置有問題)
- troubleshouting的時候,在server.xml中修改配置文件,注釋掉其它兩個service,只留一個project2,重啟tomcat后project2可以正常訪問;相同的方式試了project3也是;(斷定服務器上的tomcat配置是沒問題的)
- 問題應該是在Java工程里,因此反復查看tomcat的log,發現是Log4j的問題;
1.tomcat日志中的報錯內容
#tail -f /usr/local/tomcat/log/catalina.out
Sep 22, 2017 5:46:05 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.util.Log4jConfigListener
java.lang.IllegalStateException: Web app root system property already set to different value: 'webapp.root' = [/data/wwwroot/testmerchant/shop-merchant/] instead of [/data/wwwroot/teststore/shop-store/] - Choose unique values for the 'webAppRootKey' context-param in your web.xml files!
at org.springframework.web.util.WebUtils.setWebAppRootSystemProperty(WebUtils.java:156)
at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:117)
at org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:46)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5118)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5634)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
2.錯誤的原因
日志中有句話如下:
java.lang.IllegalStateException: Web app root system property already set to different value: 'webapp.root' = [/data/wwwroot/project1] instead of [/data/wwwroot/project2] - Choose unique values for the 'webAppRootKey' context-param in your web.xml files!
每個工程系統默認webAppRootKey的值都是webapp.root,webapp.root對應的是項目的根路徑,所以當有兩個使用該值的工程同時在一個Tomcat下面時會報錯,因為一個key只能保存一個value,后初始化的會替換先初始化的,所以遇到這個問題的解決辦法,就是去每個工程中的web.xml里面給webAppRootKey重新賦個值,且每個值不能重復,重啟tomcat就OK了。
3.解決問題的代碼
3.1 修改project1的web.xml
#vim /data/wwwroot/project1/WEB-INF/web.xml
29 <listener>
30 <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
31 </listener>
32
33 <!--app中的web.xml-->
34 <context-param>
35 <param-name>webAppRootKey</param-name>
36 <param-value>app1.root</param-value>
37 </context-param>
3.2 修改project2的web.xml
#vim /data/wwwroot/project2/WEB-INF/web.xml
29 <listener>
30 <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
31 </listener>
32
33 <!--app中的web.xml-->
34 <context-param>
35 <param-name>webAppRootKey</param-name>
36 <param-value>app2.root</param-value>
37 </context-param>
其中,<param-value>的格式建議:[工程名].root
關于Log4j
1.什么是Log4j?
- log4j是一個用Java編寫的可靠,快速和靈活的日志框架(API),它在Apache軟件許可下發布。
- Log4j已經被移植到了C,C++,C#,Perl,Python和Ruby等語言中。
- Log4j是高度可配置的,并可通過在運行時的外部文件配置。它根據記錄的優先級別,并提供機制,以指示記錄信息到許多的目的地,諸如:數據庫,文件,控制臺,UNIX系統日志等。
2.Log4j中有三個主要組成部分:
- loggers: 負責捕獲記錄信息。
- appenders : 負責發布日志信息,以不同的首選目的地。
- layouts: 負責格式化不同風格的日志信息。
3.Log4j的特性
- log4j的是線程安全的
- log4j是經過優化速度的
- log4j是基于一個名為記錄器的層次結構
- log4j的支持每個記錄器多輸出追加器(appender)
- log4j支持國際化。
- log4j并不限于一組預定義的設備
- 日志行為可以使用配置文件在運行時設置
- log4j設計從一開始就是處理Java異常
- log4j使用多個層次,即ALL,TRACE,DEBUG,INFO,WARN,ERROR和FATAL
- 日志輸出的格式可以通過擴展Layout類容易地改變
- 日志輸出的目標,以及在寫入策略可通過實現Appender程序接口改變
- log4j 會故障停止。然而,盡管它肯定努力確保傳遞,log4j不保證每個日志語句將被傳遞到目的地。