背景:
我们日常的开发中,使用logback日志打印错误日志是最常使用的功能了,比如如下代码所示:
logger.error("user login in exception,userId={}",userId, e);
如果有异常输出,则会有如下的异常日志输出:
user login in exception,userId=用户id,
java.lang.RuntimeException:用户不存在
at com.user.UserServiceImpl.getUser(UserServiceImpl.java:144)
at com.user.UserController.getUser(UserController.java:269)
那么我们知道logback是如何打印出异常堆栈的吗? 如果我们这样子写:
logger.error("user login in exception={},userId={}",e,userId);
把异常放在userId变量的前面,这样还能打印出异常的堆栈吗?
源码追踪:
我们先来跟踪下logger.error中构造的LoggingEvent对象:
public LoggingEvent(String fqcn, Logger logger, Level level, String message, Throwable throwable, Object[] argArray) {
this.fqnOfLoggerClass = fqcn;
this.loggerName = logger.getName();
this.loggerContext = logger.getLoggerContext();
this.loggerContextVO = loggerContext.getLoggerContextRemoteView();
this.level = level;
this.message = message;
this.argumentArray = argArray;
if (throwable == null) {
throwable = extractThrowableAnRearrangeArguments(argArray);
}
if (throwable != null) {
this.throwableProxy = new ThrowableProxy(throwable);
LoggerContext lc = logger.getLoggerContext();
if (lc.isPackagingDataEnabled()) {
this.throwableProxy.calculatePackagingData();
}
}
timeStamp = System.currentTimeMillis();
}
重点看extractThrowableAnRearrangeArguments方法,这个方法的作用是判断参数数组中的最后一个参数是否是throwable异常类,这里的话是[useId,e],很明显,这里最后一个参数是异常类
private Throwable extractThrowableAnRearrangeArguments(Object[] argArray) {
Throwable extractedThrowable = EventArgUtil.extractThrowable(argArray);
if (EventArgUtil.successfulExtraction(extractedThrowable)) {
this.argumentArray = EventArgUtil.trimmedCopy(argArray);
}
return extractedThrowable;
}
再继续往下看,如果是异常类的时候会新建一个异常的代理类
this.throwableProxy = new ThrowableProxy(throwable);
然后我们跟踪一直到PatternLayout类输出具体的日志内容,首先看下PatternLayout类的构造:
static {
defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
defaultConverterMap.put("d", DateConverter.class.getName());
defaultConverterMap.put("date", DateConverter.class.getName());
defaultConverterMap.put("r", RelativeTimeConverter.class.getName());
defaultConverterMap.put("relative", RelativeTimeConverter.class.getName());
defaultConverterMap.put("level", LevelConverter.class.getName());
defaultConverterMap.put("le", LevelConverter.class.getName());
defaultConverterMap.put("p", LevelConverter.class.getName());
defaultConverterMap.put("t", ThreadConverter.class.getName());
defaultConverterMap.put("thread", ThreadConverter.class.getName());
defaultConverterMap.put("lo", LoggerConverter.class.getName());
defaultConverterMap.put("logger", LoggerConverter.class.getName());
defaultConverterMap.put("c", LoggerConverter.class.getName());
defaultConverterMap.put("m", MessageConverter.class.getName());
defaultConverterMap.put("msg", MessageConverter.class.getName());
defaultConverterMap.put("message", MessageConverter.class.getName());
defaultConverterMap.put("C", ClassOfCallerConverter.class.getName());
defaultConverterMap.put("class", ClassOfCallerConverter.class.getName());
defaultConverterMap.put("M", MethodOfCallerConverter.class.getName());
defaultConverterMap.put("method", MethodOfCallerConverter.class.getName());
defaultConverterMap.put("L", LineOfCallerConverter.class.getName());
defaultConverterMap.put("line", LineOfCallerConverter.class.getName());
defaultConverterMap.put("F", FileOfCallerConverter.class.getName());
defaultConverterMap.put("file", FileOfCallerConverter.class.getName());
defaultConverterMap.put("X", MDCConverter.class.getName());
defaultConverterMap.put("mdc", MDCConverter.class.getName());
defaultConverterMap.put("ex", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("exception", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("rEx", RootCauseFirstThrowableProxyConverter.class.getName());
defaultConverterMap.put("rootException", RootCauseFirstThrowableProxyConverter.class.getName());
defaultConverterMap.put("throwable", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("xEx", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("xException", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("xThrowable", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("nopex", NopThrowableInformationConverter.class.getName());
defaultConverterMap.put("nopexception", NopThrowableInformationConverter.class.getName());
defaultConverterMap.put("cn", ContextNameConverter.class.getName());
defaultConverterMap.put("contextName", ContextNameConverter.class.getName());
defaultConverterMap.put("caller", CallerDataConverter.class.getName());
defaultConverterMap.put("marker", MarkerConverter.class.getName());
defaultConverterMap.put("property", PropertyConverter.class.getName());
defaultConverterMap.put("n", LineSeparatorConverter.class.getName());
defaultConverterMap.put("black", BlackCompositeConverter.class.getName());
defaultConverterMap.put("red", RedCompositeConverter.class.getName());
defaultConverterMap.put("green", GreenCompositeConverter.class.getName());
defaultConverterMap.put("yellow", YellowCompositeConverter.class.getName());
defaultConverterMap.put("blue", BlueCompositeConverter.class.getName());
defaultConverterMap.put("magenta", MagentaCompositeConverter.class.getName());
defaultConverterMap.put("cyan", CyanCompositeConverter.class.getName());
defaultConverterMap.put("white", WhiteCompositeConverter.class.getName());
defaultConverterMap.put("gray", GrayCompositeConverter.class.getName());
defaultConverterMap.put("boldRed", BoldRedCompositeConverter.class.getName());
defaultConverterMap.put("boldGreen", BoldGreenCompositeConverter.class.getName());
defaultConverterMap.put("boldYellow", BoldYellowCompositeConverter.class.getName());
defaultConverterMap.put("boldBlue", BoldBlueCompositeConverter.class.getName());
defaultConverterMap.put("boldMagenta", BoldMagentaCompositeConverter.class.getName());
defaultConverterMap.put("boldCyan", BoldCyanCompositeConverter.class.getName());
defaultConverterMap.put("boldWhite", BoldWhiteCompositeConverter.class.getName());
defaultConverterMap.put("highlight", HighlightingCompositeConverter.class.getName());
defaultConverterMap.put("lsn", LocalSequenceNumberConverter.class.getName());
}
我们可以看到DateConverter日期转换器,ThreadConverter线程转换器,ThrowableProxyConverter异常转换器等赫然在列,
也就是一条日志,都会经过这些转换器的转换,结合前面的ThrowableProxy我们来看一看ThrowableProxyConverter是怎么输出异常堆栈的:
protected void subjoinSTEPArray(StringBuilder buf, int indent, IThrowableProxy tp) {
StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
int commonFrames = tp.getCommonFrames();
boolean unrestrictedPrinting = lengthOption > stepArray.length;
int maxIndex = (unrestrictedPrinting) ? stepArray.length : lengthOption;
if (commonFrames > 0 && unrestrictedPrinting) {
maxIndex -= commonFrames;
}
int ignoredCount = 0;
for (int i = 0; i < maxIndex; i++) {
StackTraceElementProxy element = stepArray[i];
if (!isIgnoredStackTraceLine(element.toString())) {
ThrowableProxyUtil.indent(buf, indent);
printStackLine(buf, ignoredCount, element);
ignoredCount = 0;
buf.append(CoreConstants.LINE_SEPARATOR);
} else {
++ignoredCount;
if (maxIndex < stepArray.length) {
++maxIndex;
}
}
}
if (ignoredCount > 0) {
printIgnoredCount(buf, ignoredCount);
buf.append(CoreConstants.LINE_SEPARATOR);
}
if (commonFrames > 0 && unrestrictedPrinting) {
ThrowableProxyUtil.indent(buf, indent);
buf.append("... ").append(tp.getCommonFrames()).append(" common frames omitted").append(CoreConstants.LINE_SEPARATOR);
}
}
重点查看
printStackLine(buf, ignoredCount, element);
ignoredCount = 0;
buf.append(CoreConstants.LINE_SEPARATOR);
这个就是打印异常堆栈的地方,这里每打印一行异常的行就打印一个换行符,所以你才能在日志中看到完整的堆栈,ok,这就是logback堆栈打印异常堆栈日志的源码过程.
答疑:
那我们再来看一下一开始提出的问题:这样打印错误日志,
logger.error("user login in exception={},userId={}",e,userId);
可以打印出异常堆栈吗?
答案很显然是不能,因为之前已经说过logback只判断入参数组的最后一个参数是否是异常类,很明显这里[e,userId]最后一个参数是userId,不是异常类,所以不会有异常堆栈打印出来,至于中间的占位符的输出仅仅只是e.toString的内容而已,和异常堆栈完全不是一回事.