Enable logging OkHttp traffic based on the current log level (#5750)
* Use `LogLevel` to decide whether to log the HTTP requests and responses Added `DynamicHttpLoggingInterceptor` for this. * Code cleanup. * Use Timber.d * OutOfMemoryError should not be caught. They are considered unrecoverable. * Improve code in DefaultBugReporter. --------- Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
committed by
GitHub
parent
5a5c0b16ca
commit
396edbce3d
@@ -45,5 +45,5 @@ interface BugReporter {
|
||||
/**
|
||||
* Save the logcat.
|
||||
*/
|
||||
fun saveLogCat()
|
||||
fun saveLogCat(): File?
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ class DefaultBugReporter(
|
||||
private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*")
|
||||
private var currentTracingLogLevel: String? = null
|
||||
|
||||
private val logCatErrFile: File
|
||||
get() = File(logDirectory(), LOG_CAT_FILENAME)
|
||||
private val baseLogDirectory = File(context.cacheDir, LOG_DIRECTORY_NAME)
|
||||
private var currentLogDirectory: File = baseLogDirectory
|
||||
|
||||
@@ -160,10 +158,14 @@ class DefaultBugReporter(
|
||||
}
|
||||
if (withCrashLogs || withDevicesLogs) {
|
||||
saveLogCat()
|
||||
val gzippedLogcat = compressFile(logCatErrFile)
|
||||
if (gzippedLogcat != null) {
|
||||
gzippedFiles.add(0, gzippedLogcat)
|
||||
}
|
||||
?.let { logCatFile ->
|
||||
compressFile(logCatFile).also {
|
||||
logCatFile.safeDelete()
|
||||
}
|
||||
}
|
||||
?.let { gzippedLogcat ->
|
||||
gzippedFiles.add(0, gzippedLogcat)
|
||||
}
|
||||
}
|
||||
val sessionData = sessionStore.getLatestSession()
|
||||
val numberOfAccounts = sessionStore.numberOfSessions()
|
||||
@@ -387,7 +389,8 @@ class DefaultBugReporter(
|
||||
onException = { Timber.e(it, "## getLogFiles() failed") }
|
||||
) {
|
||||
val logDirectory = logDirectory()
|
||||
logDirectory.listFiles()?.toList()
|
||||
logDirectory.listFiles()
|
||||
?.filter { it.isFile && !it.name.endsWith(LOG_CAT_FILENAME) }
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
@@ -400,19 +403,19 @@ class DefaultBugReporter(
|
||||
*
|
||||
* @return the file if the operation succeeds
|
||||
*/
|
||||
override fun saveLogCat() {
|
||||
val file = logCatErrFile
|
||||
override fun saveLogCat(): File? {
|
||||
val file = File(baseLogDirectory, LOG_CAT_FILENAME)
|
||||
if (file.exists()) {
|
||||
file.safeDelete()
|
||||
}
|
||||
try {
|
||||
return try {
|
||||
file.writer().use {
|
||||
getLogCatError(it)
|
||||
getLogCatContent(it)
|
||||
}
|
||||
} catch (error: OutOfMemoryError) {
|
||||
Timber.e(error, "## saveLogCat() : fail to write logcat OOM")
|
||||
file
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## saveLogCat() : fail to write logcat")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,15 +424,10 @@ class DefaultBugReporter(
|
||||
*
|
||||
* @param streamWriter the stream writer
|
||||
*/
|
||||
private fun getLogCatError(streamWriter: OutputStreamWriter) {
|
||||
val logcatProcess: Process
|
||||
|
||||
try {
|
||||
logcatProcess = Runtime.getRuntime().exec(logcatCommandDebug)
|
||||
} catch (e1: IOException) {
|
||||
return
|
||||
}
|
||||
|
||||
private fun getLogCatContent(streamWriter: OutputStreamWriter) {
|
||||
val logcatProcess = tryOrNull {
|
||||
Runtime.getRuntime().exec(logcatCommandDebug)
|
||||
} ?: return
|
||||
try {
|
||||
val separator = System.lineSeparator()
|
||||
logcatProcess.inputStream
|
||||
@@ -440,7 +438,7 @@ class DefaultBugReporter(
|
||||
streamWriter.append(separator)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "getLog fails")
|
||||
Timber.e(e, "getLogCatContent fails")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class FakeBugReporter(val mode: Mode = Mode.Success) : BugReporter {
|
||||
// No op
|
||||
}
|
||||
|
||||
override fun saveLogCat() {
|
||||
// No op
|
||||
override fun saveLogCat(): File? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,5 @@ fun compressFile(file: File): File? {
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## compressFile() failed")
|
||||
null
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## compressFile() failed")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(platform(libs.network.okhttp.bom))
|
||||
implementation(libs.network.okhttp)
|
||||
implementation(libs.network.okhttp.logging)
|
||||
|
||||
@@ -13,7 +13,7 @@ import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.network.interceptors.DynamicHttpLoggingInterceptor
|
||||
import io.element.android.libraries.network.interceptors.FormattedJsonHttpLogger
|
||||
import io.element.android.libraries.network.interceptors.UserAgentInterceptor
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -26,21 +26,20 @@ object NetworkModule {
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesOkHttpClient(
|
||||
buildMeta: BuildMeta,
|
||||
userAgentInterceptor: UserAgentInterceptor,
|
||||
dynamicHttpLoggingInterceptor: DynamicHttpLoggingInterceptor,
|
||||
): OkHttpClient = OkHttpClient.Builder().apply {
|
||||
connectTimeout(30, TimeUnit.SECONDS)
|
||||
readTimeout(60, TimeUnit.SECONDS)
|
||||
writeTimeout(60, TimeUnit.SECONDS)
|
||||
addInterceptor(userAgentInterceptor)
|
||||
if (buildMeta.isDebuggable) addInterceptor(providesHttpLoggingInterceptor())
|
||||
addInterceptor(dynamicHttpLoggingInterceptor)
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
val loggingLevel = HttpLoggingInterceptor.Level.BODY
|
||||
val logger = FormattedJsonHttpLogger(loggingLevel)
|
||||
val interceptor = HttpLoggingInterceptor(logger)
|
||||
interceptor.level = loggingLevel
|
||||
return interceptor
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
val logger = FormattedJsonHttpLogger(HttpLoggingInterceptor.Level.BODY)
|
||||
return HttpLoggingInterceptor(logger)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.network.interceptors
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okhttp3.logging.HttpLoggingInterceptor.Level
|
||||
|
||||
/**
|
||||
* HTTP logging interceptor that decides whether to display the HTTP logs or not based on the current log level.
|
||||
*/
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class DynamicHttpLoggingInterceptor(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val loggingInterceptor: HttpLoggingInterceptor,
|
||||
) : Interceptor by loggingInterceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
// This is called in a separate thread, so calling `runBlocking` here should be fine, it should be also instant after the value is cached
|
||||
val logLevel = runBlocking { appPreferencesStore.getTracingLogLevelFlow().first() }
|
||||
loggingInterceptor.level = if (logLevel >= LogLevel.DEBUG) Level.BODY else Level.NONE
|
||||
return loggingInterceptor.intercept(chain)
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ internal class FormattedJsonHttpLogger(
|
||||
*/
|
||||
@Synchronized
|
||||
override fun log(message: String) {
|
||||
Timber.v(message.ellipsize(200_000))
|
||||
Timber.d(message.ellipsize(200_000))
|
||||
|
||||
// Try to log formatted Json only if there is a chance that [message] contains Json.
|
||||
// It can be only the case if we log the bodies of Http requests.
|
||||
|
||||
Reference in New Issue
Block a user