JAX-RS Mapped Diagnostic Context Filter
Are you running REST APIs? Using a JAX-RS 2.0 implementation?
Have you ever felt the need of knowing in which client request a log entry was originated?
If you answered «yes» to the above questions, then the following filter will make your day (hopefully).
MDCFilter.java
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import org.slf4j.MDC;
import com.google.common.base.Optional;
/**
* JAX-RS filter that sets a 'client-id', obtained from
* 'X-Forwarded-For' HTTP header or remote IP address
* if not present, as mapped diagnostic context attribute
* for resource methods and classes annotated with @Diagnostic.
*
*/
@Diagnostic
public class MDCFilter implements ContainerRequestFilter, ContainerResponseFilter
{
private static final String CLIENT_ID = "client-id";
@Context
protected HttpServletRequest r;
@Override
public void filter(ContainerRequestContext req) throws IOException
{
Optional<String> clientId = Optional.fromNullable(r.getHeader("X-Forwarded-For"));
MDC.put(CLIENT_ID, clientId.or(defaultClientId()));
}
@Override
public void filter(ContainerRequestContext req, ContainerResponseContext resp) throws IOException
{
MDC.remove(CLIENT_ID);
}
private String defaultClientId()
{
return "Direct:" + r.getRemoteAddr();
}
}
And here the name binding annotation:
Diagnostic.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface Diagnostic
{
}
Configure your logging facility layout pattern to include the %X{client-id}
attribute that will be available from MDC. For the sake of example, a log4j conversion pattern:
log4j.appender.stdout.layout.ConversionPattern=
%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{client-id} %m%n
Lastly, register the MDCFilter in your JAX-RS runtime and that's all, folks.
Now you can annotate your resource methods with @Diagnostic
to enable a Mapped Diagnostic Context per client request.
A trivial example to close this tip:
public class LogResource
{
private static final Logger log = LoggerFactory.getLogger(LogResource.class);
@GET
@Diagnostic
public void info()
{
log.info("Hi ho!");
}
}
Don't forget to play nice!
References: