Logback在Docker容器退出时抛出ConcurrentModificationException问题求助
问题:Docker容器内Logback退出时抛出ConcurrentModificationException异常
应用作为独立Java程序运行时正常,但在Docker容器内退出时触发ConcurrentModificationException异常,堆栈跟踪如下:
Exception in thread "Logback shutdown hook [default]" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997) at ch.qos.logback.classic.LoggerContext.fireOnReset(LoggerContext.java:323) at ch.qos.logback.classic.LoggerContext.reset(LoggerContext.java:226) at ch.qos.logback.classic.LoggerContext.stop(LoggerContext.java:348) at ch.qos.logback.core.hook.ShutdownHookBase.stop(ShutdownHookBase.java:39) at ch.qos.logback.core.hook.DelayingShutdownHook.run(DelayingShutdownHook.java:57) at java.base/java.lang.Thread.run(Thread.java:834)
Logback配置(logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!--See https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html --> <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="ch.qos.logback.contrib.json.classic.JsonLayout"> <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter"> <!-- <prettyPrint>true</prettyPrint> --> </jsonFormatter> <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSXXX</timestampFormat> <includeContextName>false</includeContextName> <appendLineSeparator>true</appendLineSeparator> </layout> <charset>UTF-8</charset> </encoder> </appender> <appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender"> <queueSize>5000</queueSize> <!-- Default is 256 --> <discardingThreshold>0</discardingThreshold> <!-- Instruction to drop events once the buffer reaches the max capacity. Default value is 20%. 20 means drop lower event when has 20% capacity remaining --> <neverBlock>true</neverBlock><!-- default false. Setting it to true to cause the Appender not block the application and just drop the messages --> <maxFlushTime>5000</maxFlushTime> <!-- The default maximum queue flush time allowed during appender stop. If the worker takes longer than this time it will exit, discarding any remaining cartItems in the queue --> <appender-ref ref="CONSOLE_JSON"/> </appender> <logger name="org.springframework.web" level="INFO"/> <root level="INFO"> <appender-ref ref="ASYNCSTDOUT"/> </root> </configuration>
POM依赖构件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.8</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.5.8</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> <version>2.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.14</version> </dependency> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>6.6</version> </dependency> <dependency> <groupId>ch.qos.logback.contrib</groupId> <artifactId>logback-jackson</artifactId> <version>0.1.5</version> </dependency> <dependency> <groupId>ch.qos.logback.contrib</groupId> <artifactId>logback-json-classic</artifactId> <version>0.1.5</version> </dependency> </dependencies>
解决方案
问题根源
异常发生在Logback关闭钩子遍历LoggerContext的监听器列表时,由于多线程并发修改非线程安全的ArrayList导致。Docker环境下JVM关闭流程与本地存在差异,触发了监听器在遍历过程中被修改的时序问题,而本地环境未复现该场景。
具体修复步骤
升级Logback版本
Logback 1.2.3存在监听器列表并发修改的bug,1.2.9及以上版本已修复该问题。将pom中Logback相关依赖版本升级到1.2.10(与Spring Boot 2.5.8兼容):<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.10</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.10</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.2.10</version> </dependency>调整Logback关闭钩子配置
若暂时无法升级版本,可尝试两种调整方式:- 移除自定义
DelayingShutdownHook,使用Spring Boot默认的关闭处理逻辑:<!-- 注释或删除该行 --> <!-- <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> --> - 增加关闭延迟时间,确保异步日志队列完全刷写完成后再执行关闭:
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"> <delay>10000</delay> <!-- 设置10秒延迟 --> </shutdownHook>
- 移除自定义
升级Spring Cloud Sleuth版本
当前使用的Sleuth 2.1.0与Spring Boot 2.5.8版本不匹配(对应Spring Cloud版本应为2020.0.x),升级到兼容版本可避免关闭时修改Logback监听器:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> <version>3.0.4</version> </dependency>
验证
修改后重新构建Docker镜像,运行容器并正常退出,检查日志是否不再出现ConcurrentModificationException异常。
内容的提问来源于stack exchange,提问作者Amol Aggarwal
相关产品推荐
相关产品推荐

