You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SLF4J MDC替代方案咨询:JSF应用请求用户名日志记录问题

Great question—this is a super common pitfall with MDC in pooled thread environments like app servers and EJB containers. Let’s break down the root cause, walk through practical fixes, and address whether wrapping SLF4J is necessary.

MDC Leakage: Why It Happens

MDC relies on thread-local storage to track context data like usernames. When your app server or EJB container reuses threads from a pool, any un-cleaned MDC data from a previous request/async task sticks around. That’s exactly what’s happening with your @Asynchronous EJB calls—those methods borrow threads from the container’s async pool, and if the prior thread user didn’t clear MDC, your new request ends up with someone else’s username.

Practical Solutions (No Manual Username Logging)

You don’t need to hardcode usernames into every log call. Here are the most reliable approaches:

1. Bind MDC to the JSF Request Lifecycle

The simplest fix is to set and clear MDC automatically as each request starts and ends. Use a Servlet Filter (the most universal option for Java web apps):

public class MdcUsernameFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest) request;
        // Grab username from the request's authenticated principal
        String username = httpReq.getUserPrincipal() != null 
            ? httpReq.getUserPrincipal().getName() 
            : "anonymous";
        
        MDC.put("username", username);
        try {
            // Let the request proceed
            chain.doFilter(request, response);
        } finally {
            // ALWAYS clean up in finally—even if the request throws an exception
            MDC.remove("username");
        }
    }

    // Implement init() and destroy() with empty bodies if needed
}

Register this filter in your web.xml to run on all requests, and your logs will automatically include the username for every JSF request—no manual work needed.

If you prefer a JSF-native approach, use a Phase Listener to set MDC in the RESTORE_VIEW phase and clear it in RENDER_RESPONSE. Just make sure to wrap the cleanup in a finally block to handle exceptions.

2. Fix Asynchronous EJB MDC Leaks

EJB async calls run on separate pool threads, so the request thread’s MDC won’t transfer automatically. Plus, those threads might have leftover MDC data from prior tasks. Fix this with one of two options:

Option A: Manually Pass and Clean MDC Context

When calling the async method, snapshot the current MDC and pass it to the async task. Then set and clean it inside the task:

// Async EJB Bean
@Stateless
public class AsyncTaskService {
    private static final Logger logger = LoggerFactory.getLogger(AsyncTaskService.class);

    @Asynchronous
    public void runAsyncTask(Map<String, String> mdcContext) {
        MDC.setContextMap(mdcContext);
        try {
            // Your async logic here—logs will include the username from the original request
            logger.info("Starting async task");
        } finally {
            MDC.clear();
        }
    }
}

// JSF Managed Bean (caller)
@ManagedBean
public class TaskController {
    @EJB
    private AsyncTaskService asyncService;

    public void triggerAsync() {
        // Snapshot the current request's MDC context
        Map<String, String> mdcSnapshot = MDC.getCopyOfContextMap();
        asyncService.runAsyncTask(mdcSnapshot);
    }
}

Option B: Use a CDI Interceptor for Async Methods

If you have lots of async EJB methods, avoid repeating code with a CDI interceptor that automatically handles MDC transfer and cleanup:

// Custom interceptor binding annotation
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface AsyncMdcContext {}

// The interceptor itself
@Interceptor
@AsyncMdcContext
public class AsyncMdcInterceptor {
    @AroundInvoke
    public Object intercept(InvocationContext ctx) throws Exception {
        Map<String, String> mdcSnapshot = MDC.getCopyOfContextMap();
        try {
            MDC.setContextMap(mdcSnapshot);
            return ctx.proceed();
        } finally {
            MDC.clear();
        }
    }
}

Just add @AsyncMdcContext to any @Asynchronous EJB method, and the interceptor will handle the rest.

Do You Need to Wrap SLF4J?

Short answer: No, not unless you have very specific custom logging needs.

The MDC + lifecycle cleanup approach is already aligned with SLF4J’s design—MDC was built exactly for this kind of context-aware logging. Wrapping SLF4J would add unnecessary maintenance overhead, unless you need to inject additional custom data (like tenant IDs, request IDs) across all logs and want a single point of control. If it’s just about usernames, stick with the filter/interceptor solutions above.

内容的提问来源于stack exchange,提问作者TownCube

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:00:38