源码解析logback日志输出异常堆栈

背景:

我们日常的开发中,使用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的内容而已,和异常堆栈完全不是一回事.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Logback 是一个 Java日志框架,可以用来控制日志输出。它支持多种日志级别,可以通过配置文件来控制日志输出方式。 Logback 的配置文件是一个 XML 文件,通常命名为 `logback.xml`。下面是一个简单的 Logback 配置文件的例子: ```xml <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration> ``` 这个配置文件定义了一个名为 `STDOUT` 的输出器,它将日志输出到控制台。`<encoder>` 元素定义了日志输出的格式,`%-5level` 表示日志级别,`%logger{35}` 表示日志记录器的名称,`%msg%n` 表示日志消息和换行符。 `<root>` 元素定义了日志的根级别,这里设置为 `debug`,表示输出所有级别的日志。`<appender-ref>` 元素将输出器 `STDOUT` 添加到根记录器中。 Logback 支持多种日志级别,从低到高分别是 TRACE、DEBUG、INFO、WARN、ERROR 和 FATAL。可以通过设置日志级别来控制日志输出。例如,如果只想输出 WARN 级别以上的日志,可以将根级别设置为 WARN: ```xml <root level="warn"> <appender-ref ref="STDOUT" /> </root> ``` 这样,INFO 和 DEBUG 级别的日志就不会被输出了。 除了根级别外,还可以为每个记录器单独设置日志级别。例如,可以将某个记录器的级别设置为 DEBUG,而其他记录器的级别设置为 INFO: ```xml <logger name="com.example" level="debug" /> ``` 这样,名为 `com.example` 的记录器就会输出 DEBUG 级别的日志Logback 还支持多种输出方式,例如文件、邮件等。可以通过配置文件来定义这些输出方式。详细的配置方法可以参考 Logback 的官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值