试图在不修改Log4J源码情况下,用静态方法打印正确日志信息失败

在开源的项目中使用 Log4j一般 都是在类中添加一静态变量,如
 protected static Log log = LogFactory.getLog(RequestProcessor.class); //通用日志组件

 protected static Logger log = Logger.getLogger(RequestProcessor.class); //直接申明为Log4j的logger


原来有一个项目是做了一个自定义了 MyLogger 类, 其中的 debug, info 等到方法直接就是调用 log4j 的 logger 的对应方法. 别的代码中调用 MyLogger 的静态方法打印日志时, 依据log4j.properties的配置显示 %l  定位信息就始终是 MyLogger

如: 2007-05-23 12:18:46,828 [DEBUG] com.unmi.MyLogger.debug(MyLogger.java:12) Hello MyLogger

而不是调用类,

如:2007-05-23 12:18:46,828 [DEBUG] com.unmi.MyClass.debug(MyClass.java:7) Hello MyLogger

记得当时为了解决这个问题, 是在 MyLogger 的日志打印方法中 new Throw() 实例,然后从异常栈中找到调用类的信息,放在msg中输出,而不用 %l 输出,后来发现Log4j的代码行定位的实现也如出一则。前面那个项目的做法也就造成了两次找寻异常栈信息,势必耗费不少的资源。

于是思量着,是否能在 MyLogger 中不 new Throw() 也能让 log4j 有正确的定位信息输出, 考虑直接用 AspectJ 来拦截,同时带着一个疑问:是不是用 Logger.getLogger(Class clz) 构造 Logger 时传入的是什么Class实例,输出时就会定位在这个类上。对此作了下面的试验,共有四个文件,Eclipse 中安装了 AspectJ 的插件 ajdt
一:MyLogger.java
 1package com.unmi;
 2import org.apache.log4j.Logger;
 3
 4public class MyLogger
 5{
 6    //留待Aspecj来初始化
 7    public static Logger log;
 8
 9    //打印日志的方法
10    public static void debug(String msg)
11    {
12        log.debug(msg);
13    }
14}

二:MyClass.java
 1package com.unmi;
 2public class MyClass
 3{
 4    public void foo()
 5    {
 6        //希望打印日志定位是在这一行
 7        MyLogger.debug("Hello MyLogger");
 8    }
 9
10    public static final void main(String args[])
11    {
12        MyClass myObject = new MyClass();
13        myObject.foo();
14    }
15}

三:LoggerRecipe.aj
 1package com.unmi;
 2import org.apache.log4j.Logger;
 3
 4public aspect LoggerRecipe
 5{
 6    //拦截MyLogger中的所有日志打印方法
 7    pointcut logMethod():call(public void com.unmi.MyLogger.*(..));
 8
 9    void around():logMethod()
10    {
11        //能获取到需打印日志的类实例
12        Class clz = thisJoinPoint.getThis().getClass();
13        System.out.println("Construct Logger by Class: "+clz);
14
15        //在执行日志方法前初始化Logger实例
16        MyLogger.log = Logger.getLogger(clz);
17
18        //执行实际的日志打印方法
19        proceed();
20    }
21}

四:log4j.properties
1log4j.rootLogger=DEBUG,console
2log4j.appender.console=org.apache.log4j.ConsoleAppender
3log4j.appender.console.layout=org.apache.log4j.PatternLayout
4log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%5p] %l %m%n

执行 MyClass 后的输出是:
Construct Logger by Class: class com.unmi.MyClass
2007-05-23 12:29:29,031 [DEBUG] com.unmi.MyLogger.debug(MyLogger.java:12) Hello MyLogger

我们能看到确实在用调用类 MyClass 构造的 Logger 实例,但是输出日志时指示仍然是 MyLogger, 必须修改 Log4j 的源码才能输出日志时定位在 com.unmi.MyClass中

关于 Log4j 如何获知调用类的信息请参看以前写的一篇日志:Log4j是输出日志时是如何获知当前方法、行号的

其实这样的想法与做法即使达成所愿也不会带多大益处,因为并没有减少代码的侵入性,仍然要引用自定义日志类,写下日志输出方法。 Logger.getLogger(Class clz)传入的 Class 只不过是一个 Logger 实例缓存时的标识。同时关于这个实现的思考也可以停下来了。


Spring似乎也没有完美的解决此种问题,当你把一个 Advice 中的 before 方法写成
1before(Method method, Object[] args, Object target) throws Throwabel
2{
3    Logger log = Logger.getLogger(target.getClass());
4    log.debug(method.getName());
5}
输出的日志也是定位在你的 Advice 类中,而不是你所拦截的那个类。 永久链接 https://yanbin.blog/log4j-static-method-log/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。