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:
Jorge Martin Espinosa 2025-11-18 15:18:27 +01:00 committed by GitHub
parent 740e486cd0
commit bf0274074d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 72 additions and 40 deletions

View file

@ -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
}
}

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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.