http://openwebx.org/docs/logging.html#d0e15858
第 11 章 Webx日志系统的配置
/WEB-INF/logback.xml
(或/WEB-INF/log4j.xml
)
NoSuchMethodError
: org.slf4j.MDC.getCopyOfContextMap()
STDERR
输出:Class path contains multiple SLF4J bindings
日志系统是一个应用中必备的部分,提供了查看错误信息、了解系统状态的最直接手段。
本章介绍了基于Webx框架的应用如何配置、使用日志系统的方法。
表 11.1. 日志系统
名称 | 说明 |
---|---|
Log4j | http://logging.apache.org/log4j/ 较早出现的比较成功的日志系统是Log4j。 Log4j开创的日志系统模型(Logger/Appender/Level)行之有效,并一直延用至今。 |
JUL(java.util.logging.* ) |
http://download.oracle.com/javase/6/docs/technotes/guides/logging/overview.html JDK1.4是第一个自带日志系统的JDK,简称(JUL)。 JUL并没有明显的优势来战胜Log4j,反而造成了标准的混乱 —— 采用不同日志系统的应用程序无法和谐共存。 |
Logback | http://logback.qos.ch/ 是较新的日志系统。 它是Log4j的作者吸取多年的经验教训以后重新做出的一套系统。它的使用更方便,功能更强,而且性能也更高。 Logback不能单独使用,必须配合日志框架SLF4J来使用。 |
JUL诞生以后,为了克服多种日志系统并存所带来的混乱,就出现了“日志框架”。日志框架本身不提供记录日志的功能,它只提供了日志调用的接口。日志框架依赖于实际的日志系统如Log4j或JUL来产生真实的日志。
使用日志框架的好处是:应用的部署者可以决定使用哪一种日志系统(Log4j还是JUL),或者在多种日志系统之间切换,而不需要更改应用的代码。
表 11.2. 日志框架
名称 | 说明 |
---|---|
JCL(Jakarta Commons Logging) | http://commons.apache.org/logging/ 这是目前最流行的一个日志框架,由Apache Jakarta社区提供。 Spring框架、许多老应用都依赖于JCL。 |
SLF4J | http://www.slf4j.org/ 这是一个最新的日志框架,由Log4j的作者推出。 SLF4J提供了新的API,特别用来配合Logback的新功能。但SLF4J同样兼容Log4j。 |
由于Log4j原作者的感召力,SLF4J和Logback很快就流行起来。Webx的新版本也决定使用SLF4J作为其日志框架;并推荐Logback作为日志系统,但同时支持Log4J。
要在应用中使用日志系统,必须把正确的jar包组装起来。本章假设你的应用是用maven构建的。
如图所示,
由于JCL-over-SLF4J和原来的JCL具有完全相同的API,因此两者是不能共存的。
Logback和slf4j-log4j12也不能并存,否则SLF4J会迷惑并产生不确定的结果。
组装完整的日志系统将涉及如下部件:
表 11.3. 日志系统的组成
类别 | 组件名称 | 说明 |
---|---|---|
日志框架 | SLF4J | Webx框架以及所有新应用,直接依赖于SLF4J。 |
JCL | Spring框架、许多以前的老应用,都使用JCL来输出日志。 好在SLF4J提供了一个“桥接”包:JCL-over-SLF4J,它重写了JCL的API,并将所有日志输出转向SLF4J。这样就避免了两套日志框架并存的问题。 |
|
日志系统 | Logback | Webx推荐使用logback来取代log4j。 Logback可直接被SLF4J识别并使用。 |
Log4j | 由于客观原因,有些系统暂时不能升级到Logback。 好在SLF4J仍然支持Log4j。Log4j需要一个适配器slf4j-log4j12才能被SLF4J识别并使用。 |
例 11.1. 配置pom.xml
以使用logback
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> <scope>runtime</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement>
|
把所依赖jar包的版本定义在 如果你的项目指定了parent pom,那么建议把 |
|
将logback日志系统的依赖设定为 |
|
由于和jcl-over-slf4j存在冲突,因此JCL(commons-logging)是必须被排除的。由于maven目前缺少这样一个功能:它不能全局地排除一个jar包依赖,所以建议将commons-logging设置成 |
将commons-logging设置成<scope>provided</scope>
可以用来排除commons-logging,然而这样做有一个缺点 —— 你无法从单元测试中将commons-logging排除。假如这个影响了你的单元测试的话,请使用另一种方案:
例 11.2. 另一种排除commons-logging的方法
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>99.0-does-not-exist</version> </dependency>
|
“ |
最后,你需要在项目文件夹下面,执行一下命令:“mvn dependency:tree
”,确保没有jar包直接或间接依赖了slf4j-log4j12。如果有的话,你可以用下面的配置来排除掉:
例 11.3. 排除间接依赖的slf4j-log4j12
<dependencyManagement> <dependencies> <dependency> <groupId>yourGroupId</groupId> <artifactId>yourArtifactId</artifactId> <version>yourVersion</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement>
事实上,如果有其它的jar包依赖slf4j-log4j12,这本身就是有错误的。因为应用不应该直接依赖于这些包中的API —— 它们只应该依赖于日志框架API。任何应用都应该把下列和日志系统相关的依赖(如:slf4j-log4j12、logback-classic)设置成<scope>runtime</scope>
的。
例 11.4. 配置pom.xml
以使用log4j
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> <scope>runtime</scope> </dependency> </dependencies> </dependencyManagement>
配置log4j的方法和关注要点和logback相似,请参见例 11.1 “配置pom.xml
以使用logback”。除此以外,你需要在项目文件夹下面,执行一下命令:“mvn dependency:tree
”,确保没有jar包间接依赖了logback-classic。如果有的话,你可以用下面的配置来排除掉:
例 11.5. 排除间接依赖的logback-classic
<dependencyManagement> <dependencies> <dependency> <groupId>yourGroupId</groupId> <artifactId>yourArtifactId</artifactId> <version>yourVersion</version> <exclusions> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement>
事实上,如果有其它的jar包依赖logback-classic,这本身就是有错误的。因为应用不应该直接依赖于这些包中的API —— 它们只应该依赖于日志框架API。任何应用都应该把下列和日志系统相关的依赖(如:slf4j-log4j12、logback-classic)设置成<scope>runtime</scope>
的。
例 11.6. 设置/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd "> <context-param> <param-name>loggingRoot</param-name> <param-value>/tmp/logs</param-value> </context-param> <context-param> <param-name>loggingLevel</param-name> <param-value>INFO</param-value> </context-param> <context-param> <param-name>loggingCharset</param-name> <param-value>UTF-8</param-value> </context-param> <context-param> <param-name>log...</param-name> <param-value>...</param-value> </context-param> <listener> <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class> </listener> <filter> <filter-name>mdc</filter-name> <filter-class>com.alibaba.citrus.webx.servlet.SetLoggingContextFilter</filter-class> </filter> <filter-mapping> <filter-name>mdc</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
|
指定日志系统的参数。 |
|
在WEB应用启动的时候,这个listener会被激活,并初始化日志系统。 |
|
将当前请求的信息放到日志系统的MDC中(Mapped Diagnostic Context)。 |
可以在/WEB-INF/web.xml
配置文件中集中定义日志系统的参数。
表 11.4. 可配置的日志参数
参数名称 | 说明 |
---|---|
loggingRoot |
指定保存日志文件的根目录。如不指定,默认为:“${user.home}/logs ”。 |
loggingLevel |
指定日志级别,低于指定级别的日志将不被输出。如不指定,默认为“INFO ”。 |
loggingCharset |
指定用来生成日志文件的字符集编码。如不指定,默认为当前操作系统的默认字符集编码。 |
log* |
名称以“log ”开头的任意<context-param> 参数,都将被用作日志系统的参数。 |
日志系统的参数可被替换到log4j或logback的配置中去,例如,在logback的配置文件中,你可以指定${loggingRoot}
来取得存放日志文件的根目录。
将日志参数配置在/WEB-INF/web.xml
中,有如下优点:
使一套配置参数可同时应用于任意日志系统,包括logback和log4j。
便于管理。通常,我们可以利用maven的filtering机制,或者autoconfig插件来生成/WEB-INF/web.xml
文件,以便定制上述参数。
LogConfiguratorListener
负责在系统启动的时候初始化日志系统。LogConfiguratorListener
会根据下面所列的条件,来自动识别出当前的日志系统,并正确地配置它:
假如你在maven的pom.xml
中指定log4j为日志系统,那么该listener就会试图用/WEB-INF/log4j.xml
来初始化日志系统。
假如你在maven的pom.xml
中指定logback为日志系统,那么该listener就会试图用/WEB-INF/logback.xml
来初始化日志系统。
假如你在maven的pom.xml
中未指定任何日志系统(不存在logback-classic或slf4j-log4j12),那么listener会报错并失败,整个WEB应用会退出,服务器报告应用启动失败。
假如/WEB-INF/logback.xml
(或/WEB-INF/log4j.xml
)不存在,那么listener会用默认的配置文件来初始化日志。默认的配置会:
把WARN
级别以上的日志打印在STDERR
中,
把WARN
级别以下的日志打印在STDOUT
中。
SetLoggingContextFilter
将当前请求的信息放到日志系统的MDC中(Mapped Diagnostic Context)。这样,日志系统就可以打印出诸如下面所示的日志信息:
例 11.7. 利用MDC输出的日志
30377 [2010-06-02 15:24:29] - GET /wrongpage.htm [ip=127.0.0.1, ref=http://localhost:8081/index, ua=Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3, sid=scnd32ei11a7] ……
这段日志信息中包含了如下信息:
|
用户请求的类型: |
|
请求的URI: |
|
用户IP: |
|
上一个页面的链接referrer: |
|
用户的浏览器:Mac版的mozilla浏览器。 |
|
Session ID: |
用户客户端的详细信息,对于发现和追踪错误非常有帮助。
SetLoggingContextFilter
是一个可选的filter —— 即使没有它,Webx的<setLoggingContext />
valve也会做同样的事情。但是把这些信息放在filter中,有利于及早记录用户的信息。
/WEB-INF/logback.xml
(或/WEB-INF/log4j.xml
)在日志配置文件中,你可以使用以下参数:
表 11.5. 日志配置文件中可用的参数
在/WEB-INF/web.xml 中定义的所有日志参数 |
|
---|---|
${loggingRoot} |
代表保存日志文件的根目录。 |
${loggingCharset} |
代表用来生成日志文件的字符集编码。 |
${loggingLevel} |
代表日志级别,低于指定级别的日志将不被输出。 |
${log*} |
自定义参数,其中“* ”代表任意名称。 |
由系统自动取得的参数 | |
---|---|
${loggingHost} |
代表当前的服务器名称 |
${loggingAddress} |
代表当前的服务器IP地址 |
在appender pattern中,你可以使用以下MDC参数:
表 11.6. 日志配置文件中可用的MDC参数
参数名 | 说明 |
---|---|
%X{productionMode} |
系统运行的模式。如果系统以开发模式运行,将会显示Development Mode ;否则将会显示Production Mode 。在生产环境中启动开发模式会引起严重的性能和安全问题。将系统运行的模式打印在日志中,可以作为一种提醒。 |
%X{method} |
请求类型,如:GET 或POST |
%X{requestURL} |
完整的URL,如:http://localhost/test |
%X{requestURLWithQueryString} |
完整的URL,以及query string,如:http://localhost/test?id=1 |
%X{requestURI} |
不包括host信息的URI,如:/test |
%X{requestURIWithQueryString} |
不包括host信息的URI,以及query string,如:/test?id=1 |
%X{queryString} |
URL参数,如:id=1 |
%X{remoteAddr} |
客户端地址 |
%X{remoteHost} |
客户端域名 |
%X{userAgent} |
客户端浏览器信息 |
%X{referrer} |
上一个页面的URL |
%X{cookies} |
所有cookies的名称,如:[cookie1, cookie2] |
%X{cookie.*} |
特定cookie的值,如:%X{cookie.JSESSIONID} ,将显示当前session的ID |
%X{*} |
其它由应用程序或框架置入MDC的参数 |
例 11.8. Logback配置示例(/WEB-INF/logback.xml
)
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <target>System.out</target> <encoding>${loggingCharset}</encoding> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern><![CDATA[ %n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n ]]></pattern> </layout> <filter class="com.alibaba.citrus.logconfig.logback.LevelRangeFilter"> <levelMax>INFO</levelMax> </filter> </appender> <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender"> <target>System.err</target> <encoding>${loggingCharset}</encoding> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern><![CDATA[ %n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n ]]></pattern> </layout> <filter class="com.alibaba.citrus.logconfig.logback.LevelRangeFilter"> <levelMin>WARN</levelMin> </filter> </appender> <appender name="PROJECT" class="ch.qos.logback.core.FileAppender"> <file>${loggingRoot}/${localHost}/petstore.log</file> <encoding>${loggingCharset}</encoding> <append>false</append> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern><![CDATA[ %n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n ]]></pattern> </layout> </appender> <root> <level value="${loggingLevel}" /> <appender-ref ref="STDERR" /> <appender-ref ref="STDOUT" /> <appender-ref ref="PROJECT" /> </root> </configuration>
更详细配置方法请参考logback官方文档:http://logback.qos.ch/manual/configuration.html。
请特别留意示例中参数的写法,如“${loggingRoot}
”;以及appender pattern中MDC参数的的写法,如:“%X{method}
”、“%X{requestURIWithQueryString}
”等。
例 11.9. Log4j配置示例(/WEB-INF/log4j.xml
)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="target" value="System.out" /> <param name="encoding" value="${loggingCharset}" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n" /> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="levelMax" value="INFO" /> </filter> </appender> <appender name="STDERR" class="org.apache.log4j.ConsoleAppender"> <param name="target" value="System.err" /> <param name="encoding" value="${loggingCharset}" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n" /> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="levelMin" value="WARN" /> </filter> </appender> <appender name="PROJECT" class="org.apache.log4j.FileAppender"> <param name="file" value="${loggingRoot}/${localHost}/myapp.log" /> <param name="encoding" value="${loggingCharset}" /> <param name="append" value="true" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n" /> </layout> </appender> <root> <level value="${loggingLevel}" /> <appender-ref ref="STDOUT" /> <appender-ref ref="STDERR" /> <appender-ref ref="PROJECT" /> </root> </log4j:configuration>
更详细配置方法请参考log4j官方文档:http://logging.apache.org/log4j/1.2/manual.html。
请特别留意示例中参数的写法,如“${loggingRoot}
”;以及appender pattern中MDC参数的的写法,如:“%X{method}
”、“%X{requestURIWithQueryString}
”等。
在某些遗留系统中,有些代码直接用到了Log4j API(例如Log4j Appender)。假如,我们仍然希望SLF4J以logback作为日志系统,但是保持这些老代码继续不变地使用log4j来记录日志。这样我们就需要同时初始化logback和log4j。
例 11.10. 同时初始化Logback和Log4j
首先,你需要确保在pom.xml
中,同时包含log4j和logback-classic这两个依赖,但是请一定不要包含slf4j-log4j12这个包,因为它会和logback-classic起冲突。
下面的配置在例 11.1 “配置pom.xml
以使用logback”基础上,添加了log4j的依赖:
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> <scope>provided</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> <scope>runtime</scope> </dependency> </dependencies> </dependencyManagement>
然后,你需要在/WEB-INF/web.xml
中增加logSystem
参数。
下面的配置在例 11.6 “设置/WEB-INF/web.xml
”基础上,添加了所需的参数:
<web-app>
<context-param> <param-name>logSystem</param-name> <param-value>log4j, logback</param-value> </context-param>
...
</web-app>
以上这段/WEB-INF/web.xml
的配置,告诉LogConfiguratorListener
同时初始化两个日志系统:log4j和logback。它们的配置文件分别是:/WEB-INF/log4j.xml
和/WEB-INF/logback.xml
。假如文件不存在也没关系,LogConfiguratorListener
会用系统默认的配置文件来初始化它们。
分析错误前,先检查一下日志系统输出的提示信息,往往可以节省很多时间。当LogConfiguratorListener
启动时,将会在STDERR
中打印信息,像下面这个样子:
例 11.11. 日志初始化时的提示信息(STDERR
)
2010-06-02 16:57:28.021:INFO:/:Initializing log4j system INFO: configuring "log4j" using file:/Users/…/WEB-INF/log4j.xml - with property localAddress = 10.16.58.5 - with property localHost = baobao-macbook-pro.local - with property loggingCharset = UTF-8 - with property loggingLevel = warn - with property loggingRoot = /tmp/logs
通过这些信息,你可以检查如下内容:
|
是否选择了正确的日志系统,如:log4j或logback,抑或两样都有。 |
|
是否选择了正确的日志配置文件,如: |
|
日志文件的参数,如根目录、字符集编码、日志级别等信息。 |
有时,因为各种原因导致应用找到了错误的jar包,从而产生神秘的错误。例如,你以为你使用了SLF4J的最新版,然而在服务器上存在一个SLF4J的老版本,并且其class loader优先级比新版本更高。在这种情况下,应用会引用高优先级class loader中的老版本的class。这可能导致错误。
发现这类错误的有效的方法,是在应用程序的任意点设置断点(利用eclipse远程调试功能),当系统停留在断点处时,执行如下的java代码,查看其值:
例 11.12. 查看class真实归属的jar包位置
getClass().getClassLoader().getResource(getClass().getName().replace('.', '/') + ".class")
另外,Webx开发模式所提供的详细出错页面中,也会列出stacktrace中每一个class的真实jar包位置。
报这个错的原因可能是:
不存在slf4j-log4j12、logback-classic等任何一个日志系统的实现。
Slf4j的版本和日志系统的版本不匹配,例如,slf4j为1.4.3版,而slf4j-log4j12为1.7.5版。
解决方法:
用mvn dependency:tree
命令查看所有的依赖包,排除以上错误。
查看服务器环境(如jboss),查看是不是存在不正确版本的jar包,被优先于应用jar包而加载了。参见第 11.4.1.2 节 “查看class真实归属的jar包位置”。
NoSuchMethodError
: org.slf4j.MDC.getCopyOfContextMap()
报这个错的原因是:
SLF4J的版本过老。MDC.getCopyOfContextMap()方法是从SLF4J 1.5.1时加入的,假如你的SLF4J是之前的版本,就会报错。
解决方法:
用mvn dependency:tree
查看所有的依赖包,排除以上错误。
查看服务器环境(如jboss),查看是不是存在不正确版本的jar包,被优先于应用jar包而加载了。
STDERR
输出:Class path contains multiple SLF4J bindingsSLF4J在STDERR
报如下错误:
例 11.13. Class path contains multiple SLF4J bindings
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/…/WEB-INF/lib/logback-classic-0.9.18.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/…/WEB-INF/lib/slf4j-log4j12-1.5.11.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
报这个错的原因是:
classpath中存在多个日志系统,使SLF4J无所适从。
解决方法:
SLF4J已经列出了classpath中所有的日志系统的位置。根据这些信息,你可以调整应用的依赖,或者整理服务器的环境,使之只剩下一个日志系统。
原因可能是日志的配置文件可能有错。
解决方法:
首先,查看LogConfiguratorListener
输出到STDERR
中的信息(参见第 11.4.1.1 节 “检查提示信息”),确定系统:
选择了正确的日志系统;
选择了正确的配置文件;
设置了正确的参数(loggingRoot
、loggingLevel
等)。
注意 | |
---|---|
在JBOSS环境中, |
假如以上信息均正确,查看日志配置文件/WEB-INF/log4j.xml
或/WEB-INF/logback.xml
,是否引用了正确的参数,例如:${loggingRoot}
、${loggingLevel}
等。
检查文件系统权限,确保应用有权限创建和修改日志文件。
假设你使用log4j作为日志系统,以jboss作为应用服务器。在JBOSS环境中,当log4j被初始化后,STDOUT
和STDERR
可能会被重新配置到不同的appender中。原先用来记录STDOUT和STDERR的日志文件log/server.log
将不会再被使用。建议你设置/WEB-INF/log4j.xml
,增加如下内容:
例 11.14. 在log4j中配置jboss服务器日志
<appender name="JBOSS_APPENDER" class="org.apache.log4j.FileAppender"> <param name="file" value="${loggingRootJboss}/server.log" /> <param name="encoding" value="${loggingCharset}" /> <param name="append" value="true" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n" /> </layout> </appender> <logger name="STDOUT"> <appender-ref ref="JBOSS_APPENDER" /> </logger> <logger name="STDERR"> <appender-ref ref="JBOSS_APPENDER" /> </logger>
这里用到了一个新的变量:${loggingRootJboss}
,你需要把它定义在/WEB-INF/web.xml
中。
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
...
<context-param>
<param-name>loggingRootJboss</param-name>
<param-value>${jboss}/log</param-value>
</context-param>
...
</web-app>
如果你使用logback作为日志系统,则不需要作如上配置。
LogConfiguratorListener
目前只提供了logback和log4j的支持,尽管支持一种新的日志系统是非常容易的,但现在看来,这两种日志系统已经足够我们使用了。
LogConfiguratorListener
以SLF4J为基础。SLF4J还提供了更多的功能:
除了log4j和logback以外,SLF4J还支持几种其它的日志系统;
除了jcl-over-slf4j以外,SLF4J还提供了几种对其它legacy日志系统的桥接功能。
详情请见SLF4J的文档:http://www.slf4j.org/docs.html。