小言_互联网的博客

希望通过javaagent动态修改log4j最终打印的日志

433人阅读  评论(0)

背景

希望通过javaagent动态修改log4j最终打印的日志

Demo

log4j

package com.leesin;

import org.apache.log4j.Logger;

public class log4j {
	public static void main(String[] args) {
		new Test().test();
	}
}

class Test {
	final Logger log = Logger.getLogger(Test.class);

	public void test() {
		log.info("hello this is log4j info log");
	}
}

log4j.properties

log4j.rootLogger=INFO,console
log4j.additivity.org.apache=true
#console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=INFO
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %m%n

源码分析


通过往下跟,能够知道是一定走了

org.apache.log4j.Category
callAppenders方法

 public void callAppenders(LoggingEvent event) {
        int writes = 0;

        for(Category c = this; c != null; c = c.parent) {
            synchronized(c) {
                if (c.aai != null) {
                    writes += c.aai.appendLoopOnAppenders(event);
                }

                if (!c.additive) {
                    break;
                }
            }
        }

        if (writes == 0) {
            this.repository.emitNoAppenderWarning(this);
        }

    }

可以拦截这个方法,然后得到当前的方法中的第一个参数,当前的第一个参数中有如下的字段

	private static long startTime = System.currentTimeMillis();
    public final transient String fqnOfCategoryClass;
    /** @deprecated */
    private transient Category logger;
    /** @deprecated */
    public final String categoryName;
    /** @deprecated */
    public transient Priority level;
    private String ndc;
    private Hashtable mdcCopy;
    private boolean ndcLookupRequired = true;
    private boolean mdcCopyLookupRequired = true;
    private transient Object message;
    private String renderedMessage;
    private String threadName;
    private ThrowableInformation throwableInfo;
    public final long timeStamp;
    private LocationInfo locationInfo;
    static final long serialVersionUID = -868428216207166145L;
    static final Integer[] PARAM_ARRAY = new Integer[1];
    static final String TO_LEVEL = "toLevel";
    static final Class[] TO_LEVEL_PARAMS;
    static final Hashtable methodCache;	

希望在level中进行嵌入,可是这里的类型并不是String,是Priority,但是断点出显示的值是INFO

进入这个类,看到他的toString方法

public final String toString() {
        return this.levelStr;
    }

然后通过下面这段,验证了log4j是通过event.getLevel().toString();方法拼接最终的结果的

一般log4j提供这个方法给开发者调用
org.apache.log4j.DailyRollingFileAppender

protected void subAppend(LoggingEvent event) {
        long n = System.currentTimeMillis();
        if (n >= this.nextCheck) {
            this.now.setTime(n);
            this.nextCheck = this.rc.getNextCheckMillis(this.now);

            try {
                this.rollOver();
            } catch (IOException var5) {
                LogLog.error("rollOver() failed.", var5);
            }
        }

        super.subAppend(event);
    }

点击 super.subAppend(event);

protected void subAppend(LoggingEvent event) {
        this.qw.write(super.layout.format(event));
        if (super.layout.ignoresThrowable()) {
            String[] s = event.getThrowableStrRep();
            if (s != null) {
                int len = s.length;

                for(int i = 0; i < len; ++i) {
                    this.qw.write(s[i]);
                    this.qw.write(Layout.LINE_SEP);
                }
            }
        }

        if (this.immediateFlush) {
            this.qw.flush();
        }

    }

看到有一个_this_.qw.write(super.layout.format(event));
这里面的format方法应该是把event里面对应的写入到console等地方,所以这个format是关键

public void format(StringBuffer sbuf, LoggingEvent e) {
        String s = this.convert(e);
        if (s == null) {
            if (0 < this.min) {
                this.spacePad(sbuf, this.min);
            }

        } else {
            int len = s.length();
            if (len > this.max) {
                sbuf.append(s.substring(len - this.max));
            } else if (len < this.min) {
                if (this.leftAlign) {
                    sbuf.append(s);
                    this.spacePad(sbuf, this.min - len);
                } else {
                    this.spacePad(sbuf, this.min - len);
                    sbuf.append(s);
                }
            } else {
                sbuf.append(s);
            }

        }
    }

可以看到这里是通过s一次次拼接的,所以看看这个s到底是什么?
进入convert方法

org.apache.log4j.helpers.PatternParser

public String convert(LoggingEvent event) {
            switch(this.type) {
            case 2000:
                return Long.toString(event.timeStamp - LoggingEvent.getStartTime());
            case 2001:
                return event.getThreadName();
            case 2002:
                return event.getLevel().toString();
            case 2003:
                return event.getNDC();
            case 2004:
                return event.getRenderedMessage();
            default:
                return null;
            }
}

到了这里可以知道level调用了toString方法,所以可以通过动态的修改levelStr这个字段达到嵌入到后面相应的字符串的功能.
当然上面还有getRenderedMessage这个方法,以后可以考虑.

结果


在INFO后面加入了leesin


转载:https://blog.csdn.net/dataiyangu/article/details/100736385
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场