Android网络请求日志封装与高效接口调用实践指南
2025.09.15 11:48浏览量:0简介:本文详细讲解Android开发中如何通过日志封装优化接口调用流程,提供可复用的代码方案和实用建议,帮助开发者提升调试效率和代码质量。
一、接口调用日志封装的必要性
在Android开发中,网络请求是连接客户端与服务端的核心环节。然而,实际开发中常面临以下问题:
- 调试困难:请求参数、响应数据、异常信息分散在不同位置,难以快速定位问题
- 性能监控缺失:无法直观统计接口耗时、成功率等关键指标
- 重复代码:每个接口都需要单独编写日志记录逻辑,违反DRY原则
- 安全风险:敏感信息可能通过日志泄露
通过系统化的日志封装,可以:
- 统一管理请求/响应日志
- 自动记录关键指标(耗时、状态码)
- 提供灵活的日志级别控制
- 集成错误重试机制
- 支持日志脱敏处理
二、核心封装方案实现
1. 基础日志工具类
object NetworkLogger {
private const val TAG = "NetworkRequest"
// 日志级别枚举
enum class LogLevel { DEBUG, INFO, WARNING, ERROR }
// 配置日志开关和级别
var isLoggingEnabled = true
var currentLogLevel = LogLevel.DEBUG
fun log(
level: LogLevel,
tag: String = TAG,
message: String,
throwable: Throwable? = null
) {
if (!isLoggingEnabled || level.ordinal < currentLogLevel.ordinal) return
val logMessage = "[$tag] ${level.name}: $message"
when (level) {
LogLevel.DEBUG -> Log.d(tag, logMessage, throwable)
LogLevel.INFO -> Log.i(tag, logMessage, throwable)
LogLevel.WARNING -> Log.w(tag, logMessage, throwable)
LogLevel.ERROR -> Log.e(tag, logMessage, throwable)
}
}
// 敏感信息脱敏处理
fun maskSensitiveData(input: String): String {
return input.replace(Regex("(\"token\":\")[^\"]*"), "\"token\":\"***\"")
.replace(Regex("(\"password\":\")[^\"]*"), "\"password\":\"***\"")
}
}
2. 接口调用拦截器实现
基于OkHttp的拦截器实现完整请求日志记录:
class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.nanoTime()
// 记录请求信息
logRequest(request)
try {
val response = chain.proceed(request)
val endTime = System.nanoTime()
val durationMs = (endTime - startTime) / 1_000_000
// 记录响应信息
logResponse(response, durationMs)
return response
} catch (e: IOException) {
val endTime = System.nanoTime()
val durationMs = (endTime - startTime) / 1_000_000
NetworkLogger.log(
level = NetworkLogger.LogLevel.ERROR,
message = "Request failed in $durationMs ms",
throwable = e
)
throw e
}
}
private fun logRequest(request: Request) {
val requestBody = request.body
val requestLog = StringBuilder().apply {
append("\n--> ${request.method} ${request.url}")
append("\nHeaders: ${request.headers}")
requestBody?.let {
val buffer = Buffer()
it.writeTo(buffer)
append("\nBody: ${NetworkLogger.maskSensitiveData(buffer.readUtf8())}")
}
}
NetworkLogger.log(NetworkLogger.LogLevel.DEBUG, message = requestLog.toString())
}
private fun logResponse(response: Response, durationMs: Long) {
val responseBody = response.body
val responseLog = StringBuilder().apply {
append("\n<-- ${response.code} ${response.message} ($durationMs ms)")
append("\nHeaders: ${response.headers}")
responseBody?.let {
val source = it.source()
source.request(Long.MAX_VALUE)
val buffer = source.buffer
append("\nBody: ${NetworkLogger.maskSensitiveData(buffer.clone().readUtf8())}")
}
}
NetworkLogger.log(NetworkLogger.LogLevel.DEBUG, message = responseLog.toString())
}
}
3. Retrofit集成方案
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
fun create(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(provideOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun provideOkHttpClient(): OkHttpClient {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY // 仅用于示例,实际应使用自定义拦截器
}
return OkHttpClient.Builder()
.addInterceptor(LoggingInterceptor()) // 使用我们的自定义拦截器
.addInterceptor(httpLoggingInterceptor) // 可选:保留原生日志作为补充
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
}
三、高级功能扩展
1. 日志分级存储策略
object LogStorageManager {
private const val MAX_LOG_FILES = 5
private const val MAX_FILE_SIZE = 1024 * 1024 // 1MB
fun saveLogToFile(log: String, level: NetworkLogger.LogLevel) {
val logDir = File(context.getExternalFilesDir(null), "network_logs")
if (!logDir.exists()) logDir.mkdirs()
val logFile = File(logDir, "${level.name}_${System.currentTimeMillis()}.log")
// 轮转策略实现
if (logFile.exists() && logFile.length() > MAX_FILE_SIZE) {
rotateLogFiles(logDir, level)
}
FileOutputStream(logFile, true).bufferedWriter().use {
it.write("$log\n")
}
}
private fun rotateLogFiles(logDir: File, level: NetworkLogger.LogLevel) {
// 实现日志文件轮转逻辑
}
}
2. 接口调用监控面板
data class ApiMetric(
val apiName: String,
val successCount: Int,
val failureCount: Int,
val totalDuration: Long,
val lastCallTime: Long
)
object ApiMonitor {
private val metrics = mutableMapOf<String, ApiMetric>()
fun recordApiCall(
apiName: String,
isSuccess: Boolean,
durationMs: Long
) {
val currentMetric = metrics.getOrPut(apiName) {
ApiMetric(apiName, 0, 0, 0, 0)
}
metrics[apiName] = currentMetric.copy(
successCount = if (isSuccess) currentMetric.successCount + 1 else currentMetric.successCount,
failureCount = if (!isSuccess) currentMetric.failureCount + 1 else currentMetric.failureCount,
totalDuration = currentMetric.totalDuration + durationMs,
lastCallTime = System.currentTimeMillis()
)
}
fun getMetricsReport(): String {
return metrics.entries.joinToString("\n") { (apiName, metric) ->
val avgDuration = if (metric.successCount > 0)
metric.totalDuration / metric.successCount else 0
"$apiName: Success=${metric.successCount}, " +
"Fail=${metric.failureCount}, " +
"AvgTime=${avgDuration}ms"
}
}
}
四、最佳实践建议
生产环境配置:
- 发布版本关闭DEBUG级别日志
- 实现日志上传机制(需用户授权)
- 敏感信息必须脱敏处理
性能优化:
- 对大响应体进行采样记录
- 异步写入日志文件
- 实现日志分级存储
错误处理:
- 统一捕获网络异常
- 实现自动重试机制
- 提供优雅的降级方案
测试建议:
- 编写单元测试验证日志内容
- 使用MockWebServer进行接口测试
- 监控日志文件大小增长
五、完整调用示例
interface ApiService {
@GET("user/{id}")
suspend fun getUser(@Path("id") userId: String): Response<User>
}
class UserRepository(private val apiService: ApiService) {
suspend fun fetchUserDetails(userId: String): User? {
return try {
val response = apiService.getUser(userId)
if (response.isSuccessful) {
response.body()?.also {
ApiMonitor.recordApiCall("getUser", true, 0) // 实际应计算耗时
}
} else {
ApiMonitor.recordApiCall("getUser", false, 0)
throw ApiException("Request failed: ${response.code()}")
}
} catch (e: Exception) {
ApiMonitor.recordApiCall("getUser", false, 0)
NetworkLogger.log(
level = NetworkLogger.LogLevel.ERROR,
message = "Failed to fetch user details",
throwable = e
)
throw e
}
}
}
// 使用示例
val retrofit = RetrofitClient.create()
val apiService = retrofit.create(ApiService::class.java)
val repository = UserRepository(apiService)
viewModelScope.launch {
try {
val user = repository.fetchUserDetails("123")
// 处理用户数据
} catch (e: ApiException) {
// 处理错误
}
}
通过上述封装方案,开发者可以获得:
- 统一的日志格式和存储
- 自动化的性能监控
- 简化的错误处理流程
- 可配置的日志级别控制
- 敏感信息保护机制
这种实现方式既保持了代码的简洁性,又提供了足够的灵活性,可以根据项目需求进行定制扩展。建议在实际项目中结合代码审查和自动化测试,确保日志系统的稳定性和安全性。
发表评论
登录后可评论,请前往 登录 或 注册