SpringBoot生产环境Logback打印错误重复输出登录用户名求助
问题分析与解决方案
你的方案核心问题在于Logback的Context是全局单例,且每次调用configurator.doConfigure会重复加载日志配置、创建新的Appender,同时全局属性LOGGED_USER会被多请求覆盖,最终导致生产环境并发场景下,多个Appender同时输出日志,且带着不同请求设置的用户名。
正确的实现方式:用MDC(映射诊断上下文)
MDC是Logback专门用于存储请求/线程级上下文数据的机制,天然支持线程隔离,完全适配你这种“每个请求携带不同用户信息”的日志需求。
步骤1:修改OncePerRequestFilter的逻辑
去掉原来重新配置Logback的代码,改为在请求开始时将用户名放入MDC,请求结束后清除(避免线程池复用导致数据残留):
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String username = null; Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) { username = authentication.getName(); // 将用户名放入MDC MDC.put("LOGGED_USER", username); } try { filterChain.doFilter(request, response); } finally { // 请求结束后移除MDC中的用户信息,必须在finally块执行 MDC.remove("LOGGED_USER"); } }
步骤2:修改logback.xml的日志格式
把原来的${LOGGED_USER}替换为Logback获取MDC值的占位符%X{LOGGED_USER},还可以设置默认值(比如匿名用户):
<!-- 示例日志格式 --> <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{LOGGED_USER:-anonymous} - %msg%n" />
为什么原来的方案会出问题
- Logback的
Context是全局共享的,所有请求都会操作同一个实例,context.putProperty("LOGGED_USER", username)会被并发请求覆盖,导致日志中的用户名混乱。 - 每次调用
configurator.doConfigure都会重新解析logback.xml,创建新的Appender实例,旧的Appender不会被销毁,最终日志会被多个Appender重复输出,每个Appender可能捕获了不同时刻的LOGGED_USER值,所以会出现多个用户名的情况。
额外注意:异步线程的MDC传递
如果你的系统用到了异步线程(比如@Async),MDC不会自动传递到异步线程中,需要手动复制MDC上下文:
// 提交异步任务时复制MDC Map<String, String> mdcContext = MDC.getCopyOfContextMap(); CompletableFuture.runAsync(() -> { if (mdcContext != null) { MDC.setContextMap(mdcContext); } // 执行异步逻辑 // ... }, executor).whenComplete((v, e) -> MDC.clear());
内容的提问来源于stack exchange,提问作者Danilo Leite Ribeiro
相关产品推荐
相关产品推荐

