间接调用Log4j的日志功能导致类名输出错误解决方案
在使用Log4j的时候,一般都是在每个类中定义一个Logger对象,通过该对象输出日志,此方法需要重复编写创建Logger对象的代码;
考虑编写一个公共Log类,对外提供静态日志输出方法,在该方法内部再调用Logger的方法进行日志输出;此方法下有一个弊端:
当日志中需要输出调用日志请求的类名、方法名、代码行数时,输出的是公共类(Log)中的相关信息,这不符合实际要求,通过分析Log4j的源码发现Log4j的调用堆栈结构如下:

在这里有个概念需要弄清楚,Log4j打出日志方法调用者的类名等信息是通过Java提供的堆栈跟踪信息实现的:
Throwable t = new Throwable();
StackTraceElement[] ste = t.getStackTrace(); 从上图知道Log4j的调用堆栈结构如下:
Caller-->Category-->LoggingEvent-->LocationInfo,因为info等方法在Category中,故堆栈中不包含Logger
在Category创建LoggingEvent对象的时候会把FQCN传递过去,FQCN信息如下:
private static final String FQCN = Category.class.getName(); 接下来看下Log4j是怎么在LocationInfo中获取调用者(调用日志的对象)的类名等信息的,以下是LocationInfo构造器中的主要代码
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);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;} 代码中fqnOfCallingClass即Category中的FQCN,可参考以下代码:
Category类:
publicvoid info(Object message) {if(repository.isDisabled(Level.INFO_INT))return;if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel()))forcedLog(FQCN, Level.INFO, message, null);}
protectedvoid forcedLog(String fqcn, Priority level, Object message, Throwable t) {callAppenders(new LoggingEvent(fqcn, this, level, message, t));}
LoggingEvent类:
public LoggingEvent(String fqnOfCategoryClass, Category logger,Priority level, Object message, Throwable throwable) {this.fqnOfCategoryClass = fqnOfCategoryClass;this.logger = logger;this.categoryName = logger.getName();this.level = level;this.message = message;if(throwable != null) {this.throwableInfo = new ThrowableInformation(throwable, logger);}timeStamp = System.currentTimeMillis();}public LocationInfo getLocationInformation() {if(locationInfo == null) {locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass);}return locationInfo;}
从以上两段代码分析可知,LocationInfo在遍历调用堆栈的时候,匹配到Catetory类后,再往上取一层——即Caller,以此来得到调用者的信息。
综上所述,只要把创建LoggingEvent对象是的fqnOfCategoryClass换成公用类(Log)的名称即可,这样LocationInfo在遍历调用堆栈的时候,在匹配到公用类(Log)后再往上取一层——即得到真正需要调用日志的对象的类信息。
实际上Category中所有日志相关方法(info、debug、error等)都是通过forcedLog方法创建LoggingEvent对象的——从上述代码可见,并且forcedLog方法是protected的,因此只要覆写该方法,改变fqnOfCategoryClass参数为公用类(Log)即可,实现的结构如下:

因为要使用自定义的MyLogger,因此需要自定义工厂类,在Log中按如下方式使用MyLogger
private final static Logger log = MyLogger.getLogger(Log.class.getName(),new MyLoggerFactory());public static void debug(String msg){log.debug(msg);}public static void debug(String msg,Throwable t){log.debug(msg,t);}public static void info(String msg){log.info(msg);}public static void info(String msg,Throwable t){log.info(msg,t);}
}
这样在其他地方直接调用Log的静态日志方法也能正确的输出调用者信息!
附件附上代码~~
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
