spring/logback - 仅针对错误记录 http 请求正文

spring/logback - log http request body for errors only

我在拦截器中使用 MDC 将与 http 请求相关的字段添加到我的日志记录中。现在,我也想在 errorfatal and/or 级别的任何日志中记录请求正文以获取异常。一般来说,我对 MDCspringjava 完全陌生。

我的 logback-spring.xml 的提供商部分目前看起来像这样:

<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>
```

与现成的解决方案相比,它会有点建议,但我认为解决方案可能不会那么明显。

一般来说,如果您从 HttpRequest 的 InputStream 请求中读取一次有效负载,它可能不再可用于 spring 应用程序的其余部分。 (Spring 可能在其上做(或可以做)一个代理对象来存储来自 InputStream 的读取请求负载以便多次访问它)。

我认为,您可以在处理 HTTP 请求后创建 HTTP 过滤器并设置 MDC 上下文 - 但在这种情况下,您只能在处理此请求后使用正文记录错误消息,而不是在(当您记录消息时)通常你不知道它是否会以 500 结束这个 http 请求。

如果我们谈论 Spring,这里有抽象过滤器 class 可以帮助您:org.springframework.web.filter.AbstractRequestLoggingFilter

这是我根据 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();
    }
}