logback对于过长的堆栈有什么处理方法?

作者站长头像
站长
· 阅读数 22

项目上发生几次由于代码不当,无限递归引发Java栈溢出。即java.lang.StackOverflowError: null

现在的问题是,在logback中输出这样的异常信息时,logger.error("xxx", ex);完整的异常堆栈输出,导致日志巨大,有的日志文件 几百兆,夸张的日志文件高达 10个G

运行环境:

  • Java8 ,JVM主要参数:-Xms512m -Xmx3072m -XX:MetaspaceSize=256m -XX:+UseG1GC -XX:-OmitStackTraceInFastThrow
  • 尝试设置过:-XX:MaxJavaStackTraceDepth=1024 只对最后一段caused by的堆栈深度有作用,对于几百兆的日志文件大小影响很小。
  • 未指定 -Xss参数指定java栈大小,沿用缺省值
  • logback 日志样式有限定 最大msg长度:%.-40000msg (实测该长度只影响 msg的最大长度,对堆栈长度无效)
  • logback 使用的 异步的循环日志,每个日志文件最大限定 5M字节

所以,我的问题是:

  1. logback有没有什么参数设定,对这种超长的堆栈输出,进行优化输出的?
  2. 缘何StackOverflowError输出的日志文件大小差异这么大?比如下面的试验代码,模拟堆栈溢出时,日志文件很小,只有几百K。

附:测试代码:

public static void main(String[] args) throws Throwable {
    LoggerFactory.getLogger(TestStackOverflow.class).error("开始测试堆栈溢出--日志大小--");
    AtomicLong count = new AtomicLong(0);
    try {
        foo(count);
    }
    catch (Throwable e) {
        LoggerFactory.getLogger(TestStackOverflow.class).error("结束测试堆栈溢出:循环次数:" + count);
        LoggerFactory.getLogger(TestStackOverflow.class).error("", e);
    }
}

private static void foo(AtomicLong count) throws Throwable {
    count.incrementAndGet();
    foo(count);
}

补充:已采纳 @乔治 的答案。但需要做部分改进:

样例代码原理上有效,但实际对日志减小有限。 
因为出现 StackOverflow时,同时往往伴有大量的 caused by,样例代码只精简掉了第一层的栈的层数。
接下来,可能要考虑下,是递归对 throwableProxy.getCause() 进行精简? 
还是 要精简掉 caused by 的次数呢? 主要看哪种精简方式不会丢失关键信息,更有利于后期问题排查。
回复
1个回答
avatar
test
2024-07-04

你可以在logback配置文件中添加一个自定义的过滤器来限制堆栈跟踪的长度。比如限制了堆栈跟踪的最大长度为200行:先创建一个自定义的过滤器类:

//TruncateStackTraceFilter.java:

import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class TruncateStackTraceFilter extends Filter {
    private static final int MAX_STACK_TRACE_LENGTH = 200;

    @Override
    public FilterReply decide(Object eventObject) {
        IThrowableProxy throwableProxy = ((ch.qos.logback.classic.spi.LoggingEvent) eventObject).getThrowableProxy();
        truncateStackTrace(throwableProxy);
        return FilterReply.NEUTRAL;
    }

    private void truncateStackTrace(IThrowableProxy throwableProxy) {
        if (throwableProxy == null) {
            return;
        }

        StackTraceElementProxy[] originalStackTrace = throwableProxy.getStackTraceElementProxyArray();

        if (originalStackTrace.length > MAX_STACK_TRACE_LENGTH) {
            StackTraceElementProxy[] truncatedStackTrace = new StackTraceElementProxy[MAX_STACK_TRACE_LENGTH];
            System.arraycopy(originalStackTrace, 0, truncatedStackTrace, 0, MAX_STACK_TRACE_LENGTH);
            throwableProxy.setStackTraceElementProxyArray(truncatedStackTrace);
        }

        truncateStackTrace(throwableProxy.getCause());
    }
}

然后,将这个过滤器添加到logback配置文件:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="your.package.TruncateStackTraceFilter" />
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容