sentry-java: HTTP request body not being reported for application/json encodings

If your HTTP request contains a json-encoded body (eg POST requests on modern API’s), the body doesn’t show up in sentry.

The lib extracts the body using ServletRequest.getParameterMap(): https://github.com/getsentry/raven-java/blob/v7.8.3/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java#L55

This works fine for form url encodings but returns nothing for JSON encodings. eg http://stackoverflow.com/questions/3831680/httpservletrequest-get-json-post-data

The simplest way to get the raw body text is something like servletRequest.getReader().lines().collect(Collectors.joining()) (but getReader() has limitations that it can only be called once, so sentry will have to be clever not to hurt any downstream code). Ideally this could be sent up to sentry.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 11
  • Comments: 19 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Is this fixed or is there any workaround if I am using spring-sentry?

@bretthoerner Can you please provide some example how to set request body manual? Can I define it in ServletContextInitializer as a Spring bean? Or I have to set body every time error happen?

Is it fixed completely? i can’t see body data on sentry error event now with sentry-java 1.7.23

That’s great news @bruno-garcia, can’t wait for that!!

Just a few more suggestions, hopefully they’ll be useful:

  • Instead of having (just) this 4 “random” body sizes, let the users choose the threshold that better fits their app
  • Have an option to log only certain bodies according to request headers such as application/json (or having a similar options such as log text-only-bodies). Useful in cases where we want to log json bodies, but not binary bodies, for example. I mean, if an app allows uploading relatively big binary files (few MBs). Would they be buffered too?

Also, don’t know if that’s possible, but it would also be great if through some configuration, or maybe annotations, we could enable/disable body logging on specific endpoints, therefore having more control on where it makes sense to log or not body requests. Using annotations seems to be the easiest or cleanest way to achieve that, but configuring this through spring properties would allow to enable/disable body logging temporary on specific endpoints without changing any code. I know this is asking way too much for a first solution (in case this is possible), I just wanted to suggest this idea in case it makes sense to you to have it in backlog for a far future release. 😉

I hava fixed this issue using spring-boot step1:

public class HttpEventBuilderHelperWithBody extends HttpEventBuilderHelper {

    @Override
    public void helpBuildingEvent(EventBuilder eventBuilder) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        ContentCachingRequestWrapper wrapper =
                WebUtils.getNativeRequest(requestAttributes.getRequest(), ContentCachingRequestWrapper.class);
        String body = null;
        if (wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                try {
                    body = new String(buf,0,buf.length,wrapper.getCharacterEncoding());
                } catch (UnsupportedEncodingException e) {
                }
            }
        }
        eventBuilder.withSentryInterface(new HttpInterface(requestAttributes.getRequest(),super.getRemoteAddressResolver(),body),false);
        addUserInterface(eventBuilder,requestAttributes.getRequest());
    }

    private void addUserInterface(EventBuilder eventBuilder, HttpServletRequest servletRequest) {
        String username = null;
        if (servletRequest.getUserPrincipal() != null) {
            username = servletRequest.getUserPrincipal().getName();
        }

        UserInterface userInterface = new UserInterface(null, username,
                super.getRemoteAddressResolver().getRemoteAddress(servletRequest), null);
        eventBuilder.withSentryInterface(userInterface, false);
    }
}

step2:

public class SentryClientCustomFactory extends DefaultSentryClientFactory {

	@Override
	public SentryClient createSentryClient(Dsn dsn) {

		try {
			SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn));
			try {
				// `ServletRequestListener` was added in the Servlet 2.4 API, and
				// is used as part of the `HttpEventBuilderHelper`, see:
				// https://tomcat.apache.org/tomcat-5.5-doc/servletapi/
				Class.forName("javax.servlet.ServletRequestListener", false, this.getClass().getClassLoader());
                                // sentryClient.addBuilderHelper(new HttpEventBuilderHelper());
				sentryClient.addBuilderHelper(new HttpEventBuilderHelperWithBody());
			} catch (ClassNotFoundException e) {
				log.debug("The current environment doesn't provide access to servlets,"
						+ " or provides an unsupported version.");
			}
                      
			sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient));
			return configureSentryClient(sentryClient, dsn);
		} catch (Exception e) {
			log.error("Failed to initialize sentry, falling back to no-op client", e);
			return new SentryClient(new NoopConnection(), new ThreadLocalContextManager());
		}
	}

}