feat: 增强日志系统和请求追踪功能

- 新增CorrelationFilter过滤器,为每个请求生成唯一跟踪ID
- 增强logback配置,支持关联ID、租户信息和用户ID的结构化日志
- 新增RequestLoggingInterceptor,记录详细的HTTP请求响应信息
This commit is contained in:
irving
2025-09-06 20:28:52 -04:00
8 changed files with 696 additions and 5 deletions

View File

@@ -0,0 +1,210 @@
package com.starry.common.interceptor;
import com.alibaba.fastjson2.JSON;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 请求响应日志拦截器
* 记录HTTP请求和响应的详细信息用于调试和监控
*
* @author Claude
*/
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
private static final String START_TIME_ATTRIBUTE = "startTime";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute(START_TIME_ATTRIBUTE, startTime);
// 记录请求开始
logRequestStart(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
long duration = startTime != null ? System.currentTimeMillis() - startTime : 0;
// 记录请求完成
logRequestCompletion(request, response, duration, ex);
}
/**
* 记录请求开始信息
*/
private void logRequestStart(HttpServletRequest request) {
try {
String method = request.getMethod();
String uri = request.getRequestURI();
String queryString = request.getQueryString();
String remoteAddr = getClientIpAddress(request);
String userAgent = request.getHeader("User-Agent");
// 构建完整URL
String fullUrl = uri;
if (queryString != null && !queryString.isEmpty()) {
fullUrl += "?" + queryString;
}
log.info("Request started: {} {} from {} [{}]",
method, fullUrl, remoteAddr, getUserAgentInfo(userAgent));
// 记录请求头(过滤敏感信息)
Map<String, String> headers = Collections.list(request.getHeaderNames())
.stream()
.filter(this::isSafeHeader)
.collect(Collectors.toMap(
name -> name,
request::getHeader
));
if (!headers.isEmpty()) {
log.debug("Request headers: {}", JSON.toJSONString(headers));
}
} catch (Exception e) {
log.warn("Error logging request start: {}", e.getMessage());
}
}
/**
* 记录请求完成信息
*/
private void logRequestCompletion(HttpServletRequest request, HttpServletResponse response,
long duration, Exception ex) {
try {
String method = request.getMethod();
String uri = request.getRequestURI();
int status = response.getStatus();
String statusText = getStatusText(status);
if (ex != null) {
log.error("Request completed with error: {} {} - {} {} ({}ms) - Exception: {}",
method, uri, status, statusText, duration, ex.getMessage());
} else if (status >= 400) {
log.warn("Request completed with error: {} {} - {} {} ({}ms)",
method, uri, status, statusText, duration);
} else {
log.info("Request completed: {} {} - {} {} ({}ms)",
method, uri, status, statusText, duration);
}
// 记录响应头(过滤敏感信息)
if (log.isDebugEnabled()) {
Map<String, String> responseHeaders = response.getHeaderNames()
.stream()
.filter(this::isSafeHeader)
.collect(Collectors.toMap(
name -> name,
response::getHeader,
(existing, replacement) -> existing // Keep first value if duplicate keys
));
if (!responseHeaders.isEmpty()) {
log.debug("Response headers: {}", JSON.toJSONString(responseHeaders));
}
}
} catch (Exception e) {
log.warn("Error logging request completion: {}", e.getMessage());
}
}
/**
* 获取客户端真实IP地址
*/
private String getClientIpAddress(HttpServletRequest request) {
String[] headerNames = {
"X-Forwarded-For",
"X-Real-IP",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
};
for (String headerName : headerNames) {
String ip = request.getHeader(headerName);
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
// X-Forwarded-For可能包含多个IP取第一个
if (ip.contains(",")) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
}
return request.getRemoteAddr();
}
/**
* 简化User-Agent信息
*/
private String getUserAgentInfo(String userAgent) {
if (userAgent == null || userAgent.isEmpty()) {
return "Unknown";
}
if (userAgent.contains("WeChat")) {
return "WeChat";
} else if (userAgent.contains("Chrome")) {
return "Chrome";
} else if (userAgent.contains("Safari")) {
return "Safari";
} else if (userAgent.contains("Firefox")) {
return "Firefox";
} else if (userAgent.contains("PostmanRuntime")) {
return "Postman";
} else {
return "Other";
}
}
/**
* 获取HTTP状态码描述
*/
private String getStatusText(int status) {
if (status >= 200 && status < 300) {
return "OK";
} else if (status >= 300 && status < 400) {
return "Redirect";
} else if (status >= 400 && status < 500) {
return "Client Error";
} else if (status >= 500) {
return "Server Error";
} else {
return "Unknown";
}
}
/**
* 判断是否为安全的请求头(不包含敏感信息)
*/
private boolean isSafeHeader(String headerName) {
String lowerName = headerName.toLowerCase();
return !lowerName.contains("authorization") &&
!lowerName.contains("cookie") &&
!lowerName.contains("password") &&
!lowerName.contains("token") &&
!lowerName.contains("secret");
}
}