在本地电脑里用RAD中开发项目并部署在本地WAS能正常输出日志,但部署到其它linux系统上里就出现可以创建日志文件但不能输出信息的问题,其中部署的ear包含两个war,每个war都有自己的配置信息,如log4j.xml等。
可能的原因:
一、WAS在启动后分配到的logger很可能不是log4j的,特别是使用logFactory.getLog(name)有得到不同的log实现类;
现在就来说说查找 LogFactory 的顺序:
1. 从系统属性中查找键为 org.apache.commons.logging.LogFactory 的值作为 LogFactory 的实现类;却通过 System.getProperty("org.apache.commons.logging.LogFactory") 获得
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 这个目录层次及文件名。
3. 在 Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 属性值作为 LogFactory 实现类
4. 前面三步未找个 LogFactory 的实现类,或有任何异常的情况下,就用默认的实现类,即 LogFactory 为我们准备的 org.apache.commons.logging.impl.LogFactoryImpl
明白了以上的顺序,可以帮助我们理解和解决一些实际的问题,例如,为什么可以不用 commons-logging.properties 也是使用的 log4j 日志实现,部署在 WAS 下的应用 log4j 怎么就不能输出日志了呢?
一般,某个具体的 LogFactory 类对应就会使用与其相应的 Logger 实现,如 Log4jFactory.getLog() 得到的是 Log4JLogger 实例,WAS 的 TrLogFactory.getLog() 返回的是 TrLog 实例。
老师们教我们用 commons-logging 时也许会让我们在 classpath 下放一个 commons-logging.properties 文件,并在这个文件中写上一行:
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory
Log4jFactory 已不推荐使用,新的建议的用法是 LogFactory 统一用 LogFactoryImpl,然后在 LogFactoryImpl 中决定声明哪个 Log 实现类。
或者是这么两行:
org.apache.cmmons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
然而我们基本都是用的 Log4j 来输出日志,其实不管 commons-logging.properties 是第一种写法还是第二种写法或许(有时不是) 都是多余的,回望 LogFactory.getFactory() 方法,还要再看看 org.apache.commons.logging.impl.LogFactoryImpl 的 getLogClassName() 方法便可知。
LogFactory.getFactory() 在前面三步找不到 LogFactory 实现类时,就会用默认的 LogFactoryImpl,而默认的 LogFactoryImpl.getLog() 时,又会根据以下四个顺序来决定返回什么 Log 实例(Log 实例对应到实际的日志实现),在 LogFactoryImpl.getLogClassName() 中体现了:
1. commons-logging.properties 中的 org.apache.commons.logging.Log 指定的 Log 实现类
2. Log4j 可用就用 org.apache.commons.logging.impl.Log4JLogger
3. Jdk1.4 Logger 可用就用 org.apcache.commons.logging.impl.Jdk14Logger(JDK1.4 开始自带)
4. SimpleLog 可用就用 org.apache.commons.logging.impl.SimpleLog(commons-logging 自带)
所以这就是为什么了,使用了 commons-logging 的框架类,只要扔个 log4j 的 jar,根本不用 commons-logging.properties 文件就会用 log4j 来输出日志,当然 log4j 自己的配置文件 log4j.xml 或 log4j.properties 是需要的。
那为什么在 Tomcat 或别的应用服务器中 log4j 能正常输出日志,一放到 WAS 5 下却不灵了呢?原因是在 $WAS_HOME/lib/ws-commons-logging.jar 中有个文件 commons-logging.properties,其中有一行 org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory,虽然你的应用中可能也有一个 commons-logging.properties,可是很不幸,WAS 自己的 commons-logging.properties 优先了,原因是类加载器的委托机制在作用,所以最终 log4j 没派上用场,被 com.ibm.ws.commons.logging.TrLog 取而代之了,解决办法是要抢在它之前,比系统属性中声明 LogFactory 实现类,或是在 META-INF/services/org.apache.commons.logging.LogFactory 中指名 org.apache.commons.logging.impl.Log4jFactory 或 org.apache.commons.logging.impl.LogFactoryImpl 作为实现类名。
二、两个war里面定义的xml可能出现同名的情况。
这里先介绍下 commons-logging和Log4j。
Commons-loggin的目的是为“所有的Java日志实现”提供一个统一的接口,它自身的日志功能平常弱(只有一个简单的SimpleLog?),所以一般不会单独使用它。Log4j的功能非常全面强大,是目前的首选。我发现几乎所有的Java开源项目都会用到Log4j,但我同时发现,所有用到Log4j的项目一般也同时会用到commons-loggin。我想,大家都不希望自己的项目与Log4j绑定的太紧密吧。另外一个我能想到的“同时使用commons-logging和Log4j”的原因是,简化使用和配置。
Commons-logging提供一个统一的日志接口,简单了操作,同时避免项目与某个日志实现系统紧密耦合,很贴心的帮我们自动选择适当的日志实现系统(这一点非常好!)它甚至不需要配置。
这里看一下它怎么“‘很贴心的’帮我们‘自动选择’‘适当的’日志实现系统”。
首先org.apche.commons.logging.LogFactory 是一个抽象类,所以需要一个 LogFactory 具体类。Commons-logging查找 LogFactory 的实现类的顺序:
1. 从系统属性中查找键为 org.apache.commons.logging.LogFactory 的值作为 LogFactory 的实现类;却通过 System.getProperty("org.apache.commons.logging.LogFactory") 获得.
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 这个目录层次及文件名.
3. 在 Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 属性值作为 LogFactory 实现类。
4. 如果前面未找个 LogFactory 的实现类,或有任何异常的情况下,就用默认的实现类,即 LogFactory 为我们准备的 org.apache.commons.logging.impl.LogFactoryImpl。
如果用了默认的LogFactoryImpl, LogFactoryImpl.getLog() 返回时,又会根据以下四个顺序来决定返回什么 Log 实例(Log 实例对应到实际的日志实现),在 LogFactoryImpl.getLogClassName() 中体现了:
1. commons-logging.properties 中的 org.apache.commons.logging.Log 指定的 Log 实现类;
2. 查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类;
3. Jdk1.4 Logger 可用就用 org.apcache.commons.logging.impl.Jdk14Logger(JDK1.4 开始自带);
4. SimpleLog 可用就用 org.apache.commons.logging.impl.SimpleLog(commons-logging 自带);
所 以这就是为什么了,使用了 commons-logging 的框架类,只要扔个 log4j 的 jar,根本不用 commons-logging.properties 文件就会用 log4j 来输出日志,当然 log4j 自己的配置文件 log4j.xml 或 log4j.properties 是需要的。
转回正题,was是在哪里先下手为强的呢?就在查找commons-logging.properties 文件。那为什么我指定了 commons-logging.properties 文件用 Log4jFactory 还不行呢?实际上 WAS 加载的是 $WAS_HOME/lib/ws-commons-logging.jar!/commons-logging.properties,则其中的内容 是:
org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory
所以实际应用的是 WAS 5 自带的 TrLogFactory,而不是 Log4jFactory 或别的。为什么优先加载了些 commons-logging.properties 而非应用中的 commons-logging.properties,原因就是类加载器的委托机制:commons-logging.properties 是通过 ClassLoader 定位的,子 ClassLoader 加载某个类或资源时会委托给父 ClassLoader 加载,父 ClassLoader 能加载到该类或资源则优先。而往往这个父加载器是was提供的。
知道了上面那些,那我们来找可能的解决办法,使我们的应用能借助 Log4j 输出日志:
1. 修改 $WAS_HOME/lib/ws-commons-logging.jar!commons-logging.properties 的内容为:
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
或者org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory
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 也能用了。但这样做同样动了全局的东西。
3. 回到前面 LogFactory 决定具体实现类的步骤上,可以在第一、二步上做文章。其一,设置系统属性 org.apahe.commons.logging.LogFactory 为 org.apache.commons.logging.impl.LogFactoryImpl 或者为 org.apache.commons.logging.impl.Log4jFactory。
要在 WAS 管理控制台对相应的应用服务器设置系统属性,或在程序中设定。这样做同样影响了全局,似乎不怎么妥。
4. 还有第二步,应用 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 包中。
5. 还有个办法就是直接用 Log4j,而不用或绕开 commons-logging 的 LogFactory 定位机制,直接在代码中声明使用 Log4j 的 Log,Logger log = Logger.getLogger(TestLog.class),这里的 Logger 是 org.apache.log4j.Logger。这样再换成别的日志组件就没辙了,当然很少有必要去换的。
6. 再 WAS 可以改变应用的类载入器方式。管理控制台中,在应用的属性页面,类载入器方式可以选择 PARENT_FIRST 和 PARENT_LAST,默认是 PARENT_FIRST,遵循类加载器的委托机制。若是改成 PARENT_LAST,也能让你应用中的 commons-logging.properties 优先得到加载,使用上 Log4j。真要改成 PARENT_LAST 能造成的意外情况更是不可预知的。
具体解决方法有待改进。
IBM的东西,真是太麻烦了,一个日志都给你替换掉,太霸道了,Google了下,居然是个普遍想象,不过也有相关的解决方
案,基本好像都是在 WAS5,WAS6.1上的,我没环境,不过我这边的WAS7 没试成功过
描述两种网上的流行的配置
第一种:
在你的项目的\webapp\META-INF下建立一个名叫services的文件夹,然后建立一个名为org.apache.commons.logging.LogFactory的文件,里面添加一行文字:
org.apache.commons.logging.impl.Log4jFactory
即可解决
第二种:
①、在类路径下增加commons-logging.properties配置文件,文件内容为:
priority=1
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
②、.将类加载方式改成parent_last
以上两种我在 WAS 7都没试成功过。
就在绝望之际,柳暗花明,在国外的小角落了发现了,下面一段描述
*A very strange solution :* |
http://fixunix.com/websphere/538047-log4j-commons-logging-was6-1-0-19-a.html
尝试之果然有效
步骤如下(以windows为例):
1、将写好配置的commons-logging.properties 放到
D:\IBM\WebSphere\AppServer\profiles\AppSrv01\properties 目录
2、将 log4j-1.2.14.jar commons-logging-1.1.1.jar 放到
D:\IBM\WebSphere\AppServer\lib 目录下
3、将你的log4j.Properties 也放到步骤2的目录下
不需要改类加载气先后顺序,也不要要动工程结构,重启下服务,日志就生效,,完全是按照log4j的配置文件打印日志的。
目前知道的缺点:所有的应用公用了一个日志配置文件,,需要改变websphere的本身的配置
为啥没就人家weblogic 方便呢,几乎啥都不用动,想怎么用就用怎么用。