Kotlin+OkHttp:从零开始打造你的专属网络请求日志拦截器
KotlinOkHttp从零开始打造你的专属网络请求日志拦截器在移动应用开发中网络请求的调试和监控是每个开发者都绕不开的课题。OkHttp作为Android平台上最受欢迎的HTTP客户端之一其强大的拦截器机制为我们提供了灵活的网络请求处理能力。本文将带你深入探索如何利用Kotlin语言特性从零开始构建一个功能完善、高度可定制的网络请求日志拦截器。1. OkHttp拦截器基础与日志需求分析OkHttp的拦截器机制是其架构设计的精髓所在。拦截器按照添加顺序形成一个链式结构每个拦截器都能对请求和响应进行处理。官方提供的HttpLoggingInterceptor虽然功能完善但在实际项目中往往存在以下痛点日志输出格式固定难以与项目现有日志系统集成敏感信息如认证头、请求体中的隐私数据缺乏自动过滤性能指标如请求耗时需要额外计算多线程环境下日志顺序混乱难以追踪完整请求链路针对这些问题我们可以基于Interceptor接口打造一个更符合项目需求的日志拦截器。以下是自定义拦截器需要实现的核心功能矩阵功能模块官方拦截器自定义拦截器优势日志格式固定格式完全可定制敏感信息处理无可配置过滤规则性能监控无内置耗时统计线程安全部分支持完整请求链路追踪扩展性有限支持插件化扩展2. 构建基础日志拦截器框架让我们从创建一个基本的拦截器骨架开始。这个版本已经包含了请求/响应日志记录和简单耗时统计class CustomLoggingInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request chain.request() val startTime System.nanoTime() // 记录请求日志 logRequest(request) val response chain.proceed(request) // 记录响应日志 val duration (System.nanoTime() - startTime) / 1e6 logResponse(response, duration) return response } private fun logRequest(request: Request) { val logMessage buildString { appendln(→→→→→ Request Start →→→→→) appendln(Method: ${request.method}) appendln(URL: ${request.url}) appendln(Headers: ${request.headers}) } println(logMessage) } private fun logResponse(response: Response, duration: Double) { val logMessage buildString { appendln(←←←←← Response Start ←←←←←) appendln(Status: ${response.code} ${response.message}) appendln(Duration: ${duration}ms) appendln(Headers: ${response.headers}) } println(logMessage) } }这个基础版本已经解决了官方拦截器的部分问题清晰的请求/响应分隔标识自动计算请求耗时更结构化的日志输出提示在实际项目中建议将println替换为项目使用的日志框架如Timber或Logger以获得更好的日志控制能力。3. 增强拦截器功能3.1 请求/响应体处理OkHttp的请求和响应体只能被读取一次我们需要特别处理private fun logRequest(request: Request) { val requestBody request.body val bodyContent requestBody?.let { val buffer Buffer() it.writeTo(buffer) buffer.readUtf8() } ?: Empty body val logMessage →→→→→ Request Start →→→→→ Method: ${request.method} URL: ${request.url} Headers: ${request.headers} Body: $bodyContent →→→→→ Request End →→→→→ .trimIndent() println(logMessage) } private fun logResponse(response: Response, duration: Double): Response { val responseBody response.body val bodyContent responseBody?.let { val source it.source() source.request(Long.MAX_VALUE) val buffer source.buffer buffer.clone().readUtf8() } ?: Empty body val logMessage ←←←←← Response Start ←←←←← Status: ${response.code} ${response.message} Duration: ${duration}ms Headers: ${response.headers} Body: $bodyContent ←←←←← Response End ←←←←← .trimIndent() println(logMessage) // 重建response以保证后续处理能读取body return response.newBuilder() .body(responseBody?.let { it.contentType()?.let { type - bodyContent.toResponseBody(type) } }) .build() }3.2 敏感信息过滤对于包含敏感数据的请求我们可以添加过滤规则class CustomLoggingInterceptor( private val sensitiveHeaders: SetString setOf(Authorization, Cookie), private val sensitiveDataPatterns: ListRegex listOf(Regex((password|token)[^])) ) : Interceptor { private fun sanitizeHeaders(headers: Headers): String { return headers.map { (name, value) - $name: ${if (sensitiveHeaders.contains(name)) ***** else value} }.joinToString(, ) } private fun sanitizeBody(content: String): String { var sanitized content sensitiveDataPatterns.forEach { pattern - sanitized sanitized.replace(pattern, $1*****) } return sanitized } }4. 高级功能实现4.1 请求链路追踪在多线程环境下为每个请求分配唯一ID有助于追踪private val requestId AtomicLong(0) override fun intercept(chain: Interceptor.Chain): Response { val id requestId.incrementAndGet() val threadName Thread.currentThread().name try { MDC.put(requestId, id.toString()) // 用于日志框架上下文 val request chain.request() .newBuilder() .header(X-Request-ID, id.toString()) .build() logRequest(id, threadName, request) // ...其余处理逻辑 } finally { MDC.remove(requestId) } }4.2 性能监控与统计我们可以扩展拦截器来收集性能指标class CustomLoggingInterceptor : Interceptor { private val stats mutableMapOfString, RequestStats() data class RequestStats( var count: Int 0, var totalTime: Double 0.0, var errors: Int 0 ) { val averageTime get() totalTime / count.coerceAtLeast(1) } override fun intercept(chain: Interceptor.Chain): Response { val request chain.request() val host request.url.host val startTime System.nanoTime() var success true return try { chain.proceed(request).also { if (!it.isSuccessful) success false } } catch (e: Exception) { success false throw e } finally { val duration (System.nanoTime() - startTime) / 1e6 synchronized(stats) { val stat stats.getOrPut(host) { RequestStats() } stat.count stat.totalTime duration if (!success) stat.errors } } } fun getStats(): MapString, RequestStats stats.toMap() }5. 拦截器配置与最佳实践5.1 灵活的日志级别控制enum class LogLevel { NONE, BASIC, // 仅记录方法/URL/状态码 HEADERS, // 包含头信息 BODY // 包含完整请求/响应体 } class CustomLoggingInterceptor( private var level: LogLevel LogLevel.BODY ) : Interceptor { fun setLevel(newLevel: LogLevel) { this.level newLevel } private fun shouldLogHeaders() level LogLevel.HEADERS private fun shouldLogBody() level LogLevel.BODY // 在logRequest/logResponse中根据级别控制输出 }5.2 与OkHttpClient集成的最佳实践val client OkHttpClient.Builder() .addInterceptor(CustomLoggingInterceptor().apply { setLevel(if (BuildConfig.DEBUG) LogLevel.BODY else LogLevel.BASIC) }) .addNetworkInterceptor(/* 其他拦截器 */) .connectTimeout(15, TimeUnit.SECONDS) .build()注意addInterceptor和addNetworkInterceptor的区别addInterceptor应用拦截器不处理重定向和重试addNetworkInterceptor网络拦截器能观察到完整的网络交互5.3 性能优化技巧在release构建中禁用body日志以减少内存分配使用缓冲机制避免频繁的IO操作对大型响应体实现分块日志输出考虑使用后台线程处理日志写入private val logExecutor Executors.newSingleThreadExecutor() private fun logAsync(message: String) { logExecutor.execute { logger.info(message) } }通过本文的探索我们不仅构建了一个功能强大的日志拦截器更深入理解了OkHttp拦截器机制的工作原理。这种自定义解决方案相比官方实现具有显著优势完全掌控日志格式和内容敏感信息自动过滤保障安全内置性能监控能力高度可扩展的架构设计在实际项目中使用时建议根据具体需求逐步添加功能避免过度设计。一个好的日志拦截器应该像优秀的助手一样既提供充分的信息支持又不会成为性能瓶颈或安全漏洞。