在java中记录网络请求日志最常见且有效的方式是使用servlet过滤器(filter)拦截http请求,或利用http客户端库的拦截器(interceptor)机制捕获请求和响应数据。1. 服务器端可通过实现自定义的servlet filter,如结合contentcachingrequestwrapper和contentcachingresponsewrapper包装请求和响应对象,从而多次读取内容并记录url、方法、头信息、请求体、响应体及耗时等信息,在过滤器链执行完毕后调用copybodytoresponse确保响应体写回客户端。2. 客户端可通过http库提供的拦截器机制实现,如okhttp的interceptor接口,在intercept方法中获取request和response对象,记录发送请求的url、头信息、请求体及接收响应的时间、状态、响应体等,并通过addinterceptor方法将其添加到okhttpclient实例中。记录网络请求日志的价值体现在问题诊断、安全审计、性能分析、业务行为分析和问题重现等方面,但需注意内存消耗、性能开销和敏感数据泄露等问题,应通过限制请求体响应体大小、实施数据脱敏策略、使用异步日志、优化日志级别和配置日志轮转等方式来保障安全和性能。

在Java中记录网络请求日志,特别是URL访问信息,最常见且有效的方式是在服务器端使用Servlet过滤器(Filter)来拦截HTTP请求,或者在客户端HTTP请求发送前/后利用各HTTP客户端库提供的拦截器(Interceptor)机制。这两种方法都能让你在请求到达业务逻辑之前或之后,以及响应发送之前,捕获到丰富的网络交互数据。

记录网络请求日志通常涉及两个主要场景:Web应用服务器端和客户端发起HTTP请求。
1. 服务器端Web应用(如Spring Boot/Servlet应用)
立即学习“Java免费学习笔记(深入)”;

在Web应用中,一个通用的做法是实现一个自定义的Servlet Filter。这个过滤器会在每个HTTP请求到达Servlet之前被调用,并且在响应返回客户端之前再次被调用。这提供了绝佳的切入点来记录请求的URL、方法、头信息、参数乃至请求体,以及响应的状态码、头信息和响应体。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
@Component
public class RequestLoggingFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 包装请求和响应,以便多次读取内容
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpServletRequest);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpServletResponse);
long startTime = System.currentTimeMillis();
try {
chain.doFilter(requestWrapper, responseWrapper);
} finally {
long duration = System.currentTimeMillis() - startTime;
logRequestAndResponse(requestWrapper, responseWrapper, duration);
responseWrapper.copyBodyToResponse(); // 确保响应体被写入到原始响应中
}
}
private void logRequestAndResponse(ContentCachingRequestWrapper requestWrapper,
ContentCachingResponseWrapper responseWrapper,
long duration) throws UnsupportedEncodingException {
String requestUri = requestWrapper.getRequestURI();
String method = requestWrapper.getMethod();
String queryString = requestWrapper.getQueryString();
String fullUrl = requestUri + (queryString != null ? "?" + queryString : "");
StringBuilder requestLog = new StringBuilder();
requestLog.append("\n--- HTTP Request ---\n");
requestLog.append("URL: ").append(method).append(" ").append(fullUrl).append("\n");
requestLog.append("Client IP: ").append(requestWrapper.getRemoteAddr()).append("\n");
requestLog.append("Headers:\n");
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
requestLog.append(" ").append(headerName).append(": ").append(requestWrapper.getHeader(headerName)).append("\n");
}
byte[] requestBody = requestWrapper.getContentAsByteArray();
if (requestBody.length > 0) {
String bodyContent = new String(requestBody, requestWrapper.getCharacterEncoding());
requestLog.append("Body: ").append(bodyContent.length() > 2000 ? bodyContent.substring(0, 2000) + "..." : bodyContent).append("\n"); // 限制长度
}
log.info(requestLog.toString());
StringBuilder responseLog = new StringBuilder();
responseLog.append("\n--- HTTP Response ---\n");
responseLog.append("URL: ").append(method).append(" ").append(fullUrl).append("\n");
responseLog.append("Status: ").append(responseWrapper.getStatus()).append("\n");
responseLog.append("Duration: ").append(duration).append(" ms\n");
responseLog.append("Headers:\n");
responseWrapper.getHeaderNames().forEach(headerName ->
responseLog.append(" ").append(headerName).append(": ").append(responseWrapper.getHeader(headerName)).append("\n")
);
byte[] responseBody = responseWrapper.getContentAsByteArray();
if (responseBody.length > 0) {
String bodyContent = new String(responseBody, responseWrapper.getCharacterEncoding());
responseLog.append("Body: ").append(bodyContent.length() > 2000 ? bodyContent.substring(0, 2000) + "..." : bodyContent).append("\n"); // 限制长度
}
log.info(responseLog.toString());
}
}2. 客户端HTTP请求(如使用OkHttp、RestTemplate等)

当你的Java应用作为客户端发起HTTP请求时,大多数现代HTTP客户端库都提供了拦截器机制。你可以在请求发送前修改请求,或在接收响应后处理响应。
以OkHttp为例:
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.MediaType;
import okio.Buffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
public class OkHttpLoggingInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(OkHttpLoggingInterceptor.class);
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
StringBuilder requestLog = new StringBuilder();
requestLog.append("\n--- OkHttp Request ---\n");
requestLog.append(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
if (request.body() != null) {
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
Charset charset = StandardCharsets.UTF_8;
MediaType contentType = request.body().contentType();
if (contentType != null) {
charset = contentType.charset(StandardCharsets.UTF_8);
}
requestLog.append("Request Body: ").append(buffer.clone().readString(charset)).append("\n");
}
log.info(requestLog.toString());
Response response = chain.proceed(request);
long t2 = System.nanoTime();
StringBuilder responseLog = new StringBuilder();
responseLog.append("\n--- OkHttp Response ---\n");
responseLog.append(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
ResponseBody responseBody = response.peekBody(1024 * 1024); // 限制读取大小
if (responseBody != null) {
Charset charset = StandardCharsets.UTF_8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(StandardCharsets.UTF_8);
}
responseLog.append("Response Body: ").append(responseBody.string()).append("\n");
}
log.info(responseLog.toString());
return response;
}
}然后将这个拦截器添加到你的OkHttpClient中:
// OkHttpClient client = new OkHttpClient.Builder() // .addInterceptor(new OkHttpLoggingInterceptor()) // .build();
在我看来,网络请求日志就像是应用程序的“黑匣子”,它记录了系统与外部世界(或其他内部服务)交互的每一个细节。没有它,很多问题排查都将是盲人摸象。我个人经历过太多次,在没有详细请求日志的情况下,团队成员为了一个偶现的接口问题彻夜难眠。
记录这些日志的核心价值体现在几个方面:
所以,这不仅仅是一个“锦上添花”的功能,它简直是现代复杂分布式系统不可或缺的基础设施。
捕获请求体和响应体是网络请求日志的难点之一,因为标准的HttpServletRequest和HttpServletResponse的输入流和输出流通常只能读取一次。如果你直接尝试在过滤器中读取,那么后续的Servlet或Controller就无法再次读取了。
解决方案通常是使用内容缓存包装器(Content Caching Wrappers)。
HttpServletRequest,你可以使用jakarta.servlet.http.HttpServletRequestWrapper(或javax版本)的子类,或者Spring框架提供的ContentCachingRequestWrapper。这些包装器会在第一次读取请求体时将其内容缓存起来,这样你就可以多次读取。在上面的示例代码中,ContentCachingRequestWrapper就是做这个事情的。它把输入流的内容读到内存中的一个字节数组里,之后你可以通过getContentAsByteArray()方法获取。HttpServletResponse,可以使用jakarta.servlet.http.HttpServletResponseWrapper的子类或Spring的ContentCachingResponseWrapper。这个包装器会把所有写入响应输出流的内容先缓存起来,而不是直接发送给客户端。在过滤器链执行完毕后,你需要手动调用responseWrapper.copyBodyToResponse()方法,将缓存的内容复制回原始的响应输出流,这样客户端才能收到响应。需要注意的点:
因此,在捕获请求体和响应体时,通常会结合使用长度限制(如示例中bodyContent.length() > 2000 ? ...),以及敏感数据脱敏或加密的策略。
记录网络请求日志虽然价值巨大,但若处理不当,也可能带来一系列安全和性能上的隐患。我亲眼见过因为日志策略不当导致生产环境崩溃,或者敏感数据泄露的案例,这简直是噩梦。
安全问题:
***)、哈希值或预定义的占位符。例如,password字段可以记录为password=******。性能问题:
AsyncAppender或Log4j2的AsyncLogger)。异步日志将日志事件写入一个队列,然后由独立的线程批量写入磁盘,从而减少对主业务线程的影响。INFO或WARN,只在需要详细调试时临时切换到DEBUG。总之,一套完善的网络请求日志策略,需要仔细权衡其带来的价值与潜在的风险和开销。这绝不是一个“一劳永逸”的配置,而是需要根据业务需求、系统负载和安全要求持续迭代和优化的过程。
以上就是如何用Java记录网络请求日志 Java记录URL访问信息示例的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号