refactor(server): slim down request logging to core parameters only

This commit is contained in:
yun-zhi-ztl 2026-03-19 14:13:39 +08:00 • committed by Xudong Sun
parent cd570ed208
commit 049f5acb64
3 changed files with 83 additions and 52 deletions

View file

@ -15,24 +15,32 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Logs inbound HTTP requests and responses with truncation suitable for operational debugging.
* Logs inbound HTTP requests with only core parameters to keep log files compact.
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
private static final int MAX_LOG_BODY_LENGTH = 512;
private static final int MAX_LOG_BODY_LENGTH = 200;
private static final Set<String> SKIP_PREFIXES = Set.of(
"/actuator", "/favicon.ico", "/assets/"
);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String uri = request.getRequestURI();
if (shouldSkip(uri)) {
filterChain.doFilter(request, response);
return;
}
ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper cachedResponse = new ContentCachingResponseWrapper(response);
@ -52,45 +60,43 @@ public class RequestLoggingFilter extends OncePerRequestFilter {
String queryString = request.getQueryString();
String fullUrl = queryString != null ? requestUri + "?" + queryString : requestUri;
String contentType = request.getContentType();
String userAgent = request.getHeader("User-Agent");
StringBuilder sb = new StringBuilder();
sb.append("\n========== HTTP Request ==========\n");
sb.append("URL: ").append(request.getMethod()).append(" ").append(fullUrl).append("\n");
sb.append("Remote Address: ").append(request.getRemoteAddr()).append("\n");
sb.append("Headers: ").append(getHeaders(request)).append("\n");
sb.append(request.getMethod()).append(" ").append(fullUrl);
sb.append(" | ").append(response.getStatus());
sb.append(" | ").append(duration).append("ms");
sb.append(" | ").append(request.getRemoteAddr());
if (contentType != null) {
sb.append(" | Content-Type: ").append(contentType);
}
if (userAgent != null) {
sb.append(" | UA: ").append(truncate(userAgent, 80));
}
String requestBody = getRequestBody(request);
if (requestBody != null && !requestBody.isBlank()) {
sb.append("Request Body: ").append(requestBody).append("\n");
sb.append(" | Body: ").append(requestBody);
}
sb.append("Response Status: ").append(response.getStatus()).append("\n");
String responseBody = getResponseBody(response);
if (responseBody != null && !responseBody.isBlank()) {
sb.append("Response Body: ").append(responseBody).append("\n");
}
sb.append("Duration: ").append(duration).append("ms\n");
sb.append("===================================");
log.info(sb.toString());
}
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
private boolean shouldSkip(String uri) {
for (String prefix : SKIP_PREFIXES) {
if (uri.startsWith(prefix)) {
return true;
}
}
return headers;
return false;
}
private String getRequestBody(ContentCachingRequestWrapper request) {
byte[] buf = request.getContentAsByteArray();
if (buf.length > 0) {
try {
return truncateBody(new String(buf, request.getCharacterEncoding()));
return truncate(new String(buf, request.getCharacterEncoding()), MAX_LOG_BODY_LENGTH);
} catch (UnsupportedEncodingException e) {
return "[unknown encoding]";
}
@ -98,23 +104,10 @@ public class RequestLoggingFilter extends OncePerRequestFilter {
return null;
}
private String getResponseBody(ContentCachingResponseWrapper response) {
byte[] buf = response.getContentAsByteArray();
if (buf.length > 0) {
try {
return truncateBody(new String(buf, response.getCharacterEncoding()));
} catch (UnsupportedEncodingException e) {
return "[unknown encoding]";
}
private String truncate(String value, int maxLength) {
if (value == null || value.length() <= maxLength) {
return value;
}
return null;
}
private String truncateBody(String body) {
if (body == null || body.length() <= MAX_LOG_BODY_LENGTH) {
return body;
}
return body.substring(0, MAX_LOG_BODY_LENGTH)
+ "... [truncated, original length=" + body.length() + "]";
return value.substring(0, maxLength) + "...[truncated]";
}
}

View file

@ -30,5 +30,5 @@ skillhub:
logging:
level:
com.iflytek.skillhub: DEBUG
org.springframework.security: DEBUG
com.iflytek.skillhub: INFO
org.springframework.security: WARN

View file

@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class RequestLoggingFilterTest {
@Test
void doFilterInternal_truncatesLongRequestAndResponseBodiesInLogs(CapturedOutput output)
void doFilterInternal_truncatesLongRequestBodyAndOmitsResponseBody(CapturedOutput output)
throws ServletException, IOException {
RequestLoggingFilter filter = new RequestLoggingFilter();
String longBody = "x".repeat(5_000);
@ -39,10 +39,48 @@ class RequestLoggingFilterTest {
filter.doFilter(request, response, filterChain);
assertThat(output).contains("Request Body: " + "x".repeat(512) + "... [truncated, original length=5000]");
assertThat(output).contains("Response Body: " + "x".repeat(512) + "... [truncated, original length=5000]");
assertThat(output).doesNotContain("Request Body: " + longBody);
assertThat(output).doesNotContain("Response Body: " + longBody);
// Request body should be truncated at 200 chars
assertThat(output).contains("Body: " + "x".repeat(200) + "...[truncated]");
assertThat(output).doesNotContain("Body: " + longBody);
// Response body should not be logged at all
assertThat(output).doesNotContain("Response Body:");
// Original response should still be intact
assertThat(response.getContentAsString()).isEqualTo(longBody);
}
@Test
void doFilterInternal_skipsActuatorEndpoints(CapturedOutput output)
throws ServletException, IOException {
RequestLoggingFilter filter = new RequestLoggingFilter();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/actuator/health");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = (req, res) -> {};
filter.doFilter(request, response, filterChain);
assertThat(output).doesNotContain("/actuator/health");
}
@Test
void doFilterInternal_logsCoreSummaryFields(CapturedOutput output)
throws ServletException, IOException {
RequestLoggingFilter filter = new RequestLoggingFilter();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1/skills");
request.setRemoteAddr("127.0.0.1");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = (req, res) -> {};
filter.doFilter(request, response, filterChain);
assertThat(output).contains("GET /api/v1/skills");
assertThat(output).contains("200");
assertThat(output).contains("127.0.0.1");
assertThat(output).contains("ms");
// Should not contain full headers dump
assertThat(output).doesNotContain("Headers: {");
}
}