spring/logback - 仅针对错误记录 http 请求正文
spring/logback - log http request body for errors only
我在拦截器中使用 MDC
将与 http 请求相关的字段添加到我的日志记录中。现在,我也想在 error
或 fatal
and/or 级别的任何日志中记录请求正文以获取异常。一般来说,我对 MDC
、spring
或 java
完全陌生。
我的 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();
}
}
我在拦截器中使用 MDC
将与 http 请求相关的字段添加到我的日志记录中。现在,我也想在 error
或 fatal
and/or 级别的任何日志中记录请求正文以获取异常。一般来说,我对 MDC
、spring
或 java
完全陌生。
我的 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();
}
}