I am using MDC
inside an interceptor to add http request related fields to my logging. Now, i want to log the request body along any log of level error
or fatal
and/or for exceptions as well. I am totally new to MDC
, spring
or java
in general.
The providers section of my logback-spring.xml
looks currently like this:
<providers>
<timestamp>
<!-- <fieldName>timestamp</fieldName> -->
<timeZone>UTC</timeZone>
</timestamp>
<version/>
<nestedField>
<fieldName>log</fieldName>
<providers>
<logLevel />
</providers>
</nestedField>
<message/>
<loggerName/>
<threadName/>
<context/>
<pattern>
<omitEmptyFields>true</omitEmptyFields>
<pattern>
{
"trace": {
"id": "%mdc{X-B3-TraceId}",
"span_id": "%mdc{X-B3-SpanId}",
"parent_span_id": "%mdc{X-B3-ParentSpanId}",
"exportable": "%mdc{X-Span-Export}"
}
}
</pattern>
</pattern>
<mdc>
<excludeMdcKeyName>traceId</excludeMdcKeyName>
<excludeMdcKeyName>spanId</excludeMdcKeyName>
<excludeMdcKeyName>parentId</excludeMdcKeyName>
<excludeMdcKeyName>spanExportable</excludeMdcKeyName>
<excludeMdcKeyName>X-RequestId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-TraceId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-SpanId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-ParentSpanId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-Sampled</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-Flags</excludeMdcKeyName>
<excludeMdcKeyName>B3</excludeMdcKeyName>
<excludeMdcKeyName>X-Span-Export</excludeMdcKeyName>
</mdc>
<stackTrace/>
</providers>
```
It will be a bit of advice than a ready-made solution, but I think the solution may not be so obvious.
In general, if you read payload once from the request, from InputStream of HttpRequest, it may no longer be available for the rest of the spring application. (Spring probably does (or can do) a proxy object on it to store a read request payload from InputStream to access it several times).
I think, you can create HTTP filter and set MDC
context after processing HTTP request - but In that case you can only log error message with body after processing this request, not during (when you log messages usually you don't know if it will end up this http request with 500 or not).
If we talking about Spring, you have here abstract filter class that can help you: org.springframework.web.filter.AbstractRequestLoggingFilter
Here is simple filter created by me (but not tested), based on source of AbstractRequestLoggingFilter
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@Component
public class TestFilter extends OncePerRequestFilter {
private static final int MAX_PAYLOAD_LENGTH = 50_000;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if (!(request instanceof ContentCachingRequestWrapper)) {
requestToUse = new ContentCachingRequestWrapper(request, MAX_PAYLOAD_LENGTH);
}
try {
filterChain.doFilter(requestToUse, response);
} catch (RuntimeException | ServletException | IOException e) {
MDC.put("request-payload", convertRequestPayloadToString(request));
// You can also log your exception here
throw e;
} finally {
if (response.getStatus() != 200) {
MDC.put("request-payload", convertRequestPayloadToString(request));
}
}
}
private String convertRequestPayloadToString(HttpServletRequest request) {
ContentCachingRequestWrapper wrapper =
WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
StringBuilder sb = new StringBuilder();
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);
String payload;
try {
payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
sb.append(payload);
}
}
return sb.toString();
}
}