Log4j 是如何获取 类名、函数名、行数的
见 org.apache.log4j.spi.LocationInfo.LocationInfo(Throwable t, String fqnOfCallingClass)
源码分析
Object[] noArgs = null;
// 获取函数调用栈
Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs);
String prevClass = NA;
for(int i = elements.length - 1; i >= 0; i--) {
String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
// 判断当前调用的类是否为 Category
if(fqnOfCallingClass.equals(thisClass)) {
int caller = i + 1;
if (caller < elements.length) {
className = prevClass;
methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
if (fileName == null) {
fileName = NA;
}
int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
if (line < 0) {
lineNumber = NA;
} else {
lineNumber = String.valueOf(line);
}
StringBuffer buf = new StringBuffer();
buf.append(className);
buf.append(".");
buf.append(methodName);
buf.append("(");
buf.append(fileName);
buf.append(":");
buf.append(lineNumber);
buf.append(")");
this.fullInfo = buf.toString();
}
return;
}
prevClass = thisClass;
}
函数调用栈
org.apache.log4j.spi.LoggingEvent.getLocationInformation(LoggingEvent.java:253)
↓
org.apache.log4j.helpers.PatternParser$LocationPatternConverter.convert(PatternParser.java:500)
↓
org.apache.log4j.helpers.PatternConverter.format(PatternConverter.java:65)
↓
org.apache.log4j.PatternLayout.format(PatternLayout.java:506)
↓
org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:310)
↓
org.apache.log4j.WriterAppender.append(WriterAppender.java:162)
↓
org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
↓
org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)
↓
org.apache.log4j.Category.callAppenders(Category.java:206)
↓
xxx.xxx.Main(args:line)
从函数调用栈从下往上开始查找 Category ,若当前调用类为 Category, 则上层就是 调用者(这里即Main 方法)的调用信息。
如何手动获取 函数调用信息
(类名、函数名、行号)
// 指定 fqnOfCategoryClass 为 LoggingEvent.class.getName()
LoggingEvent event = new LoggingEvent(LoggingEvent.class.getName(), logger, Level.DEBUG, "i am message", null);
// 然后手动获取 LocationInfo,这时函数调用栈的倒数第二层就是
// org.apache.log4j.spi.LoggingEvent.getLocationInformation(:line)
// 此时的 line 就是下面这行代码的 行号
LocationInfo info = event.getLocationInformation();
上面获取的行号只是 event.getLocationInformation() 的行数,所以要想获得自己想要的行号,必须对以上操作进行包装。
献上一个包装的 Logger
maven
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
wrapper Logger
注意观察 FQCN 的使用
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
/**
* wrapper org.apache.log4j.Logger
*
* @author Jinpeng Cui
*
*/
public class Logger {
public static final boolean enable = true;
private static final String FQCN = Logger.class.getName();
private static org.apache.log4j.Logger proxy;
public Logger(final String clazzName) {
proxy = org.apache.log4j.Logger.getLogger(clazzName);
}
public static Logger getLogger(final Class<?> clazz) {
return new Logger(clazz.getName());
}
public void error(String message) {
if (isErrorEnable()) {
forceLog(Level.ERROR, message);
}
}
public void info(String message) {
if (isInfoEnabled()) {
forceLog(Level.INFO, message);
}
}
public void debug(String message) {
if (isDebugEnabled()) {
forceLog(Level.DEBUG, message);
}
}
private void forceLog(Level level, String message) {
proxy.callAppenders(new LoggingEvent(FQCN, proxy, level, message, null));
}
public boolean isErrorEnable() {
return enable && proxy.isEnabledFor(Level.ERROR);
}
public boolean isInfoEnabled() {
return enable && proxy.isEnabledFor(Level.INFO);
}
public boolean isDebugEnabled() {
return enable && proxy.isEnabledFor(Level.DEBUG);
}
}