不改动接口参数和返回,打印接口ip/url/时间,之后加到拦截器或者AOP中
注意事项
-
IP :
request.getRemoteAddr()
获取的可能是代理服务器IP而非真实客户端IP,真实IP需要从X-Forwarded-For
等header中获取。 -
线程安全 :
RequestContextHolder
是基于ThreadLocal实现的,在异步方法中可能无法获取到request -
日志框架:建议使用SLF4J等日志框架替代System.out.println
-
异常处理:确保在catch块中也记录结束时间,否则会丢失异常请求的日志
方法1:在入参中添加HttpServletRequest
java
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@RestController
@RequestMapping("/api")
public class YourController {
@GetMapping("/your-endpoint")
public ResponseEntity<?> yourMethod(HttpServletRequest request) {
// 获取请求信息
String ip = request.getRemoteAddr();
String methodName = "yourMethod";
Date startTime = new Date();
System.out.println("请求开始 - IP: " + ip + ", 方法: " + methodName + ", 时间: " + startTime);
try {
// 你的业务逻辑代码
// ...
Date endTime = new Date();
long duration = endTime.getTime() - startTime.getTime();
System.out.println("请求结束 - 方法: " + methodName + ", 时间: " + endTime + ", 耗时: " + duration + "ms");
return ResponseEntity.ok("Success");
} catch (Exception e) {
Date endTime = new Date();
System.out.println("请求异常 - 方法: " + methodName + ", 时间: " + endTime);
throw e;
}
}
}
方法2:使用RequestContextHolder获取Request
java
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@RestController
@RequestMapping("/api")
public class YourController {
@GetMapping("/your-endpoint")
public ResponseEntity<?> yourMethod() {
// 获取当前请求对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = request.getRemoteAddr();
String methodName = "yourMethod";
Date startTime = new Date();
System.out.println("请求开始 - IP: " + ip + ", 方法: " + methodName + ", 时间: " + startTime);
try {
// 你的业务逻辑代码
// ...
Date endTime = new Date();
long duration = endTime.getTime() - startTime.getTime();
System.out.println("请求结束 - 方法: " + methodName + ", 时间: " + endTime + ", 耗时: " + duration + "ms");
return ResponseEntity.ok("Success");
} catch (Exception e) {
Date endTime = new Date();
System.out.println("请求异常 - 方法: " + methodName + ", 时间: " + endTime);
throw e;
}
}
}
...
private String getClientIP(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip))
{ ip = request.getRemoteAddr();}
return ip.split(",")[0];
// 处理多级代理的情况 }
}
String apiName = request.getRequestURI();
方法3:方法2封装成工具类
日志工具类代码复用
java
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class RequestLogUtil {
public static void logRequestStart(String methodName) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = request.getRemoteAddr();
Date startTime = new Date();
System.out.println("请求开始 - IP: " + ip + ", 方法: " + methodName + ", 时间: " + startTime);
}
public static void logRequestEnd(String methodName) {
Date endTime = new Date();
System.out.println("请求结束 - 方法: " + methodName + ", 时间: " + endTime);
}
}
然后在控制器中使用:
java
@GetMapping("/your-endpoint")
public ResponseEntity<?> yourMethod() {
RequestLogUtil.logRequestStart("yourMethod");
try {
// 业务逻辑
// ...
RequestLogUtil.logRequestEnd("yourMethod");
return ResponseEntity.ok("Success");
} catch (Exception e) {
RequestLogUtil.logRequestEnd("yourMethod");
throw e;
}
}
后期
方式一:拦截器(Interceptor)
1. 创建日志拦截器类
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.Instant;
public class RequestLogInterceptor implements HandlerInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(RequestLogInterceptor.class);
private static final ThreadLocal<Instant> startTimeThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
startTimeThreadLocal.set(Instant.now());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
Instant startTime = startTimeThreadLocal.get();
Instant endTime = Instant.now();
long duration = Duration.between(startTime, endTime).toMillis();
String clientIP = getClientIP(request);
String apiName = request.getRequestURI();
LOG.info("接口请求日志 - IP: {}, 接口: {}, 开始时间: {}, 结束时间: {}, 耗时: {}ms",
clientIP, apiName, startTime, endTime, duration);
startTimeThreadLocal.remove();
}
private String getClientIP(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
2. 注册拦截器
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestLogInterceptor())
.addPathPatterns("/api/**"); // 拦截所有/api路径的请求
}
}
方式二:AOP
1. 添加AOP依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 创建切面类
java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
@Aspect
@Component
public class RequestLogAspect {
private static final Logger LOG = LoggerFactory.getLogger(RequestLogAspect.class);
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
Instant startTime = Instant.now();
String clientIP = getClientIP(request);
String apiName = request.getRequestURI();
try {
Object result = joinPoint.proceed(); // 执行目标方法
return result;
} finally {
Instant endTime = Instant.now();
long duration = endTime.toEpochMilli() - startTime.toEpochMilli();
LOG.info("接口请求日志 - IP: {}, 接口: {}, 开始时间: {}, 结束时间: {}, 耗时: {}ms",
clientIP, apiName, startTime, endTime, duration);
}
}
private String getClientIP(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip.split(",")[0]; // 处理多级代理的情况
}
}
说明
-
获取客户端IP
- 优先从
X-Forwarded-For
头获取真实IP(适用于通过代理的情况)。 - 直接使用
request.getRemoteAddr()
作为备用方案。
- 优先从
-
接口名称
- 使用
request.getRequestURI()
获取请求路径(如/api/user
)。 - 若需更精细的接口名(如方法名),可在AOP中通过
joinPoint.getSignature().getName()
获取。
- 使用
-
耗时计算
- 使用
Instant
记录时间戳,确保高精度。 - 在拦截器的
afterCompletion
或AOP的finally
块中计算总耗时。
- 使用
-
线程安全
- 拦截器中通过
ThreadLocal
存储开始时间,避免并发问题。
- 拦截器中通过
日志输出示例
2023-10-05 14:20:00 INFO RequestLogAspect - 接口请求日志 - IP: 192.168.1.100, 接口: /api/user, 开始时间: 2023-10-05T14:20:00.123Z, 结束时间: 2023-10-05T14:20:00.456Z, 耗时: 333ms
扩展建议
-
异步日志 :若日志频繁,可使用异步Appender(如Logback的
AsyncAppender
)提升性能。 -
JSON格式 :输出为JSON便于ELK采集:
javaLOG.info("{}", new ObjectMapper().writeValueAsString(logMap));
-
自定义注解 :通过注解标记需要记录日志的接口:
java@Around("@annotation(com.example.YourLoggableAnnotation)")