WAS5.x 下使用 Log4j 为何没有日志输出的详细分析及解决办法 log4jWebsphereApache应用服务器Eclipse
程序员文章站
2024-02-02 19:32:04
...
经常碰到有人使用了 Log4j 的项目在别处运行的好好的,一放到 WAS 下发现不能输出日志了:控制台文件 SystemOut.log 中没有 Log4j 本应输出的内容,要么就只见磁盘上有一个设定的日志文件,却总是空空如也。WebSphere 开发与应用社区hRBKe U,a.@HF
WebSphere 开发与应用社区6s0~%ez;_8d)\
本文就来以一个实际的例子分析为什么会出现这样的情况,以及告诉你应如何解决。这其中也是因为 ClassLoader 在作祟。通常我们会在通用日志框架 jakarta-commons-logging 之下使用 Log4j,这也是合情理的,因为 Struts、Hibernate 等众多框架就是这么干的,然而这却是一方面的根源。分析的过程应用了前面一篇介绍的Eclipse 远程调试 WebSphere Application Server (WAS)方法。
TdSo#tY{[d0
:h ?/t@ W.S/nt1F0测试环境和工具是:WAS 5.1+Eclipse 3.3.2+MyEclipse 6.0.1,在 Eclipse 中建立 Web 项目 testlog(此处可下载:http://www.blogjava.net/Files/Unmi/testlog.zip),其中所用到的 jar 包只有 commons-logging.jar(V1.0.3)、log4j-1.2.11.jar。再有为了知道是哪个 ClassLoader 加载了某个类,参考了:利用 JWhich 掌握类路径,确定类路径中的什么类将被载入。
%I z1e+E)C6M2Zt0WebSphere 开发与应用社区 K _hdN$gq,B9o^
项目 testlog 简介:在 WEB-INF/classes 下有两个配置文件,commons-logging.properties 和 log4j.properties。commons-logging.properties 的内容是:
CW;D-g-T\0
W v4ljW0org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactoryWebSphere 开发与应用社区$\6ns(uF/A&ycKeU
6Z\Xt"o)o6HI1S n0#Log4jFactory 已不推荐使用,新的建议是统一使用 LogFactoryImpl,然后由它决定声明哪个 Log 实现WebSphere 开发与应用社区?q4V4Z1C,F"K
#org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactroyImplWebSphere 开发与应用社区[ zI^7~pw Z-yj
0H&FX,W_g0log4j.properties 中配置了两个 Appender,分别输往控制台stdout(ConsoleAppender) 和 R 文件 log4j.log(DailyRollingFileAppender);输出级别设定为 DEBUG。WebSphere 开发与应用社区X/E+fb|g}m
7F$o%p+E^Q6n0日志变量 log 的声明方式是:Log log = LogFactory.getLog(TestLog.class);把该行代码放在 main 方法中是为了便于测试。该项目发布到 WAS5.1 下,通过访问 index.jsp 来进行测试,index.jsp 页面中还包括了测试日志输出和查找哪个 ClassLoader 加载了某个类的功能。WebSphere 开发与应用社区Bx@ Er
WebSphere 开发与应用社区qIk+M5Op
然后用 Eclipse 的远程调试功能连接到 WAS 的调试端口上就可以单步调试了。在 com.unmi.TestLog 类的行 Log log = LogFactory.getLog(TestLog.class); 上打断点,访问部署的 index.jsp 页面(如:http://10.80.39.41:9080/testlog/index.jsp),然后在 Log Message 框中输入信息,点击 Print Log 按钮,代码就停在 Log log = LogFactory.getLog(TestLog.class); 行上,我们真正就是从这里开始看问题了。WebSphere 开发与应用社区#Uu3S*g*c1T0WI2}
WebSphere 开发与应用社区T4T\)G+NW a.X1z
LogFactory.getLog(TestLog.class); 会调用 LogFactory.getFactory() 来获得真正的 LogFactory 实例,注意 org.apche.commons.logging.LogFactory 是个抽象类。通过看 LogFactory.getFactory() 方法代码,我们可以知道 commons-logging 是按以下顺序找到相应的 LogFactory 实现类的(详细分析见我前面写的一篇:Apache Commons Logging 是如何决定使用哪个日志实现类的)。WebSphere 开发与应用社区"|!gO(i{'rx.E2G
WebSphere 开发与应用社区&}${ ] @4AH?0L_rM
1. 从系统属性中查找键为 org.apache.commons.logging.LogFactory 的值作为 LogFactory 的实现类;却通过 System.getProperty("org.apache.commons.logging.LogFactory") 获得WebSphere 开发与应用社区JN-S`ceh
WebSphere 开发与应用社区ylN.J*Q0b h)k$ZV*r
2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI) 类发现机制,从配置文件 META-INF/services/org.apache.commons.logging.LogFactory 的的第一行读取 LogFactory 的实现类名。这个 META-INF/services/org.apache.commons.logging.LogFactory 文件可以是某个 Web 应用的根目录中;也可以在 classpath 下,如某个 Jar 包中,WebRoot/WEB-INF/classes 中等。这里需多加留心下 META-INF/services/org.apache.commons.logging.LogFactory 这个目录层次及文件名。
9P4n:_.F\tr'n0
!J_ t.vpe2_,{03. 在 Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 属性值作为 LogFactory 实现类WebSphere 开发与应用社区!g8ar mc!|6Vu,cl
_3d.cc,R rZok]04. 前面三步未找个 LogFactory 的实现类,或有任何异常的情况下,就用默认的实现类,即 LogFactory 为我们准备的 org.apache.commons.logging.impl.LogFactoryImplWebSphere 开发与应用社区(oNY,{N q0n)m
j2AK m/i0那么 WAS 5 是在上面哪一步先下手为强的呢?就在第三步。你是不是有疑问了,我们项目中不是有一个 commons-logging.properties 文件,在其中指定了用 Log4jFactory 的吗?可是实际用运行 WAS 加载的是 $WAS_HOME/lib/ws-commons-logging.jar!/commons-logging.properties,则其中的内容是:
"b1in'?k0WebSphere 开发与应用社区u*VL.T_/w
org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory
5Xg:x3W;e-H"l-]0
^^(p`$m*e"E3u0所以实际应用的是 WAS 5 自带的 TrLogFactory,而不是 Log4jFactory 或别的。为什么优先加载了些 commons-logging.properties 而非应用中的 commons-logging.properties,原因就是类加载器的委托机制:commons-logging.properties 是通过 ClassLoader 定位的,子 ClassLoader 加载某个类或资源时会委托给父 ClassLoader 加载,父 ClassLoader 能加载到该类或资源则优先。WebSphere 开发与应用社区G gI WU XK
WebSphere 开发与应用社区.ob/L`+~3[,?9g
应用 testlog 的类加载器 com.ibm.ws.classloader.CompoundClassLoader(从WEB-INF/class 和 WEB-INF/lib 下加载) 的父加载器是 com.ibm.ws.bootstrap.ExtClassLoader(从 $WAS_HOME/lib 下加载)。再顺带说明一下,$WAS_HOME/lib 带了 commons-logging 包 commons-logging-api.jar,它是 1.0.3版,但是 WAS 的 commons-logging-api.jar 与我们下载的 commons-logging-1.0.3.jar 相比,少了 org.apache.commons.logging.impl 包中的 Log4jFactory、Log4JCategoryLog、Log4JLogger 这几个类。所以 commons-logging 中的有些类从 WAS 的 commons-logging-api.jar 加载,有些从应用中的 commons-logging.jar 加载,以及通过不同的加载器加载的。
%[?n8P2d,U+e7|,A0WebSphere 开发与应用社区:RA`CD%u7a1q
知道了上面那些,那我们来找可能的解决办法,使我们的应用能借助 Log4j 输出日志:WebSphere 开发与应用社区b9]X.Xf"l ?
WebSphere 开发与应用社区7Sq7B3~i4u+Y
1. 修改 $WAS_HOME/lib/ws-commons-logging.jar!commons-logging.properties 的内容为:WebSphere 开发与应用社区L'E.tGa JM
j9Nz!W'uz^@ O(M0o0org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImplWebSphere 开发与应用社区7dt ]Fk8h k9W!c']
#或者org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory
2D YI;Ki%C,m#zz0WebSphere 开发与应用社区?&cO'E+]E*m"V ]O3V8W
WebSphere 开发与应用社区zK+prMp*O
2. 把 $WAS_HOME/lib/ws-commons-logging.jar!/commons-logging.properties 文件删了?这样父 ClassLoader 是加载不到 commons-logging.properties,能用上应用中的 commons-logging.properties 文件,即使应用中没有 commons-logging.properties 文件也行(没有该文件时会使用 org.apache.commons.logging.impl.LogFactoryImpl,它找到了 Log4j 就用 Log4j),Log4j 也能用了。但这样做同样动了全局的东西。
@D q*^y](@0
%]QL6A7I iD03. 回到前面 LogFactory 决定具体实现类的步骤上,可以在第一、二步上做文章。其一,设置系统属性 org.apahe.commons.logging.LogFactory 为 org.apache.commons.logging.impl.LogFactoryImpl 或者为 org.apache.commons.logging.impl.Log4jFactoryWebSphere 开发与应用社区]"Ue4Ke/Ow7v
。要在 WAS 管理控制台对相应的应用服务器设置系统属性,或在程序中设定。这样做同样影响了全局,似乎不怎么妥。
a+K3lg%|CfU0
] M.V]O_04. 还有第二步,应用 SPI,指定 SERVICE_ID,具体做法是在 WebRoot/ 下放个文件 META-INF/services/org.apache.commons.logging.LogFactory,在该文件的第一行写上 org.apache.commons.logging.impl.LogFactoryImpl 或是 org.apache.commons.logging.impl.Log4jFactory 就 OK 啦,这无疑是最好的办法了。总之就是不让 WAS 用 com.ibm.ws.commons.logging.TrLogFactory。因为这个文件也是通过 ClassLoader 加载的,所以 META-INF/services/org.apache.commons.logging.LogFactory 也可以放在 WebRoot/WEB-INF/classes/ 或者是你应用的某个 jar 包中。
M$?-]^n#a'@]O0
i3@&Nu)R2N1a%[ I05. 还有个办法就是直接用 Log4j,而不用或绕开 commons-logging 的 LogFactory 定位机制,直接在代码中声明使用 Log4j 的 Log,Logger log = Logger.getLogger(TestLog.class),这里的 Logger 是 org.apache.log4j.Logger。这样再换成别的日志组件就没辙了,当然很少有必要去换的。WebSphere 开发与应用社区s"h9B;V"G+h
WebSphere 开发与应用社区 i-] l AK)Ov9g9n3Q
6. 再 WAS 可以改变应用的类载入器方式。管理控制台中,在应用的属性页面,类载入器方式可以选择 PARENT_FIRST 和 PARENT_LAST,默认是 PARENT_FIRST,遵循类加载器的委托机制。若是改成 PARENT_LAST,也能让你应用中的 commons-logging.properties 优先得到加载,使用上 Log4j。真要改成 PARENT_LAST 能造成的意外情况更是不可预知的。
+M[2n,E)^y-Nm0
\o|)Pl%^6j P-x F0说到这里,记起了,有次也是因为 Log4j 未能工作,原因是某个 jar 包中有 META-INF/services/org.apache.commons.logging.LogFactory 文件。而为何只产生空的 log4j 的日志文件,应该是你哪处代码触碰到了 Log4j 的初始化行为,实际却未用上 Log4j 来输出日志。
R)o4Og)p$_:E0
Xdb G F^cx(I0这个应用放到 WAS 6.1.0.0 下 Log4j 可没什么问题,WAS 6.1.0.0 的包没有哪个有 META-INF/services/org.apache.commons.logging.LogFactory 这个文件,所以默认情况下会使用 org.apache.commons.logging.impl.LogFactoryImpl 实现类,由 LogFactoryImpl 也就能正确加载到 org.apache.commons.logging.impl.Log4JLogger。
WebSphere 开发与应用社区6s0~%ez;_8d)\
本文就来以一个实际的例子分析为什么会出现这样的情况,以及告诉你应如何解决。这其中也是因为 ClassLoader 在作祟。通常我们会在通用日志框架 jakarta-commons-logging 之下使用 Log4j,这也是合情理的,因为 Struts、Hibernate 等众多框架就是这么干的,然而这却是一方面的根源。分析的过程应用了前面一篇介绍的Eclipse 远程调试 WebSphere Application Server (WAS)方法。
TdSo#tY{[d0
:h ?/t@ W.S/nt1F0测试环境和工具是:WAS 5.1+Eclipse 3.3.2+MyEclipse 6.0.1,在 Eclipse 中建立 Web 项目 testlog(此处可下载:http://www.blogjava.net/Files/Unmi/testlog.zip),其中所用到的 jar 包只有 commons-logging.jar(V1.0.3)、log4j-1.2.11.jar。再有为了知道是哪个 ClassLoader 加载了某个类,参考了:利用 JWhich 掌握类路径,确定类路径中的什么类将被载入。
%I z1e+E)C6M2Zt0WebSphere 开发与应用社区 K _hdN$gq,B9o^
项目 testlog 简介:在 WEB-INF/classes 下有两个配置文件,commons-logging.properties 和 log4j.properties。commons-logging.properties 的内容是:
CW;D-g-T\0
W v4ljW0org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactoryWebSphere 开发与应用社区$\6ns(uF/A&ycKeU
6Z\Xt"o)o6HI1S n0#Log4jFactory 已不推荐使用,新的建议是统一使用 LogFactoryImpl,然后由它决定声明哪个 Log 实现WebSphere 开发与应用社区?q4V4Z1C,F"K
#org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactroyImplWebSphere 开发与应用社区[ zI^7~pw Z-yj
0H&FX,W_g0log4j.properties 中配置了两个 Appender,分别输往控制台stdout(ConsoleAppender) 和 R 文件 log4j.log(DailyRollingFileAppender);输出级别设定为 DEBUG。WebSphere 开发与应用社区X/E+fb|g}m
7F$o%p+E^Q6n0日志变量 log 的声明方式是:Log log = LogFactory.getLog(TestLog.class);把该行代码放在 main 方法中是为了便于测试。该项目发布到 WAS5.1 下,通过访问 index.jsp 来进行测试,index.jsp 页面中还包括了测试日志输出和查找哪个 ClassLoader 加载了某个类的功能。WebSphere 开发与应用社区Bx@ Er
WebSphere 开发与应用社区qIk+M5Op
然后用 Eclipse 的远程调试功能连接到 WAS 的调试端口上就可以单步调试了。在 com.unmi.TestLog 类的行 Log log = LogFactory.getLog(TestLog.class); 上打断点,访问部署的 index.jsp 页面(如:http://10.80.39.41:9080/testlog/index.jsp),然后在 Log Message 框中输入信息,点击 Print Log 按钮,代码就停在 Log log = LogFactory.getLog(TestLog.class); 行上,我们真正就是从这里开始看问题了。WebSphere 开发与应用社区#Uu3S*g*c1T0WI2}
WebSphere 开发与应用社区T4T\)G+NW a.X1z
LogFactory.getLog(TestLog.class); 会调用 LogFactory.getFactory() 来获得真正的 LogFactory 实例,注意 org.apche.commons.logging.LogFactory 是个抽象类。通过看 LogFactory.getFactory() 方法代码,我们可以知道 commons-logging 是按以下顺序找到相应的 LogFactory 实现类的(详细分析见我前面写的一篇:Apache Commons Logging 是如何决定使用哪个日志实现类的)。WebSphere 开发与应用社区"|!gO(i{'rx.E2G
WebSphere 开发与应用社区&}${ ] @4AH?0L_rM
1. 从系统属性中查找键为 org.apache.commons.logging.LogFactory 的值作为 LogFactory 的实现类;却通过 System.getProperty("org.apache.commons.logging.LogFactory") 获得WebSphere 开发与应用社区JN-S`ceh
WebSphere 开发与应用社区ylN.J*Q0b h)k$ZV*r
2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI) 类发现机制,从配置文件 META-INF/services/org.apache.commons.logging.LogFactory 的的第一行读取 LogFactory 的实现类名。这个 META-INF/services/org.apache.commons.logging.LogFactory 文件可以是某个 Web 应用的根目录中;也可以在 classpath 下,如某个 Jar 包中,WebRoot/WEB-INF/classes 中等。这里需多加留心下 META-INF/services/org.apache.commons.logging.LogFactory 这个目录层次及文件名。
9P4n:_.F\tr'n0
!J_ t.vpe2_,{03. 在 Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 属性值作为 LogFactory 实现类WebSphere 开发与应用社区!g8ar mc!|6Vu,cl
_3d.cc,R rZok]04. 前面三步未找个 LogFactory 的实现类,或有任何异常的情况下,就用默认的实现类,即 LogFactory 为我们准备的 org.apache.commons.logging.impl.LogFactoryImplWebSphere 开发与应用社区(oNY,{N q0n)m
j2AK m/i0那么 WAS 5 是在上面哪一步先下手为强的呢?就在第三步。你是不是有疑问了,我们项目中不是有一个 commons-logging.properties 文件,在其中指定了用 Log4jFactory 的吗?可是实际用运行 WAS 加载的是 $WAS_HOME/lib/ws-commons-logging.jar!/commons-logging.properties,则其中的内容是:
"b1in'?k0WebSphere 开发与应用社区u*VL.T_/w
org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory
5Xg:x3W;e-H"l-]0
^^(p`$m*e"E3u0所以实际应用的是 WAS 5 自带的 TrLogFactory,而不是 Log4jFactory 或别的。为什么优先加载了些 commons-logging.properties 而非应用中的 commons-logging.properties,原因就是类加载器的委托机制:commons-logging.properties 是通过 ClassLoader 定位的,子 ClassLoader 加载某个类或资源时会委托给父 ClassLoader 加载,父 ClassLoader 能加载到该类或资源则优先。WebSphere 开发与应用社区G gI WU XK
WebSphere 开发与应用社区.ob/L`+~3[,?9g
应用 testlog 的类加载器 com.ibm.ws.classloader.CompoundClassLoader(从WEB-INF/class 和 WEB-INF/lib 下加载) 的父加载器是 com.ibm.ws.bootstrap.ExtClassLoader(从 $WAS_HOME/lib 下加载)。再顺带说明一下,$WAS_HOME/lib 带了 commons-logging 包 commons-logging-api.jar,它是 1.0.3版,但是 WAS 的 commons-logging-api.jar 与我们下载的 commons-logging-1.0.3.jar 相比,少了 org.apache.commons.logging.impl 包中的 Log4jFactory、Log4JCategoryLog、Log4JLogger 这几个类。所以 commons-logging 中的有些类从 WAS 的 commons-logging-api.jar 加载,有些从应用中的 commons-logging.jar 加载,以及通过不同的加载器加载的。
%[?n8P2d,U+e7|,A0WebSphere 开发与应用社区:RA`CD%u7a1q
知道了上面那些,那我们来找可能的解决办法,使我们的应用能借助 Log4j 输出日志:WebSphere 开发与应用社区b9]X.Xf"l ?
WebSphere 开发与应用社区7Sq7B3~i4u+Y
1. 修改 $WAS_HOME/lib/ws-commons-logging.jar!commons-logging.properties 的内容为:WebSphere 开发与应用社区L'E.tGa JM
j9Nz!W'uz^@ O(M0o0org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImplWebSphere 开发与应用社区7dt ]Fk8h k9W!c']
#或者org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory
2D YI;Ki%C,m#zz0WebSphere 开发与应用社区?&cO'E+]E*m"V ]O3V8W
WebSphere 开发与应用社区zK+prMp*O
2. 把 $WAS_HOME/lib/ws-commons-logging.jar!/commons-logging.properties 文件删了?这样父 ClassLoader 是加载不到 commons-logging.properties,能用上应用中的 commons-logging.properties 文件,即使应用中没有 commons-logging.properties 文件也行(没有该文件时会使用 org.apache.commons.logging.impl.LogFactoryImpl,它找到了 Log4j 就用 Log4j),Log4j 也能用了。但这样做同样动了全局的东西。
@D q*^y](@0
%]QL6A7I iD03. 回到前面 LogFactory 决定具体实现类的步骤上,可以在第一、二步上做文章。其一,设置系统属性 org.apahe.commons.logging.LogFactory 为 org.apache.commons.logging.impl.LogFactoryImpl 或者为 org.apache.commons.logging.impl.Log4jFactoryWebSphere 开发与应用社区]"Ue4Ke/Ow7v
。要在 WAS 管理控制台对相应的应用服务器设置系统属性,或在程序中设定。这样做同样影响了全局,似乎不怎么妥。
a+K3lg%|CfU0
] M.V]O_04. 还有第二步,应用 SPI,指定 SERVICE_ID,具体做法是在 WebRoot/ 下放个文件 META-INF/services/org.apache.commons.logging.LogFactory,在该文件的第一行写上 org.apache.commons.logging.impl.LogFactoryImpl 或是 org.apache.commons.logging.impl.Log4jFactory 就 OK 啦,这无疑是最好的办法了。总之就是不让 WAS 用 com.ibm.ws.commons.logging.TrLogFactory。因为这个文件也是通过 ClassLoader 加载的,所以 META-INF/services/org.apache.commons.logging.LogFactory 也可以放在 WebRoot/WEB-INF/classes/ 或者是你应用的某个 jar 包中。
M$?-]^n#a'@]O0
i3@&Nu)R2N1a%[ I05. 还有个办法就是直接用 Log4j,而不用或绕开 commons-logging 的 LogFactory 定位机制,直接在代码中声明使用 Log4j 的 Log,Logger log = Logger.getLogger(TestLog.class),这里的 Logger 是 org.apache.log4j.Logger。这样再换成别的日志组件就没辙了,当然很少有必要去换的。WebSphere 开发与应用社区s"h9B;V"G+h
WebSphere 开发与应用社区 i-] l AK)Ov9g9n3Q
6. 再 WAS 可以改变应用的类载入器方式。管理控制台中,在应用的属性页面,类载入器方式可以选择 PARENT_FIRST 和 PARENT_LAST,默认是 PARENT_FIRST,遵循类加载器的委托机制。若是改成 PARENT_LAST,也能让你应用中的 commons-logging.properties 优先得到加载,使用上 Log4j。真要改成 PARENT_LAST 能造成的意外情况更是不可预知的。
+M[2n,E)^y-Nm0
\o|)Pl%^6j P-x F0说到这里,记起了,有次也是因为 Log4j 未能工作,原因是某个 jar 包中有 META-INF/services/org.apache.commons.logging.LogFactory 文件。而为何只产生空的 log4j 的日志文件,应该是你哪处代码触碰到了 Log4j 的初始化行为,实际却未用上 Log4j 来输出日志。
R)o4Og)p$_:E0
Xdb G F^cx(I0这个应用放到 WAS 6.1.0.0 下 Log4j 可没什么问题,WAS 6.1.0.0 的包没有哪个有 META-INF/services/org.apache.commons.logging.LogFactory 这个文件,所以默认情况下会使用 org.apache.commons.logging.impl.LogFactoryImpl 实现类,由 LogFactoryImpl 也就能正确加载到 org.apache.commons.logging.impl.Log4JLogger。