前言
当我们在使用日志框架的时候,每个类都要通过工厂方法获取一个日志对象来打印日志,感觉太麻烦了。所以想着去封装一个日志静态类。但是问题是封装的那个静态日志类打印出的类信息都是日志类自己,这肯定不是我们想要的啊,我们需要的是当前调用打印日志的这个类的信息啊。
开始思考
我们发现平时使用的Logback等日志框架能准确捕获源代码的所在的类、方法、行。但java并没有提供相应的方法,这似乎很神奇。其实Logback是通过java错误堆栈来实现的,也就是说通过new一个异常Throwable,然后再捕获,从而得到堆栈信息,在进行分析就可以得到行号等信息了。
我们可以借鉴这种思想,在静态日志类中,获取当前线程的堆栈信息,然后找到实际调用者。JDK的Thread类提供了一个getStackTrace()可以获取线程的堆栈跟踪链。下面来看一下这个方法。
/**
* 返回一个表示该线程堆栈转储的堆栈跟踪元素数组。如果该线程尚未启动或已经终止,则该方法将返回一个零长度数组。
* 如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该序列中最新的方法调用。最后一个元素代表堆栈底,是该序列中最旧的方法调用。
*/
public StackTraceElement[] getStackTrace() {
// 如果该线程不是当前线程
if (this != Thread.currentThread()) {
// 获取安全管理器
SecurityManager security = System.getSecurityManager();
// 如果有安全管理器
if (security != null) {
// 检查是否有权限获取堆栈跟踪
security.checkPermission(SecurityConstants.GET_STACK_TRACE_PERMISSION);
}
// 如果线程未启动或已终止,则不会获取堆栈跟踪信息,直接返回一个空堆栈
if (!isAlive()) {
return EMPTY_STACK_TRACE;
}
StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
StackTraceElement[] stackTrace = stackTraceArray[0];
// 可能线程刚刚终止,没有获取到堆栈跟踪,也返回一个空堆栈
if (stackTrace == null) {
stackTrace = EMPTY_STACK_TRACE;
}
return stackTrace;
} else {
// new一个Exception,然后捕获当前线程的堆栈信息并返回
return (new Exception()).getStackTrace();
}
}
那么堆栈信息时啥样的呢,下面打印一个看看。
java.lang.Thread.getStackTrace(Thread.java:1559)
util.Logger.getCaller(Logger.java:20)
util.Logger.log(Logger.java:60)
util.Logger.info(Logger.java:99)
byteBuf.test1.main(test1.java:36)
这是调用静态日志的方法得到的堆栈信息。可以看到日志类的堆栈信息的后面紧跟着的就是调用日志方法的类。要拿到的就是这个调用类,然后通过这个类名构造一个日志对象,真正的日志打印是通过这个对象来执行的。
静态日志类
public class Logger {
// 当前日志类名
private final static String logClassName = Logger.class.getName();
/**
* 获取最原始被调用的堆栈信息
*/
private static StackTraceElement getCaller() {
// 获取堆栈信息
StackTraceElement[] traceElements = Thread.currentThread()
.getStackTrace();
if (null == traceElements) {
return null;
}
// 最原始被调用的堆栈信息
StackTraceElement caller = null;
// 循环遍历到日志类标识
boolean isEachLogFlag = false;
// 遍历堆栈信息,获取出最原始被调用的方法信息
// 当前日志类的堆栈信息完了就是调用该日志类对象信息
for (StackTraceElement element : traceElements) {
// 遍历到日志类
if (element.getClassName().equals(logClassName)) {
isEachLogFlag = true;
}
// 下一个非日志类的堆栈,就是最原始被调用的方法
if (isEachLogFlag) {
if (!element.getClassName().equals(logClassName)) {
caller = element;
break;
}
}
}
return caller;
}
/**
* 自动匹配请求类名,生成logger对象
*/
private static org.slf4j.Logger log() {
// 最原始被调用的堆栈对象
StackTraceElement caller = getCaller();
// 空堆栈处理
if (caller == null) {
return LoggerFactory.getLogger(Logger.class);
}
// 取出被调用对象的类名,并构造一个Logger对象返回
return LoggerFactory.getLogger(caller.getClassName());
}
// 下列是封装后的方法
// 省略其它方法的封装
public static void info(String message) {
log().info(message);
}
}