0

0

如何用Java记录网络请求日志 Java记录URL访问信息示例

爱谁谁

爱谁谁

发布时间:2025-07-20 16:49:01

|

931人浏览过

|

来源于php中文网

原创

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

如何用Java记录网络请求日志 Java记录URL访问信息示例

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

如何用Java记录网络请求日志 Java记录URL访问信息示例

解决方案

记录网络请求日志通常涉及两个主要场景:Web应用服务器端和客户端发起HTTP请求。

1. 服务器端Web应用(如Spring Boot/Servlet应用)

立即学习Java免费学习笔记(深入)”;

如何用Java记录网络请求日志 Java记录URL访问信息示例

在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 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记录网络请求日志 Java记录URL访问信息示例

当你的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();

为什么记录网络请求日志如此重要?

在我看来,网络请求日志就像是应用程序的“黑匣子”,它记录了系统与外部世界(或其他内部服务)交互的每一个细节。没有它,很多问题排查都将是盲人摸象。我个人经历过太多次,在没有详细请求日志的情况下,团队成员为了一个偶现的接口问题彻夜难眠。

科大讯飞-AI虚拟主播
科大讯飞-AI虚拟主播

科大讯飞推出的移动互联网智能交互平台,为开发者免费提供:涵盖语音能力增强型SDK,一站式人机智能语音交互解决方案,专业全面的移动应用分析;

下载

记录这些日志的核心价值体现在几个方面:

  • 问题诊断与排查: 这是最直接的用途。当用户抱怨某个功能不工作,或者服务间调用出现异常时,请求日志能立即告诉你具体是哪个请求出了问题,参数是什么,响应是什么,状态码是多少。这比单纯的错误堆栈信息要直观得多。
  • 安全审计与合规: 对于金融、医疗等行业,记录谁在何时访问了什么资源,以及访问结果如何,是满足合规性要求(如GDPR、HIPAA)的关键。它能帮助我们追踪潜在的恶意行为或数据泄露。
  • 性能分析: 通过记录请求的耗时,可以发现哪些接口是性能瓶颈,哪些服务响应缓慢。这对于优化系统性能至关重要。
  • 业务行为分析: 虽然这不是日志的主要目的,但通过分析请求的URL和参数,有时也能洞察用户行为模式,为产品改进提供数据支持。
  • 重现问题: 详细的请求和响应日志能够帮助开发人员在开发或测试环境中精准地重现生产环境中的问题。

所以,这不仅仅是一个“锦上添花”的功能,它简直是现代复杂分布式系统不可或缺的基础设施。

如何在日志中有效捕获请求体和响应体?

捕获请求体和响应体是网络请求日志的难点之一,因为标准的HttpServletRequestHttpServletResponse的输入流和输出流通常只能读取一次。如果你直接尝试在过滤器中读取,那么后续的Servlet或Controller就无法再次读取了。

解决方案通常是使用内容缓存包装器(Content Caching Wrappers)

  • 请求体: 对于HttpServletRequest,你可以使用jakarta.servlet.http.HttpServletRequestWrapper(或javax版本)的子类,或者Spring框架提供的ContentCachingRequestWrapper。这些包装器会在第一次读取请求体时将其内容缓存起来,这样你就可以多次读取。在上面的示例代码中,ContentCachingRequestWrapper就是做这个事情的。它把输入流的内容读到内存中的一个字节数组里,之后你可以通过getContentAsByteArray()方法获取。
  • 响应体: 类似地,对于HttpServletResponse,可以使用jakarta.servlet.http.HttpServletResponseWrapper的子类或Spring的ContentCachingResponseWrapper。这个包装器会把所有写入响应输出流的内容先缓存起来,而不是直接发送给客户端。在过滤器链执行完毕后,你需要手动调用responseWrapper.copyBodyToResponse()方法,将缓存的内容复制回原始的响应输出流,这样客户端才能收到响应。

需要注意的点:

  1. 内存消耗: 缓存请求体和响应体意味着这些数据会暂时存储在内存中。如果请求体或响应体非常大(例如上传大文件或下载大文件),这可能会导致显著的内存消耗,甚至OOM(内存溢出)错误。
  2. 性能开销: 复制和缓存数据本身也需要CPU和I/O开销。
  3. 敏感数据: 捕获请求体和响应体时,务必警惕敏感信息泄露。密码、个人身份信息(PII)、银行卡号等绝不能以明文形式记录到日志中。

因此,在捕获请求体和响应体时,通常会结合使用长度限制(如示例中bodyContent.length() > 2000 ? ...),以及敏感数据脱敏或加密的策略。

记录日志时需要注意哪些安全和性能问题?

记录网络请求日志虽然价值巨大,但若处理不当,也可能带来一系列安全和性能上的隐患。我亲眼见过因为日志策略不当导致生产环境崩溃,或者敏感数据泄露的案例,这简直是噩梦。

安全问题:

  • 敏感数据泄露: 这是最严重的问题。请求参数、请求头、响应体中可能包含用户的密码、身份证号、银行卡信息、API密钥、Session ID、Authorization Token等敏感数据。如果这些信息被明文记录到日志文件中,一旦日志文件被未授权访问,将造成灾难性的数据泄露。
    • 对策: 必须实施严格的数据脱敏(Data Masking/Redaction)策略。识别出所有可能包含敏感信息的字段,在写入日志前将其替换为星号(***)、哈希值或预定义的占位符。例如,password字段可以记录为password=******
  • 日志文件访问控制: 日志文件本身就是敏感资源。确保日志文件存储在受保护的目录中,并且只有授权的用户或服务才能访问。遵循最小权限原则,限制对日志目录的读写权限。
  • 日志篡改: 恶意攻击者可能会尝试修改日志文件以掩盖其行踪。虽然这不是Java日志库直接解决的问题,但应考虑使用日志审计工具或将日志发送到安全的、不可篡改的集中式日志系统(如ELK Stack配合适当的安全配置)。

性能问题:

  • I/O开销: 频繁地写入磁盘是日志记录最主要的性能瓶颈。每个请求都生成大量日志,会产生巨大的磁盘I/O压力,尤其是在高并发场景下。
    • 对策: 使用异步日志(如Logback的AsyncAppender或Log4j2的AsyncLogger)。异步日志将日志事件写入一个队列,然后由独立的线程批量写入磁盘,从而减少对主业务线程的影响。
  • CPU开销: 日志内容的格式化、字符串拼接、敏感数据脱敏等操作都会消耗CPU资源。
    • 对策: 优化日志格式,避免不必要的复杂计算。对于高并发路径,考虑只记录关键信息,或根据配置动态调整日志详细程度。
  • 内存消耗: 前面提到的内容缓存包装器会临时占用内存。如果请求/响应体过大,或者并发量极高,可能导致内存溢出。
    • 对策: 严格限制捕获的请求体和响应体大小,对于超出的部分进行截断。对于非常大的文件上传/下载,可以考虑不记录其完整内容,只记录文件大小和元数据。
  • 日志量过大: 即使性能不是问题,过大的日志量也会迅速填满磁盘空间,并且使得日志分析变得困难。
    • 对策:
      • 日志级别管理: 在生产环境中,将日志级别设置为INFOWARN,只在需要详细调试时临时切换到DEBUG
      • 采样(Sampling): 对于非关键或高频接口,可以考虑只记录一部分请求的日志,例如每100个请求只记录一个。
      • 按需开启: 提供配置开关,可以在不重启应用的情况下动态开启或关闭详细的请求日志。
      • 日志轮转与归档: 配置日志框架进行日志文件按大小或时间自动轮转,并定期归档或删除旧的日志文件。

总之,一套完善的网络请求日志策略,需要仔细权衡其带来的价值与潜在的风险和开销。这绝不是一个“一劳永逸”的配置,而是需要根据业务需求、系统负载和安全要求持续迭代和优化的过程。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

112

2025.08.06

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

27

2026.01.26

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

135

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

390

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

70

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

34

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

135

2025.12.24

什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

328

2023.08.11

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.7万人学习

Java 教程
Java 教程

共578课时 | 52.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号