Spring MVC 拦截器实现日志记录
拦截器是记录请求日志的理想位置,可统一记录所有Controller请求的详细信息。
基本日志拦截器
Java
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
private static final String START_TIME = "startTime";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute(START_TIME, System.currentTimeMillis());
log.info("请求开始: {} {} from {}",
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME);
long duration = System.currentTimeMillis() - startTime;
log.info("请求完成: {} {} 状态:{} 耗时:{}ms",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
request.removeAttribute(START_TIME);
}
}
详细日志记录
Java
@Slf4j
@Component
public class DetailedLogInterceptor implements HandlerInterceptor {
private static final String START_TIME = "startTime";
private static final String REQUEST_ID = "requestId";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String requestId = UUID.randomUUID().toString().substring(0, 8);
request.setAttribute(REQUEST_ID, requestId);
request.setAttribute(START_TIME, System.currentTimeMillis());
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("\n========== 请求信息 ==========\n");
logBuilder.append("RequestId: ").append(requestId).append("\n");
logBuilder.append("Method: ").append(request.getMethod()).append("\n");
logBuilder.append("URI: ").append(request.getRequestURI()).append("\n");
logBuilder.append("Query: ").append(request.getQueryString()).append("\n");
logBuilder.append("IP: ").append(request.getRemoteAddr()).append("\n");
logBuilder.append("User-Agent: ").append(request.getHeader("User-Agent")).append("\n");
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
logBuilder.append("Handler: ")
.append(handlerMethod.getBeanType().getSimpleName())
.append(".")
.append(handlerMethod.getMethod().getName())
.append("\n");
}
logBuilder.append("Headers:\n");
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
logBuilder.append(" ").append(name).append(": ")
.append(request.getHeader(name)).append("\n");
}
log.info(logBuilder.toString());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute(START_TIME);
String requestId = (String) request.getAttribute(REQUEST_ID);
long duration = System.currentTimeMillis() - startTime;
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("\n========== 响应信息 ==========\n");
logBuilder.append("RequestId: ").append(requestId).append("\n");
logBuilder.append("Status: ").append(response.getStatus()).append("\n");
logBuilder.append("Duration: ").append(duration).append("ms\n");
if (ex != null) {
logBuilder.append("Exception: ").append(ex.getClass().getName()).append("\n");
logBuilder.append("Message: ").append(ex.getMessage()).append("\n");
}
log.info(logBuilder.toString());
}
}
异常日志记录
Java
@Slf4j
@Component
public class ExceptionLogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long duration = System.currentTimeMillis()
- (Long) request.getAttribute("startTime");
if (ex != null) {
// 记录异常详情
log.error("请求异常: {} {}", request.getMethod(), request.getRequestURI());
log.error("异常类型: {}", ex.getClass().getName());
log.error("异常信息: {}", ex.getMessage());
log.error("处理耗时: {}ms", duration);
log.error("堆栈信息: ", ex);
} else {
log.info("请求成功: {} {} 状态:{} 耗时:{}ms",
request.getMethod(), request.getRequestURI(),
response.getStatus(), duration);
}
}
}
日志级别分级
Java
@Slf4j
@Component
public class LogLevelInterceptor implements HandlerInterceptor {
private static final long WARN_THRESHOLD = 1000; // 1秒
private static final long ERROR_THRESHOLD = 3000; // 3秒
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long duration = System.currentTimeMillis()
- (Long) request.getAttribute("startTime");
String method = request.getMethod();
String uri = request.getRequestURI();
int status = response.getStatus();
if (ex != null) {
log.error("[ERROR] {} {} - 异常: {}, 耗时: {}ms",
method, uri, ex.getMessage(), duration);
} else if (duration >= ERROR_THRESHOLD) {
log.error("[SLOW] {} {} - 耗时: {}ms, 状态: {}",
method, uri, duration, status);
} else if (duration >= WARN_THRESHOLD) {
log.warn("[WARN] {} {} - 耗时: {}ms, 状态: {}",
method, uri, duration, status);
} else {
log.info("[INFO] {} {} - 耗时: {}ms, 状态: {}",
method, uri, duration, status);
}
}
}
敏感信息过滤
Java
@Slf4j
@Component
public class SecureLogInterceptor implements HandlerInterceptor {
private static final Set<String> SENSITIVE_HEADERS = Set.of(
"Authorization", "Cookie", "X-Auth-Token", "Password"
);
private static final Set<String> SENSITIVE_PARAMS = Set.of(
"password", "token", "secret", "creditCard"
);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
String uri = filterSensitiveParams(request.getRequestURI(), request);
String queryString = filterQueryString(request.getQueryString());
log.info("请求开始: {} {}?{}", request.getMethod(), uri, queryString);
return true;
}
private String filterSensitiveParams(String uri, HttpServletRequest request) {
String result = uri;
for (String param : SENSITIVE_PARAMS) {
String value = request.getParameter(param);
if (value != null) {
result = result.replace(value, "***");
}
}
return result;
}
private String filterQueryString(String queryString) {
if (queryString == null) {
return "";
}
StringBuilder filtered = new StringBuilder();
String[] params = queryString.split("&");
for (String param : params) {
String[] kv = param.split("=");
if (kv.length == 2 && SENSITIVE_PARAMS.contains(kv[0])) {
filtered.append(kv[0]).append("=***&");
} else {
filtered.append(param).append("&");
}
}
return filtered.length() > 0
? filtered.substring(0, filtered.length() - 1)
: "";
}
}
日志入库存储
Java
@Slf4j
@Component
public class DatabaseLogInterceptor implements HandlerInterceptor {
@Autowired
private RequestLogService requestLogService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
RequestLog logEntity = new RequestLog();
logEntity.setRequestId(UUID.randomUUID().toString());
logEntity.setMethod(request.getMethod());
logEntity.setUri(request.getRequestURI());
logEntity.setIp(request.getRemoteAddr());
logEntity.setStartTime(LocalDateTime.now());
request.setAttribute("logEntity", logEntity);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
RequestLog logEntity = (RequestLog) request.getAttribute("logEntity");
if (logEntity == null) {
return;
}
logEntity.setEndTime(LocalDateTime.now());
logEntity.setStatus(response.getStatus());
logEntity.setDuration(
System.currentTimeMillis() - logEntity.getStartTime().toEpochSecond(ZoneOffset.UTC) * 1000
);
if (ex != null) {
logEntity.setException(ex.getClass().getName());
logEntity.setExceptionMessage(ex.getMessage());
}
// 异步保存到数据库
requestLogService.saveAsync(logEntity);
}
}
拦截器配置
Java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/static/**",
"/favicon.ico",
"/error"
)
.order(Integer.MIN_VALUE); // 最高优先级
}
}
日志拦截器order值设为最小,确保最先执行记录完整耗时。
要点总结
- preHandle记录请求开始时间和基本信息
- afterCompletion计算耗时并记录完成日志
- 根据耗时分级设置不同日志级别
- 过滤敏感信息避免泄露密码、Token等
- 生产环境可将日志异步存储到数据库
- 日志拦截器order值应最小以记录完整耗时
📝 发现内容有误?点击此处直接编辑