logo

Android网络请求日志封装与高效接口调用实践指南

作者:rousong2025.09.15 11:48浏览量:0

简介:本文详细讲解Android开发中如何通过日志封装优化接口调用流程,提供可复用的代码方案和实用建议,帮助开发者提升调试效率和代码质量。

一、接口调用日志封装的必要性

在Android开发中,网络请求是连接客户端与服务端的核心环节。然而,实际开发中常面临以下问题:

  1. 调试困难:请求参数、响应数据、异常信息分散在不同位置,难以快速定位问题
  2. 性能监控缺失:无法直观统计接口耗时、成功率等关键指标
  3. 重复代码:每个接口都需要单独编写日志记录逻辑,违反DRY原则
  4. 安全风险:敏感信息可能通过日志泄露

通过系统化的日志封装,可以:

  • 统一管理请求/响应日志
  • 自动记录关键指标(耗时、状态码)
  • 提供灵活的日志级别控制
  • 集成错误重试机制
  • 支持日志脱敏处理

二、核心封装方案实现

1. 基础日志工具类

  1. object NetworkLogger {
  2. private const val TAG = "NetworkRequest"
  3. // 日志级别枚举
  4. enum class LogLevel { DEBUG, INFO, WARNING, ERROR }
  5. // 配置日志开关和级别
  6. var isLoggingEnabled = true
  7. var currentLogLevel = LogLevel.DEBUG
  8. fun log(
  9. level: LogLevel,
  10. tag: String = TAG,
  11. message: String,
  12. throwable: Throwable? = null
  13. ) {
  14. if (!isLoggingEnabled || level.ordinal < currentLogLevel.ordinal) return
  15. val logMessage = "[$tag] ${level.name}: $message"
  16. when (level) {
  17. LogLevel.DEBUG -> Log.d(tag, logMessage, throwable)
  18. LogLevel.INFO -> Log.i(tag, logMessage, throwable)
  19. LogLevel.WARNING -> Log.w(tag, logMessage, throwable)
  20. LogLevel.ERROR -> Log.e(tag, logMessage, throwable)
  21. }
  22. }
  23. // 敏感信息脱敏处理
  24. fun maskSensitiveData(input: String): String {
  25. return input.replace(Regex("(\"token\":\")[^\"]*"), "\"token\":\"***\"")
  26. .replace(Regex("(\"password\":\")[^\"]*"), "\"password\":\"***\"")
  27. }
  28. }

2. 接口调用拦截器实现

基于OkHttp的拦截器实现完整请求日志记录:

  1. class LoggingInterceptor : Interceptor {
  2. override fun intercept(chain: Interceptor.Chain): Response {
  3. val request = chain.request()
  4. val startTime = System.nanoTime()
  5. // 记录请求信息
  6. logRequest(request)
  7. try {
  8. val response = chain.proceed(request)
  9. val endTime = System.nanoTime()
  10. val durationMs = (endTime - startTime) / 1_000_000
  11. // 记录响应信息
  12. logResponse(response, durationMs)
  13. return response
  14. } catch (e: IOException) {
  15. val endTime = System.nanoTime()
  16. val durationMs = (endTime - startTime) / 1_000_000
  17. NetworkLogger.log(
  18. level = NetworkLogger.LogLevel.ERROR,
  19. message = "Request failed in $durationMs ms",
  20. throwable = e
  21. )
  22. throw e
  23. }
  24. }
  25. private fun logRequest(request: Request) {
  26. val requestBody = request.body
  27. val requestLog = StringBuilder().apply {
  28. append("\n--> ${request.method} ${request.url}")
  29. append("\nHeaders: ${request.headers}")
  30. requestBody?.let {
  31. val buffer = Buffer()
  32. it.writeTo(buffer)
  33. append("\nBody: ${NetworkLogger.maskSensitiveData(buffer.readUtf8())}")
  34. }
  35. }
  36. NetworkLogger.log(NetworkLogger.LogLevel.DEBUG, message = requestLog.toString())
  37. }
  38. private fun logResponse(response: Response, durationMs: Long) {
  39. val responseBody = response.body
  40. val responseLog = StringBuilder().apply {
  41. append("\n<-- ${response.code} ${response.message} ($durationMs ms)")
  42. append("\nHeaders: ${response.headers}")
  43. responseBody?.let {
  44. val source = it.source()
  45. source.request(Long.MAX_VALUE)
  46. val buffer = source.buffer
  47. append("\nBody: ${NetworkLogger.maskSensitiveData(buffer.clone().readUtf8())}")
  48. }
  49. }
  50. NetworkLogger.log(NetworkLogger.LogLevel.DEBUG, message = responseLog.toString())
  51. }
  52. }

3. Retrofit集成方案

  1. object RetrofitClient {
  2. private const val BASE_URL = "https://api.example.com/"
  3. fun create(): Retrofit {
  4. return Retrofit.Builder()
  5. .baseUrl(BASE_URL)
  6. .client(provideOkHttpClient())
  7. .addConverterFactory(GsonConverterFactory.create())
  8. .build()
  9. }
  10. private fun provideOkHttpClient(): OkHttpClient {
  11. val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
  12. level = HttpLoggingInterceptor.Level.BODY // 仅用于示例,实际应使用自定义拦截器
  13. }
  14. return OkHttpClient.Builder()
  15. .addInterceptor(LoggingInterceptor()) // 使用我们的自定义拦截器
  16. .addInterceptor(httpLoggingInterceptor) // 可选:保留原生日志作为补充
  17. .connectTimeout(30, TimeUnit.SECONDS)
  18. .readTimeout(30, TimeUnit.SECONDS)
  19. .build()
  20. }
  21. }

三、高级功能扩展

1. 日志分级存储策略

  1. object LogStorageManager {
  2. private const val MAX_LOG_FILES = 5
  3. private const val MAX_FILE_SIZE = 1024 * 1024 // 1MB
  4. fun saveLogToFile(log: String, level: NetworkLogger.LogLevel) {
  5. val logDir = File(context.getExternalFilesDir(null), "network_logs")
  6. if (!logDir.exists()) logDir.mkdirs()
  7. val logFile = File(logDir, "${level.name}_${System.currentTimeMillis()}.log")
  8. // 轮转策略实现
  9. if (logFile.exists() && logFile.length() > MAX_FILE_SIZE) {
  10. rotateLogFiles(logDir, level)
  11. }
  12. FileOutputStream(logFile, true).bufferedWriter().use {
  13. it.write("$log\n")
  14. }
  15. }
  16. private fun rotateLogFiles(logDir: File, level: NetworkLogger.LogLevel) {
  17. // 实现日志文件轮转逻辑
  18. }
  19. }

2. 接口调用监控面板

  1. data class ApiMetric(
  2. val apiName: String,
  3. val successCount: Int,
  4. val failureCount: Int,
  5. val totalDuration: Long,
  6. val lastCallTime: Long
  7. )
  8. object ApiMonitor {
  9. private val metrics = mutableMapOf<String, ApiMetric>()
  10. fun recordApiCall(
  11. apiName: String,
  12. isSuccess: Boolean,
  13. durationMs: Long
  14. ) {
  15. val currentMetric = metrics.getOrPut(apiName) {
  16. ApiMetric(apiName, 0, 0, 0, 0)
  17. }
  18. metrics[apiName] = currentMetric.copy(
  19. successCount = if (isSuccess) currentMetric.successCount + 1 else currentMetric.successCount,
  20. failureCount = if (!isSuccess) currentMetric.failureCount + 1 else currentMetric.failureCount,
  21. totalDuration = currentMetric.totalDuration + durationMs,
  22. lastCallTime = System.currentTimeMillis()
  23. )
  24. }
  25. fun getMetricsReport(): String {
  26. return metrics.entries.joinToString("\n") { (apiName, metric) ->
  27. val avgDuration = if (metric.successCount > 0)
  28. metric.totalDuration / metric.successCount else 0
  29. "$apiName: Success=${metric.successCount}, " +
  30. "Fail=${metric.failureCount}, " +
  31. "AvgTime=${avgDuration}ms"
  32. }
  33. }
  34. }

四、最佳实践建议

  1. 生产环境配置

    • 发布版本关闭DEBUG级别日志
    • 实现日志上传机制(需用户授权)
    • 敏感信息必须脱敏处理
  2. 性能优化

    • 对大响应体进行采样记录
    • 异步写入日志文件
    • 实现日志分级存储
  3. 错误处理

    • 统一捕获网络异常
    • 实现自动重试机制
    • 提供优雅的降级方案
  4. 测试建议

    • 编写单元测试验证日志内容
    • 使用MockWebServer进行接口测试
    • 监控日志文件大小增长

五、完整调用示例

  1. interface ApiService {
  2. @GET("user/{id}")
  3. suspend fun getUser(@Path("id") userId: String): Response<User>
  4. }
  5. class UserRepository(private val apiService: ApiService) {
  6. suspend fun fetchUserDetails(userId: String): User? {
  7. return try {
  8. val response = apiService.getUser(userId)
  9. if (response.isSuccessful) {
  10. response.body()?.also {
  11. ApiMonitor.recordApiCall("getUser", true, 0) // 实际应计算耗时
  12. }
  13. } else {
  14. ApiMonitor.recordApiCall("getUser", false, 0)
  15. throw ApiException("Request failed: ${response.code()}")
  16. }
  17. } catch (e: Exception) {
  18. ApiMonitor.recordApiCall("getUser", false, 0)
  19. NetworkLogger.log(
  20. level = NetworkLogger.LogLevel.ERROR,
  21. message = "Failed to fetch user details",
  22. throwable = e
  23. )
  24. throw e
  25. }
  26. }
  27. }
  28. // 使用示例
  29. val retrofit = RetrofitClient.create()
  30. val apiService = retrofit.create(ApiService::class.java)
  31. val repository = UserRepository(apiService)
  32. viewModelScope.launch {
  33. try {
  34. val user = repository.fetchUserDetails("123")
  35. // 处理用户数据
  36. } catch (e: ApiException) {
  37. // 处理错误
  38. }
  39. }

通过上述封装方案,开发者可以获得:

  1. 统一的日志格式和存储
  2. 自动化的性能监控
  3. 简化的错误处理流程
  4. 可配置的日志级别控制
  5. 敏感信息保护机制

这种实现方式既保持了代码的简洁性,又提供了足够的灵活性,可以根据项目需求进行定制扩展。建议在实际项目中结合代码审查和自动化测试,确保日志系统的稳定性和安全性。

相关文章推荐

发表评论