目录
[显示]

1.摘要

最近部门在做JVM监控工具的时候碰到的一个打印日志的问题。

2.背景

  • 应用部署在tomcat中,使用log4j作为日志框架。
  • 监控工具使用javaagent的方式随tomcat启动,同样使用log4j作为日志框架。

3.现象

  • 在tomcat目录下放置log4j jar包和log4j.properties文件,启动服务后发现监控工具日志正常,原有应用的日志不见了。
  • 只放置jar包,保留应用中的log4j.properties,所有日志都没有了。
  • 只放jar包,agent启动时sleep100秒,两边日志都正常了。

4.原因

  1. javaagent的premain和java main在同一个jvm中执行,使用的是system classloader。
  2. system classloader的搜索路径是启动命令行里的classpath(-cp)。
  3. tomcat webapps下的应用是由WebappClassLoader加载的,搜索路径是WEB-INF/classes,WEB-INF/lib,找不到则调用父加载器,继承关系如下:

    默认情况下(直接类引用的方式),父加载器是感知不到子加载器的,或者说,java agent加载不了webapps下的jar,webapps可以加载system classpath下的jar。

  4. log4j加载log4j.properties时会先取thread context classloader的classpath。如果由java agent初始化,会找system classpath;如果由webapp初始化,则会找webapp classpath(WEB_INF/classes),所以sleep一段时间有奇效。

  5. tomcat的日志使用了jdk提供的logging框架,具体实现是由$CATALINA_HOME/bin/tomcat-juli.jar提供,这个jar包在catalina.sh里被加入到system classpath:

    $CATALINA_HOME/conf/logging.properties文件配置了tomcat本身的日志系统,包含juli file和console两部分,console被输出为catalina.out,juli file包含localhost_xxx、host_manager等。

5.解决方案

  1. 在代码里使用java.util.logging,$CATALINA_HOME/conf/logging.properties里新增一个输出的handler,agent就可以直接使用这套日志系统了。

  2. 配置tomcat使用log4j,所有日志通过apache common-logging输出,具体参考这里

    根据官方文档里的说明,这里对原有的tomcat-juli做了修改,主要是增加了apache common logging。也就是说juli由system classloader加载,log4j由common classloader加载,由acl完成logger实现类的转移。

6.参考资料

  1. Logging in Tomcat
  2. Class Loader HOW-TO
  3. commons logging
  4. Logging, which framework to choose?