Last Updated: January 11, 2021
·
6.675K
· mfornos

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: