feat: 增强日志系统和请求追踪功能
- 新增CorrelationFilter过滤器,为每个请求生成唯一跟踪ID - 增强logback配置,支持关联ID、租户信息和用户ID的结构化日志 - 新增RequestLoggingInterceptor,记录详细的HTTP请求响应信息
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package com.starry.common.config;
|
||||
|
||||
import com.starry.common.interceptor.RequestLoggingInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 日志记录配置类
|
||||
* 配置请求日志拦截器
|
||||
*
|
||||
* 注意:CorrelationFilter已移至play-admin模块,通过@Component自动注册
|
||||
*
|
||||
* @author Claude
|
||||
*/
|
||||
@Configuration
|
||||
public class LoggingConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private RequestLoggingInterceptor requestLoggingInterceptor;
|
||||
|
||||
/**
|
||||
* 添加请求日志拦截器
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(requestLoggingInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
"/static/**",
|
||||
"/webjars/**",
|
||||
"/swagger-resources/**",
|
||||
"/v2/api-docs/**",
|
||||
"/swagger-ui.html/**",
|
||||
"/doc.html/**",
|
||||
"/error",
|
||||
"/favicon.ico"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user