Webx3日志系统的配置

Webx3日志系统的配置
1.前言
日志系统是一个应用中必备的部分,提供了查看错误信息、了解系统状态的最直接手段。
本文介绍了基于Webx3框架的应用如何配置、使用日志系统的方法。
2.名词解释
2.1.日志系统(Logging System)
Log4j —— 较早出现的比较成功的日志系统是Log4j。Log4j开创的日志系统模型(Logger/Appender/Level)行之有效,并一直延用至今。
JUL(java.util.logging.*) —— JDK1.4是第一个自带日志系统的JDK,简称(JUL)。JUL并没有明显的优势来战胜Log4j,反而造成了标准的混乱 —— 采用不同日志系统的应用程序无法和谐共存。
Logback —— 是较新的日志系统。它是Log4j的作者吸取多年的经验教训以后重新做出的一套系统。它的使用更方便,功能更强,而且性能也更高。Logback不能单独使用,必须配合日志框架SLF4J来使用。
2.2.日志框架(Logging Framework)
JUL诞生以后,为了克服多种日志系统并存所带来的混乱,就出现了“日志框架”。日志框架本身不提供记录日志的功能,它只提供了日志调用的接口。日志框架依赖于实际的日志系统如Log4j或JUL来产生真实的日志。
使用日志框架的好处是:应用的部署者可以决定使用哪一种日志系统(Log4j还是JUL),或者在多种日志系统之间切换,而不需要更改应用的代码。
JCL(Jakarta Commons Logging) —— 这是目前最流行的一个日志框架,由Apache Jakarta社区提供。Spring框架、Webx2框架都依赖于JCL。
SLF4J —— 这是一个最新的日志框架,由Log4j的作者推出。SLF4J提供了新的API,特别用来配合Logback的新功能。但SLF4J同样兼容Log4j。
由于Log4j原作者的感召力,SLF4J和Logback很快就流行起来。Webx的新版本也决定使用SLF4J作为其日志框架;并推荐Logback作为日志系统,但同时支持Log4J。
3.在Maven中组装日志系统
要在应用中使用日志系统,必须把正确的jar包组装起来。本文假设你的应用是用maven构建的。对于其它构建系统如antx,原理相同,依此类推即可。

如图所示,组装完整的日志系统将涉及如下jar包:
1. 日志框架SLF4J —— Webx3框架以及所有新应用,直接依赖于SLF4J。
2. 日志框架JCL —— Webx2框架、Spring框架、以及以前的老应用,都使用JCL来输出日志。好在SLF4J提供了一个“桥接”包:JCL-over-SLF4J,它重写了JCL的API,并将所有日志输出转向SLF4J。这样就避免了两套日志框架并存的问题。
3. 日志系统Logback —— Webx3推荐使用logback来取代log4j。Logback可直接被SLF4J识别并使用。
4. 日志系统Log4j —— 由于客观原因,有些系统暂时不能升级到Logback。好在SLF4J仍然支持Log4j。Log4j需要一个适配器slf4j-log4j12才能被SLF4J识别并使用。
一山不容二虎,除非一公一母。
 由于JCL-over-SLF4J和原来的JCL具有完全相同的API,因此两者是不能共存的。
 Logback和slf4j-log4j12也不能并存,否则SLF4J会迷惑并产生不确定的结果。
3.1.在Maven中配置logback作为日志系统

书写pom.xml如下所示。
<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>

<!--
  - 注意,如果你的项目指定了parent pom,那么建议
  - 把以下的<dependencyManagement>放在parent pom中,以便多个子项目共享配置。
-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.5.11</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.5.11</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>99.0-does-not-exist</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.18</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
请注意以下几点:
 把所依赖jar包的版本定义在<dependencyManagement>中,而不是<dependencies>中。因为前者可影响间接依赖,后者只能影响直接依赖。
 将日志系统的依赖设定为<scope>runtime</scope>,因为应用程序永远不需要直接调用日志系统,而是通过SLF4J或JCL这样的日志框架来调用它们。
 将commons-logging的依赖版本设定为“<version>99.0-does-not-exist</version>”。这是一个特殊的版本 —— 事实上这个版本的jar包里空无一物。由于maven缺少一个重要的功能 —— 它不能全局地排除一个jar包依赖,所以SLF4J的作者为我们建议了这样一种解决方法。
 最后,你需要在项目文件夹下面,执行一下命令:“mvn dependency:tree”,确保没有jar包间接依赖了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>
3.2.在Maven中配置log4j作为日志系统

书写pom.xml如下所示。
<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>

<!--
  - 注意,如果你的项目指定了parent pom,那么建议
  - 把以下的<dependencyManagement>放在parent pom中,以便多个子项目共享配置。
-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.5.11</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.5.11</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>99.0-does-not-exist</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.5.11</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
除了在前文中所讲到注意点以外,你还需要注意以下几点:
 你需要在项目文件夹下面,执行一下命令:“mvn dependency:tree”,确保没有jar包间接依赖了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>
4.在WEB应用中配置日志系统
4.1.设置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>

    <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>
配置文件包含几部分:
 日志参数,定义在<context-param>中。可以在这里定义以下参数:
 loggingRoot —— 保存日志文件的根目录。如不指定,默认为:“${user.home}/logs”。
 loggingCharset —— 用来生成日志文件的字符集编码。如不指定,默认为当前系统的默认字符集编码。
 loggingLevel —— 日志级别,低于指定级别的日志将不被输出。如不指定,默认为“INFO”。
 log* —— 名称以“log”开头的任意参数,都将被视作日志系统的参数。
通常,我们可以利用maven的filtering机制,或者autoconfig插件来生成web.xml文件,以便定制上述参数。
 LogConfiguratorListener —— 在WEB应用启动的时候,这个listener会被激活,并初始化日志系统。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(或log4j.xml)不存在,那么listener会用默认的配置文件来初始化日志。默认的配置会把WARN级别以上的日志打印在STDERR中,把WARN级别以下的日志打印在STDOUT中。默认只输出INFO及更高级别的日志。
 SetLoggingContextFilter —— 将当前请求的信息放到日志系统的MDC中(Mapped Diagnostic Context)。这样,日志系统就可以打印出诸如下面所示的日志信息:
30377 [2010-06-02 15:24:29] - GET /wrongpage.htm [ip=127.0.0.1, ref=, 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] ……
上面的日志中包含了如下信息:
1. 用户请求的类型:GET
2. 请求的URI:/wrongpage.htm
3. 用户IP:127.0.0.1
4. 上一个页面的链接referrer:如有,则显示之
5. 用户的浏览器:Mac版的mozilla浏览器
6. Session ID:scnd32ei11a7
用户客户端的详细信息,对于发现和追踪错误非常有帮助。
SetLoggingContextFilter是一个可选的filter —— 即使没有它,Webx3.0 <setLoggingContext /> valve也会做同样的事情。但是把这些信息放在filter中,有利于及早记录用户的信息。
4.2.定制WEB-INF/logback.xml(或log4j.xml)
4.2.1.可用的参数占位符
在日志配置文件中,你可以使用以下占位符:
 在WEB-INF/web.xml中定义的所有日志参数:
 ${loggingRoot}
 ${loggingCharset}
 ${loggingLevel}
 ${log*} —— “*”代表任意名称
 由系统自动取得的参数:
 ${loggingHost} —— 代表当前的服务器名称
 ${loggingAddress} —— 代表当前的服务器IP地址
4.2.2.可用的MDC参数占位符
在appender pattern中,你可以使用以下MDC参数:
 %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
 其它由应用程序或框架置入MDC的参数。
4.2.3.WEB-INF/logback.xml示例
下面是logback.xml配置文件的示例,仅供参考。详细配置方法请见logback官方文档。
请特别留意参数占位符的写法,如“${loggingRoot}”;此外,还有包含MDC占位符的appender pattern的写法,如:“%X{method}”、“%X{requestURIWithQueryString}”等。
<?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{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{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{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>
4.2.4.WEB-INF/log4j.xml示例
下面是log4j.xml配置文件的示例,仅供参考。详细配置方法请见log4j官方文档。
请特别留意参数占位符的写法,如“${loggingRoot}”;此外,还有包含MDC占位符的appender pattern的写法,如:“%X{method}”、“%X{requestURIWithQueryString}”等。
<?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{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{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{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>
5.提示信息
当LogConfiguratorListener启动时,将会在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
 是否选择了正确的日志配置文件,如:log4j.xml
 日志文件的根目录、字符集编码、日志级别等信息。
6.常见错误
6.1.No log system exists
报这个错的原因可能是:
 不存在slf4j-log4j12、logback-classic等任何一个日志系统的实现
 Slf4j的版本和日志系统的版本不匹配,例如,slf4j为1.4.3版,而slf4j-log4j12为1.5.11版。
解决方法:
 用mvn dependency:tree查看所有的依赖包,排除以上错误。
 查看服务器环境(如jboss),查看是不是存在不正确版本的jar包,被优先于应用jar包而加载了。
提示:查看真实jar包有一个简单有效的方法,就是在应用程序的任意点设置断点(利用eclipse远程调试功能),当系统停留在断点处时,执行如下的java代码,查看其值:
getClass().getClassLoader().getResource(getClass().getName().replace('.', '/') + ".class")
6.2.NoSuchMethodError: org.slf4j.MDC.getCopyOfContextMap()
报这个错的原因是:
 SLF4J的版本过老。MDC.getCopyOfContextMap()方法是从SLF4J 1.5.1时加入的,假如你的SLF4J是之前的版本,就会报错。
解决方法:
 用mvn dependency:tree查看所有的依赖包,排除以上错误。
 查看服务器环境(如jboss),查看是不是存在不正确版本的jar包,被优先于应用jar包而加载了。
6.3.Class path contains multiple SLF4J bindings
SLF4J报如下错误:
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中所有的日志系统的位置。根据这些信息,你可以调整应用的依赖,或者整理服务器的环境,使之只剩下一个日志系统。
6.4.看不到日志输出
原因可能是日志的配置文件可能有错。
解决方法:
 首先,查看LogConfiguratorListener输出到STDERR中的信息,确定系统:1) 选择了正确的日志系统;2) 选择了正确的配置文件;3) 设置了正确的参数(loggingRoot、loggingLevel等)
提示:在JBOSS环境中,STDOUT和STDERR会被重定向到Log4j中,然后被输出到一个文件中,通常是log/server.log。你必须从这个日志文件中查看LogConfiguratorListener的输出。
 假如以上信息均正确,查看日志配置文件log4j.xml或logback.xml,是否使用了正确的参数占位符,例如:${loggingRoot}、${loggingLevel}等。
 检查文件系统权限,确保应用有权限创建和修改日志文件。
提示:假设你使用log4j为日志系统。在JBOSS环境中,当log4j被初始化后,STDOUT和STDERR会被重新配置到不同的appender中。原先用来记录STDOUT和STDERR的日志文件log/server.log将不会再被使用。建议你设置log4j.xml,增加如下内容:
<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{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中。
7.结尾的话
Webx3中的LogConfigurator目前只提供了logback和log4j的支持,尽管支持一种新的日志系统是非常容易的,但现在看来,这两种日志系统已经足够我们使用了。
然而SLF4J还有更多的功能:
 除了log4j和logback以外,SLF4J还支持几种其它的日志系统;
 除了jcl-over-slf4j以外,SLF4J还提供了几种对其它legacy日志系统的桥接功能。
详情请见SLF4J的文档:http://www.slf4j.org/docs.html

你可能感兴趣的:(maven,框架,应用服务器,log4j,xml)