一、为什么要扩展Log4J
Log4J对日志提供的丰富的功能,有很多选项可以设置。一般来说,我们不需要写自己的Appender或是Layout,只要设置一个Log4J自己的对象就可以了。
应用场景不多种多样的,在做的Napoli的过程中,就发生的一个需要扩展Log4J的场景。
Log4J的Appender有多种,常用的有
- Console Appender,把Log信息打在控制台上
- File Appender,把Log信息打在文件里
- Rolling File Appender,也是打在文件里,可以指定日志文件的大小,超过这个大小,滚动文件,命名成:
info.log, info.log.1, info.log.2, info.log.3 ….
info.log是正在往里打日志的文件,后面是日志历史。
在Napoli中打出的日志,要被收集走,集中处理。 所谓的”收集走”是指,被外部程序移走。
“上面的情况使用Rolling File Appender不行吗?移走日志历史文件,就好了。”
移走日志历史文件是避免了,移动正在被写日志的文件可能出现的问题,(如可能丢掉 移动过程中的日志信息)
但是在Rolling File Appender会重命名日志的历史文件,当前的日志文件写满时,当前的命名由info.log -> info.log.1, info.log.1 -> info.log.2, info.log.2 -> info.log.3 ……
要在Rolling File Appender重命名日志文件的过程,”收集走”日志文件,就可能出错了。
“怎么办上面的问题?”
可以修改一下,当前的日志文件写满时,文件名重命名的规则,在Log4J中这个叫做Rollover(文件滚动)。
当前的日志文件写满时,由当前的命名由info.log -> info.log.20090911223059777,就是在文件后面加上时间截(精确到毫秒),这样产生新的历史文件时,并不需要生命名老的文件。BingGo!
二、如何扩展
这里针对的Log4J的版本是1.2.15。
要扩展的Log4J的RollingFileAppender类,定位文件滚动的逻辑,在rollOver方法中:
# 这里不详细说明Log4J的实现,怎么找到rollOver方法的。可以下载Log4J的源码,看一下RollingFileAppender类和它的父类
上面的代码是从Log4J的源码中拷贝出来的,保留了格式(包括缩进)。
虽然Log4J是Apache的项目,但是这个代码的风格看起来还不是很Java风格的吧 :(
- 有的代码缩进是不正确的。
- 使用了多次的局部变量renameSucceeded、target、file。这些变量含义多变,影响了代码的可读性。(这是老版C的风格!)
我们要改的逻辑是重命名的逻辑,就是下面的一些代码:
改成:
三、上面的做法有问题
“你在晃点我啊!?照这上面的改法,是要我直接改Log4J的RollingFileAppender啊?这不叫扩展!”
:) 被你发现了!
一个扩展的做法,是自己写一个 RollingFileAppender 的子类,只复然后只覆写上面的的逻辑。
要继承类,覆写逻辑的最小单位是 方法,所以覆写上面rollover方法,虽然实际要覆写只是上面列出的逻辑,结果是方法中代码行有很多是重复的。
按照上面的做法,又出现了新的问题。rollover方法用到的成员变量nextRollover是私有的,子类没有办法访问到。
要绕过 成员变量nextRollover不能访问的问题,一个方法是在子类自己建一个nextRollover成员,父类RollingFileAppender中所有用到的nextRollover的方法,在子类拷贝一份。
用到的方法有: rollOver()、subAppend(LoggingEvent)。还好只有两个。
四、扩展的一团糟 带来的启发
第三节中,给出的解决方法,真是要用”一团糟”来形容
- 大量的重量代码
- 绕过 成员变量nextRollover不能访问的问题,用的猥琐做法
之所以有这些问题,归根结底是因为,Log4J没有把Rollover文件重命名的策略抽出来,没有认识到把可变的策略隔离到方法。
如果独立出这个逻辑成proteceted方法,子类就好扩展了。
当然,根据’组合优于继承’原则, Rollover文件重命名的策略抽成一个成员(即使用策略模式)由外部提供,当然也应该有默认值。这是一个更好的实践!
Log4J的参考资料