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 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.
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.
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

