Search code examples
spring-bootjava-8auditinghttp-trace

Spring Boot: getting query string parameters and request body in AuditApplicationEvent listener


Spring Boot REST app here. I'm trying to configure Spring Boot request auditing to log each and every HTTP request that any resources/controllers receive with the following info:

  1. I need to see in the logs the exact HTTP URL (path) that was requested by the client, including the HTTP method and any query string parameters; and
  2. If there is a request body (such as with a POST or PUT) I need to see the contents of that body in the logs as well

My best attempt so far:

@Component
public class MyAppAuditor {
    private Logger logger;

    @EventListener
    public void handleAuditEvent(AuditApplicationEvent auditApplicationEvent) {
        logger.info(auditApplicationEvent.auditEvent);
    }
}

public class AuditingTraceRepository implements TraceRepository {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher

    @Override
    List<Trace> findAll() {
        throw new UnsupportedOperationException("We don't expose trace information via /trace!");
    }

    @Override
    void add(Map<String, Object> traceInfo) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        AuditEvent traceRequestEvent = new AuditEvent(new Date(), "SomeUser", 'http.request.trace', traceInfo);
        AuditApplicationEvent traceRequestAppEvent = new AuditApplicationEvent(traceRequestEvent);

        applicationEventPublisher.publishEvent(traceRequestAppEvent);
    }
}

However at runtime if I use the following curl command:

curl -i -H "Content-Type: application/json" -X GET 'http://localhost:9200/v1/data/profiles?continent=NA&country=US&isMale=0&height=1.5&dob=range('1980-01-01','1990-01-01')'

Then I only see the following log messages (where MyAppAuditor send audit events):

{ "timestamp" : "14:09:50.516", "thread" : "qtp1293252487-17", "level" : "INFO", "logger" : "com.myapp.ws.shared.auditing.MyAppAuditor", "message" : {"timestamp":"2018-06-29T18:09:50+0000","principal":"SomeUser","type":"http.request.trace","data":{"method":"GET","path":"/v1/data/profiles","headers":{"request":{"User-Agent":"curl/7.54.0","Host":"localhost:9200","Accept":"*/*","Content-Type":"application/json"},"response":{"X-Frame-Options":"DENY","Cache-Control":"no-cache, no-store, max-age=0, must-revalidate","X-Content-Type-Options":"nosniff","Pragma":"no-cache","Expires":"0","X-XSS-Protection":"1; mode=block","X-Application-Context":"application:9200","Date":"Fri, 29 Jun 2018 18:09:50 GMT","Content-Type":"application/json;charset=utf-8","status":"200"}},"timeTaken":"89"}} }

So as you can see, the auditor is picking up the base path (/v1/data/profiles) but is not logging any of the query string parameters. I also see a similar absence of request body info when I hit POST or PUT endpoints that do require a request body (JSON).

What do I need to do to configure these classes (or other Spring classes/configs) so that I get the level of request auditing that I'm looking for?


Solution

  • Fortunately, Actuator makes it very easy to configure those Trace events.

    Adding parameters to Trace info

    You can take a look at all of the options. You'll notice the defaults (line 42) are:

    Include.REQUEST_HEADERS, 
    Include.RESPONSE_HEADERS, 
    Include.COOKIES, 
    Include.ERRORS, 
    Include.TIME_TAKEN
    

    So you'll need to also add Include.PARAMETERS and anything else you'd like to have in the trace. To configure that, there's a configuration property for that management.trace.include.

    So to get what you want (i.e. parameters), plus the defaults, you'd have:

    management.trace.include = parameters, request-headers, response-headers, cookies, errors, time-taken

    Adding request body to Trace info

    In order to get the body, you're going to have to add in this Bean to your Context:

    @Component
    public class WebRequestTraceFilterWithPayload extends WebRequestTraceFilter {
    
        public WebRequestTraceFilterWithPayload(TraceRepository repository, TraceProperties properties) {
            super(repository, properties);
        }
    
        @Override
        protected Map<String, Object> getTrace(HttpServletRequest request) {
            Map<String, Object> trace = super.getTrace(request);
    
            String body = null;
            try {
                body = request.getReader().lines().collect(Collectors.joining("\n"));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
            if(body != null) {
                trace.put("body", body);
            }
    
            return trace;
        }
    
    }
    

    The above code will override the AutoConfigure'd WebRequestTraceFilter bean (which, because it is @ConditionalOnMissingBean will give deference to your custom bean), and pull the extra payload property off of the request then add it to to the Map properties that get published to your TraceRepository!

    Summary

    1. Request Parameters can be added to TraceRepository trace events by via the management.trace.include property
    2. The Request Body can be added to the TraceRepository trace events by creating an extended Bean to read the body off of the HTTP request and supplementing the trace events