Spring Boot 3.5.0升级后Logstash Logback Encoder序列化WebClientResponseException时出现循环引用解析失败问题
Spring Boot 3.5.0升级后Logstash Logback Encoder序列化WebClientResponseException时出现循环引用解析失败问题
嘿,这个问题我刚在升级Spring Boot 3.x的时候也碰到过,结合你给出的日志、代码和配置,咱们一步步来解决:
问题根因分析
从报错信息和logstash-logback-encoder官方的最佳实践来看,核心问题出在这两点:
- 循环引用的异常对象:你在日志代码里用
v(StructuredLoggingFields.EXCEPTION, e)把整个WebClientResponseException.Conflict对象直接传给了结构化参数。升级到Spring Boot 3.5.0后,这个异常类的内部结构(比如mostSpecificCause字段)出现了循环引用,Jackson序列化时就触发了循环检测报错。 - 违反官方最佳实践:logstash-logback-encoder的文档明确提到不要用结构化参数或标记来传递异常对象——异常日志应该交给logback自带的栈跟踪转换器处理,而不是手动塞进结构化参数里。另外可以排除
getMessage()的问题,它只是普通字符串,不会触发循环引用。
最直接的解决方案(推荐)
修改你的日志代码,移除把Exception对象作为结构化参数的部分,改用SLF4J标准的异常日志传递方式:
try { containerApi(tenant).updateContainerStorageUri( tenant, containerId, updateContainerRequest).block(); } catch (RuntimeException e) { log.error( "Exception while updating storageURI for container", v(StructuredLoggingFields.TENANT_ID, tenant), v(StructuredLoggingFields.CONTAINER_ID, containerId), e); // 把异常作为最后一个参数传入,交给logback自动处理 }
这样做的好处:
- 你的Logback配置里已经配置了
ShortenedThrowableConverter,它会自动序列化异常的栈跟踪到stack字段,完全不会有循环引用问题 - 不需要手动传递
EXCEPTION_MESSAGE——logback会自动处理异常的消息,同时你保留了tenant和containerId这两个关键的结构化业务字段
其他可选方案(如果有特殊需求)
如果你的业务场景确实需要结构化传递异常的部分信息,可以试试这两种方式:
- 手动提取异常关键信息:不要传整个异常对象,而是提取你需要的字段(比如响应状态码、错误消息)封装成Map或简单DTO再传入结构化参数,比如:
Map<String, Object> exceptionInfo = new HashMap<>(); exceptionInfo.put("errorMessage", e.getMessage()); if (e instanceof WebClientResponseException) { exceptionInfo.put("statusCode", ((WebClientResponseException) e).getStatusCode().value()); } // 然后把exceptionInfo作为结构化参数传入日志 - Jackson循环引用全局配置:可以在Logback的JSON编码器里添加Jackson配置,禁用循环引用检测。不过这种方式不推荐,因为可能会掩盖其他序列化问题,而且不符合官方最佳实践。
针对你现有Logback配置的小提醒
你的配置已经很规范了,ShortenedThrowableConverter会帮你处理栈跟踪的序列化,只要你不再手动把异常对象塞进结构化参数里,这个配置就能正常工作,不需要额外修改。
内容来源于stack exchange
相关产品推荐
相关产品推荐

