Logging request and response in one place with JAX-RS

48,583

Solution 1

You can create filters and easily bind them to the endpoints you need to log, keeping your endpoints lean and focused on the business logic.

Defining a name binding annotation

To bind filters to your REST endpoints, JAX-RS provides the meta-annotation @NameBinding and it can be used as following:

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Logged { }

Logging the HTTP request

The @Logged annotation will be used to decorate a filter class, which implements ContainerRequestFilter, allowing you to handle the request:

@Logged
@Provider
public class RequestLoggingFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // Use the ContainerRequestContext to extract information from the HTTP request
        // Information such as the URI, headers and HTTP entity are available
    }
}

The @Provider annotation marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider scanning phase.

The ContainerRequestContext helps you to extract information from the HTTP request.

Here are methods from the ContainerRequestContext API to get information from the HTTP request that can be useful for your logs:

Logging the HTTP response

For logging the response, consider implementing a ContainerResponseFilter:

@Logged
@Provider
public class ResponseLoggingFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, 
                       ContainerResponseContext responseContext) throws IOException {
        // Use the ContainerRequestContext to extract information from the HTTP request
        // Use the ContainerResponseContext to extract information from the HTTP response
    }
}

The ContainerResponseContext helps you to extract information from the HTTP response.

Here are some methods from the ContainerResponseContext API to get information from the HTTP response that can be useful for your logs:

Binding the filters to your endpoints

To bind the filter to your endpoints methods or classes, annotate them with the @Logged annotation defined above. For the methods and/or classes which are annotated, the filters will be executed:

@Path("/")
public class MyEndpoint {

    @GET
    @Path("{id}")
    @Produces("application/json")
    public Response myMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Logged
        // The logging filters won't be executed when invoking this method
        ...
    }

    @DELETE
    @Logged
    @Path("{id}")
    @Produces("application/json")
    public Response myLoggedMethod(@PathParam("id") Long id) {
        // This method is annotated with @Logged
        // The request logging filter will be executed before invoking this method
        // The response logging filter will be executed before invoking this method
        ...
    }
}

In the example above, the logging filters will be executed only for myLoggedMethod(Long) because it's annotated with @Logged.

Additional information

Besides the methods available in ContainerRequestContext and ContainerResponseFilter interfaces, you can inject ResourceInfo in your filters using @Context:

@Context
ResourceInfo resourceInfo;

It can be used to get the Method and the Class which match with the requested URL:

Class<?> resourceClass = resourceInfo.getResourceClass();
Method resourceMethod = resourceInfo.getResourceMethod();

HttpServletRequest and HttpServletResponse are also available for injection:

@Context
HttpServletRequest httpServletRequest;

@Context
HttpServletResponse httpServletResponse;

Refer to this answer for the types that can be injected with @Context.

Solution 2

Try Interceptors(not just vanilla EJB interceptors you can use CDI with that).

They are there for implementing Cross Cutting Concerns(aspects).

Share:
48,583
Edgaras Karka
Author by

Edgaras Karka

Updated on July 09, 2022

Comments

  • Edgaras Karka
    Edgaras Karka almost 2 years

    I have a RESTEasy web server with lot of methods. I want implement logback to track all requests and responses, but I don't want add log.info() to every methods.

    Maybe there's way to catch requests and responses in one place and log it. Maybe something like a filter on HTTP request process chain on RESTEasy.

    @Path("/rest")
    @Produces("application/json")
    public class CounterRestService {
    
        //Don't want use log in controler every method to track requests and responces
        static final Logger log = LoggerFactory.getLogger(CounterRestService.class); 
    
        @POST
        @Path("/create")
        public CounterResponce create(@QueryParam("name") String name) {
            log.info("create "+name)
            try {
                CounterService.getInstance().put(name);
                log.info("responce data"); // <- :((
                return new CounterResponce();
            } catch (Exception e){
                log.info("responce error data"); // <- :((
                return new CounterResponce("error", e.getMessage());
            }    
        }
    
        @POST
        @Path("/insert")
        public CounterResponce create(Counter counter) {
            try {
                CounterService.getInstance().put(counter);
                return new CounterResponce();
            } catch (Exception e){
                return new CounterResponce("error", e.getMessage());
            }
        }
    
        ...
    }
    
  • jzqa
    jzqa over 6 years
    How to print ContainerResponseContext#getEntityStream() as this is output stream? And ContainerResponseContext#getEntity() returns an object which is org.glassfish.jersey.client.internal.HttpUrlConnector.
  • Lorelorelore
    Lorelorelore about 6 years
    You can print it in this way: BufferedInputStream stream = new BufferedInputStream(requestContext.getEntityStream()); String payload = IOUtils.toString(stream, "UTF-8"); logger.debug("Payload: " + payload); requestContext.setEntityStream(IOUtils.toInputStream(payload‌​, "UTF-8"));
  • ramu
    ramu about 5 years
    this works for requestContext.getEntityStream (InputStream). How to do it for responseContext.getEntityStream (OutputStream) ?
  • rrrocky
    rrrocky about 4 years
    You do not need the entity stream. Just use responseContext.getEntity()
  • Adam Millerchip
    Adam Millerchip almost 4 years
    Sometimes you have to love Java. Doing this with other languages is usually a one-line change to add a middleware module.