当前位置: 首页 > 知识库问答 >
问题:

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

秦宏硕
2023-04-27

项目上发生几次由于代码不当,无限递归引发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 的次数呢? 主要看哪种精简方式不会丢失关键信息,更有利于后期问题排查。

共有2个答案

滕璞瑜
2023-04-27

可以考虑借助logstash-logback-encoder这个包来实现
参考https://blog.csdn.net/fu_huo_1993/article/details/124658831

章景同
2023-04-27

你可以在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>
 类似资料:
  • 我有一个关于书库的问题...我想写一个有3个堆栈的程序,我想在每个堆栈上添加这些操作(我应该使用数组): 1.创建堆栈2。按3号。流行音乐4号。显示每个堆栈的顶部 我写的程序,但我遇到了这些错误: 错误4错误LNK2019:未解析的外部符号“public:int\u thiscall stack::IsFull2(void)”(?IsFull2@stack@@QAEHXZ)在函数“public:v

  • 本文向大家介绍堆和栈上的指针有什么区别?相关面试题,主要包含被问及堆和栈上的指针有什么区别?时的应答技巧和注意事项,需要的朋友参考一下 指针所指向的这块内存是在哪里分配的,在堆上称为堆上的指针,在栈上为栈上的指针. 在堆上的指针,可以保存在全局数据结构中,供不同函数使用访问同一块内存. 在栈上的指针,在函数退出后,该内存即不可访问.

  • 问题内容: 通过扩展Vector类,Java的设计师可以快速创建Stack类。这种使用继承的负面影响是什么,尤其是对于Stack类? 非常感谢。 问题答案: 一个问题是Stack是一个类,而不是一个接口。这与收集框架的设计不同,在收集框架的设计中,您的名词通常表示为接口(例如,List,Tree,Set等),并且有特定的实现方式(例如,ArrayList,LinkedList)。如果Java可以避

  • 代码运行良好。只是我不明白。在递归部分有困难的。在此部分中:我的想法是,首先它将一直执行直到一个阈值。则它将执行一次。因此只会被赋值一次。显然那不是真的。 对我来说,困难的部分是在方法中,做什么?在方法中,、做什么?

  • 问题内容: 我正在阅读有关JVM体系结构的信息。今天,我了解了操作数堆栈的概念。根据一篇文章: 在字节码指令执行期间使用操作数堆栈,其方式与在本机CPU中使用通用寄存器的方式类似。 我不明白:操作数堆栈到底是什么,以及它在jvm中如何工作? 问题答案: 这是各种单个字节码操作如何获取其输入以及它们如何提供其输出的方式。 例如,考虑将两个s相加的运算。要使用它,您将两个值压入堆栈,然后使用它: 现在

  • 好的,通过下面的代码,我从pop方法中的所有内容中得到了一个null指针异常。因此,我知道当该方法运行时,“head”必须为null。问题是我不知道为什么,我已经仔细检查了我的代码。请帮忙! 节点类: 链接列表类: 我在一个将十进制数转换为二进制数(对于一个类)的程序中实现了这一点。我可以打印第一个节点的数据,也就是链表的头,但是当再次弹出时,就会出现空值问题。