OkHttp Logging Interceptor 实战指南:HTTP 请求响应日志的完整配置与敏感信息脱敏 📅 发布时间:2026/9/18 21:10:06 👁 浏览次数: OkHttp Logging Interceptor 实战指南HTTP 请求响应日志的完整配置与敏感信息脱敏【免费下载链接】okhttpA meticulous HTTP client for the JVM, Android, and GraalVM.项目地址: https://gitcode.com/gh_mirrors/okh/okhttp本文围绕 OkHttp 官方日志模块okhttp-logging-interceptor本仓库 okhttp-logging-interceptor 所讲解的主题展开讲解如何为 JVM、Android 应用接入 HTTP 请求/响应日志、四种日志级别NONE/BASIC/HEADERS/BODY的差异与输出格式、自定义日志输出目标以及在生产环境下保护Authorization、Cookie等敏感信息的脱敏方案。读完本文你将能够把该拦截器正确接入 OkHttpClient、按需切换日志级别并结合源码与测试理解其内部实现细节。一、模块定位一个可插拔的 HTTP 日志拦截器okhttp-logging-interceptor是 OkHttp 官方提供的一个 拦截器Interceptor用于记录 HTTP 请求与响应的完整数据。它既可以作为**应用拦截器application interceptor通过addInterceptor()注册也可以作为网络拦截器network interceptor**通过addNetworkInterceptor()注册源码注释见 HttpLoggingInterceptor.kt 的类注释。模块核心类为okhttp3.logging.HttpLoggingInterceptor它实现了Interceptor接口在intercept()方法中完成请求行、请求头、请求体、响应行、响应头、响应体的日志输出。需要说明的是官方对该类生成的日志格式不承诺跨版本稳定类注释明确指出The format of the logs created by this class should not be considered stable and may change slightly between releases因此如果业务上依赖固定日志格式应自行编写拦截器。二、快速开始引入依赖并启用日志1. 添加依赖在 Gradle 项目中引入模块版本以本仓库当前发布为准implementation(com.squareup.okhttp3:logging-interceptor:5.5.0)2. 最小可用示例创建拦截器实例、设置日志级别、注册到OkHttpClient.BuilderHttpLoggingInterceptor logging new HttpLoggingInterceptor(); logging.setLevel(Level.BASIC); OkHttpClient client new OkHttpClient.Builder() .addInterceptor(logging) .build();默认情况下日志级别为Level.NONE即不输出任何日志这一点在测试levelGetter()中有明确断言The default is NONE见 HttpLoggingInterceptorTest.kt。由于默认不产生日志直接构建一个HttpLoggingInterceptor实例接入客户端不会带来任何额外输出。3. 随时动态切换级别日志级别可以在运行时任意时刻通过setLevel()修改源码见 HttpLoggingInterceptor.ktlogging.setLevel(Level.BODY); // 调高细节 logging.setLevel(Level.NONE); // 关闭日志setLevel()返回this因此支持链式调用HttpLoggingInterceptor logging new HttpLoggingInterceptor().setLevel(Level.BASIC);测试setLevelShouldReturnSameInstanceOfInterceptor()验证了每次调用返回的都是同一个拦截器实例。底层实现上日志级别保存在一个Volatile var level字段中Kotlin 属性Java 侧通过setLevel()/getLevel()访问保证跨线程可见intercept()每次执行时会读取当前级别并即时生效。三、四种日志级别详解从请求行到完整 BodyHttpLoggingInterceptor.Level是模块内置的枚举包含四个成员源码对每个级别的输出格式都给出了精确示例见 HttpLoggingInterceptor.kt 中Level枚举的注释级别日志内容典型输出NONE无任何日志空BASIC请求/响应行 body 大小-- POST /greeting http/1.1 (3-byte body)/-- 200 OK (22ms, 6-byte body)HEADERS请求/响应行 全部头部-- POST /greeting http/1.1、Host: example.com、-- END POSTBODY请求/响应行 头部 完整 Body在HEADERS基础上追加请求体与响应体文本1. BASIC最小信息量仅记录请求与响应行。请求行为-- 方法 URL若请求有 body 则追加(N-byte body)响应行为-- 状态码 状态描述 URL (耗时ms, body大小 body)。测试 HttpLoggingInterceptorTest.kt 中的basicGet()、basicPost()断言了如下输出-- GET http://localhost:PORT/ -- 200 OK http://localhost:PORT/ (18ms, 0-byte body)当响应体为 chunked未知长度时body 大小显示为unknown-length body对应basicChunkedResponseBody()测试。2. HEADERS请求/响应行 头部在 BASIC 基础上追加所有请求头与响应头并以-- END 方法、-- END HTTP收尾。测试headersGet()、headersPost()展示了网络拦截器视角下Host、Connection、Accept-Encoding: gzip、User-Agent: okhttp/...等头部都会被记录。值得注意的细节作为应用拦截器时请求头通常不包含Content-Type与Content-Length它们由 OkHttp 在传输层自动补充。源码中对此做了特殊处理——若请求体存在而头部缺失这两个字段拦截器会主动从RequestBody.contentType()与contentLength()推导并补记见intercept()中if (requestBody ! null)分支并注释说明Request body headers are only present when installed as a network interceptor。3. BODY完整请求/响应体在 HEADERS 基础上额外输出请求体与响应体内容-- POST /greeting http/1.1 Host: example.com Content-Type: plain/text Content-Length: 3 Hi? -- END POST -- 200 OK (22ms) Content-Type: plain/text Content-Length: 6 Hello! -- END HTTPBODY级别也是源码中logBody判定为true的唯一级别val logBody level Level.BODY此时logHeaders也同时为真。四、自定义日志输出目标Logger 接口默认情况下日志输出到当前平台的标准位置JVM 为System.out等平台实现。如需输出到自定义位置例如接入 Timber 等日志框架、写入文件、上传日志中心可向构造函数传入Logger实例HttpLoggingInterceptor logging new HttpLoggingInterceptor(new Logger() { Override public void log(String message) { Timber.tag(OkHttp).d(message); } });Logger在源码中被定义为fun interface函数式接口只包含一个log(String)抽象方法因此在 Kotlin 中可直接用 lambda 实现val logging HttpLoggingInterceptor { message - Timber.tag(OkHttp).d(message) }Logger接口还提供一个DEFAULT常量其内部实现DefaultLogger委托给Platform.get().log(message)即由 OkHttp 平台抽象层选择当前运行平台的默认日志输出见 HttpLoggingInterceptor.kt 中Logger的伴生对象。五、敏感信息保护脱敏与使用警告原文档对此模块有一个重要警告当使用HEADERS或BODY级别时生成的日志可能泄露敏感信息例如Authorization、Cookie请求头以及请求/响应体的内容。这些数据只应在受控方式或非生产环境下记录。针对头部泄露可调用redactHeader()指定需要脱敏的头部名称logging.redactHeader(Authorization); logging.redactHeader(Cookie);调用后匹配的头部值在日志中会被替换为占位符██。测试headersAreRedacted()见 HttpLoggingInterceptorTest.kt验证了该行为SeNsItIvE: ██ Not-Sensitive: Value该测试同时证明脱敏匹配是大小写不敏感的——请求与响应均使用了SeNsItIvE/sEnSiTiVe等混合大小写写法依旧命中。这得益于源码实现中使用TreeSet(String.CASE_INSENSITIVE_ORDER)维护待脱敏头部集合见redactHeader()实现且该集合是Volatile字段支持运行时动态增删。除头部外模块还提供redactQueryParams(vararg name: String)对URL 查询参数进行脱敏该方法自 OkHttp 4.12 起提供见 CHANGELOG.md。例如logging.redactQueryParams(user, password);当 URL 包含这些参数时其值在日志中同样被替换为██。源码中的redactUrl()实现会重建 URL将命中的查询参数值替换为██未命中的参数保持原样。测试sensitiveQueryParamsAreRedacted()使用 URLhttp://localhost:PORT/api/login?usertest_userauthenticationbasicpasswordconfidential_password验证了user与passWord混合大小写两个参数值被脱敏、其余参数保留。六、源码级原理intercept() 的完整日志流程深入 HttpLoggingInterceptor.kt 的intercept()方法可以看清日志的完整生成链路级别判断读取当前level若为NONE则直接chain.proceed(request)零开销放行。请求行输出-- 方法 redactUrl(URL)若存在连接则追加协议名如http/1.1仅网络拦截器可见非 HEADERS/BODY 级别时若请求有 body追加(N-byte body)。请求头补充缺失的Content-Type/Content-Length如前文所述随后遍历头部调用logHeader()命中脱敏集合的值替换为██。请求体仅BODY级别且请求体可读时输出。源码对多种不可输出场景做了优雅降级未知 Content-Encoding既非identity也非gzip-- END 方法 (encoded body omitted)duplex双向流请求体(duplex request body omitted)one-shot一次性消费请求体(one-shot body omitted)gzip 编码先用GzipSource解压后再记录并在结尾标注原始 gzip 字节数测试bodyRequestGzipEncoded()覆盖非 UTF-8 二进制内容通过isProbablyUtf8(16L)探测二进制 body 只报大小不输出内容。执行请求并计时chain.proceed(request)前后以System.nanoTime()计时若请求抛异常输出-- HTTP FAILED: 异常. URL (耗时ms)后重新抛出保证异常信息与耗时同时可观测。响应行输出-- 状态码 描述 redactUrl(URL) (耗时ms[, body大小 body])响应体长度未知时显示unknown-length body对应 chunked 响应。响应头与响应体同请求侧对称处理。此外针对响应还有两个特殊分支text/event-streamSSE流式响应只输出-- END HTTP (streaming)避免阻塞式读取破坏流式语义bodyIsStreaming()判断UnreadableResponseBody输出(unreadable body)二进制响应体输出(totalMs ms, binary N-byte body omitted)。这些分支全部有对应测试覆盖bodyResponseGzipEncoded()、bodyResponseUnknownEncoded()、bodyResponseIsStreaming()、bodyResponseIsUnreadable()、duplexRequestsAreNotLogged()、oneShotRequestsAreNotLogged()等共同构成了该模块约 1194 行的测试套件是理解各边界行为的权威依据。七、应用拦截器与网络拦截器的差异原文档示例将日志拦截器注册为应用拦截器addInterceptor但正如类注释所述它同样适用于网络拦截器addNetworkInterceptor。结合 docs/features/interceptors.md 中的说明两者的日志表现有明显区别应用拦截器只看到一次请求/响应重定向被内部处理后只记录最终响应不包含 OkHttp 自动添加的头部如Host、Connection、Accept-Encoding。网络拦截器在重定向、连接复用等场景下可能看到多次请求/响应且能看到包括Host、User-Agent、Accept-Encoding在内的全部实际传输头部测试headersGet()中网络日志断言了这些头部的存在。因此排查连接层问题协议、DNS、重定向链路时网络拦截器信息更全日常业务调试用应用拦截器输出更简洁。二者甚至可以同时注册测试setUp()中即同时注册了应用与网络两个日志拦截器实例日志互不干扰。八、延伸LoggingEventListener 事件日志同一模块还提供了LoggingEventListener源码见 LoggingEventListener.kt它不是拦截器而是 OkHttp 的EventListener实现用于记录一次调用全生命周期的事件callStart、dispatcherQueueStart、proxySelectStart/End、dnsStart/End、connectStart/End、secureConnectStart/End、connectionAcquired/Released、requestHeadersStart/End、requestBodyStart/End、responseHeadersStart/End、responseBodyStart/End、cacheHit/Miss、callEnd/callFailed等每条日志带相对调用起始的毫秒时间戳前缀[N ms]。它通过eventListenerFactory接入客户端OkHttpClient client new OkHttpClient.Builder() .eventListenerFactory(new LoggingEventListener.Factory()) .build();Factory同样接受自定义Logger构造参数。与HttpLoggingInterceptor关注 HTTP 报文不同LoggingEventListener更偏重性能剖析各阶段耗时与连接/缓存行为的观测适合需要深入排查网络栈内部行为的场景。九、实践建议与注意事项生产环境默认NONE或BASIC结合环境动态切换——调试时提升到BODY正式环境回归NONE或BASIC并配合redactHeader()/redactQueryParams()兜底。敏感字段先脱敏再上线Authorization、Cookie、Set-Cookie以及 URL 中的token、password等参数应纳入脱敏名单脱敏匹配不区分大小写可放心使用。日志格式不稳定源码类注释明确日志格式可能在版本间微调若下游依赖日志解析请自行实现稳定格式的拦截器。关注二进制与流式场景拦截器对二进制 body、gzip body、SSE 流、duplex/one-shot 请求体均有内置降级策略不会破坏请求语义可放心在BODY级别下使用。结合 MockWebServer 验证行为本仓库的 HttpLoggingInterceptorTest.kt 使用 MockWebServer 对全部级别与边界分支做了断言是理解各配置项实际输出的最佳参考资料。总而言之okhttp-logging-interceptor以极小的接入成本为 OkHttp 应用提供了从请求行到完整报文的多级日志能力配合头部分与查询参数双路脱敏机制能够在调试便利性与生产安全性之间取得良好平衡。结合本仓库的源码与测试研读可以准确预测并掌控它在各种网络场景下的日志行为。【免费下载链接】okhttpA meticulous HTTP client for the JVM, Android, and GraalVM.项目地址: https://gitcode.com/gh_mirrors/okh/okhttp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考