自从n多年前使用log4j起,印象中就没有觉得Log4j有什么太折磨人的问题,稍微复杂些的可能就是扩展自己的Appender。
不过这一次是碰到了一个比较棘手的“麻烦”。这个麻烦并不是因为难,而是遇到之前不曾注意到问题,被Log4j不小心撞了一下腰,还挺酸疼的。
之前使用Log4j非常简单,因为大多都是在同一个ClassLoader或ContextLoader中,所以只需要把log4j.xml或log4j.properties文件仍到classpath中即可(尽量是在某一个lib目录下),因为Log4j的LogManager会自动寻找和配置。当然,尽量不把log4j的配置文件放在jar中,这种做法不便于后期客户维护和更新。
后来apache推出的
commons-logging让这个简单了一些,但无非多配置一个logging.properties文件。
再后来
SLF4J让log操作变的更为简易,只需要动态替换不同的jar包即可轻松实现部署时候的日志处理包的替换。并且支持commons-logging。
于是,在不同的组件包中,在一些需要记录日志的类中,我们一般会用commons-logging的Log类,于是就会在这些类的前面,增加上一行诸如此的代码:
Log log = LogFactory.getLogger(<your_class>);
可如今,我们的Framework面临基于OSGI进行重组,各个组件都变成了plugin。但是,正因为这个osgi的框架存在,让单个classloader变的不再起作用。因为OSGI里面的各个Bundle是有独立的 ClassLoader来进行加载的。
我们总不能在每一个bundler中去放一个log4j.properties文件,这是不现实的。
虽然可以把log4j做成一个plugin,并把log4j.properties文件放在这个plugin中,利用SLF4J在OSGI环境下动态配置。但这是一个不能采用的方式,并不是技术上实现不了,而是业务上不允许。因为未来客户可能会从update站点更新这些boudles,而且可能还需要手工修改log4j的配置。
于是,想到了利用log4j.configuration这个System Property变量进行控制。Log4j允许你利用这个变量来硬性指定log4j配置文件的URL地址。
好像这个问题解决了哦,但是并没有这么简单。
在我们开发的产品组件中,有很多command命令,每个命令都有一个自己规范的.tra文件来设置环境信息。那么这意味这我们需要在这些.tra文件中都增加一行对这个log4j.configuration变量的声明。
于是有的开发人员提出,这种方式对客户并不是很友好。我们可以允许客户自己手工设置这个变量来指定所采用log4j配置文件,但我们应该提供默认的,而且这个默认配置是不应该显示的展现出来,所以我们应该在一些主要的组件中利用代码来控制,如果客户没有额外配置的化,那么应该利用代码“透明化”的配置,对客户透明的。当然这个也必须遵循统一的规范,比如默认的log4j配置文件放在某一个默认目录下。
这个想法是好的,而且不难实现,因为利用Log4j自带的Configurator实现类可以很轻松的实现这样的额外配置。
PropertyConfigurator.configure("D:/tibco/config/log4j.properties ");
但是,很不幸运,我们并没有把所有的command都转换成osgi的方式,有一些因为特殊原因不支持plugin机制(比如存在一些早期的组件,利用反射形成相互的依赖关系,但OSGI是不支持相互依赖的)。
这个时候怎么办呢?
因为我们track了半天Log4j的代码,也没有找到如何判断“当前的Log4j已经配置过了,不需要再配置了”。否则,就会很容易造成配置冲突。有可能存在同一个classloader中,有些组件都采用了log4j的Congfigurator来进行“透明化”配置,但是这些透明化配置因为无法判断log4j是否已经初始化,而造成重复配置、覆盖配置的问题。
目前这个问题的解决方案还在和美国那边协商,解决方案尚未最后定。
是因为我们为客户考虑的太多,而把Log4j问题想复杂了;还是因为以前陈旧的代码阻碍了我们;抑或是OSGI的机制还有待改善。总之,这一次,Log4j被OSGI撞了一下腰,我们是被撞了一下腰。