背景
希望通过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