From 3bed816c91c79a086c21f91bd3a61546799f70ff Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Mon, 1 Apr 2024 12:07:32 +0100 Subject: [PATCH] Add a configurable AndroidLogging API (#7844) --- okhttp-android/api/okhttp-android.api | 7 ++ okhttp-android/build.gradle.kts | 1 + .../kotlin/okhttp3/android/AndroidLogging.kt | 36 +++++++ .../okhttp3/android/AndroidLoggingTest.kt | 102 ++++++++++++++++++ .../api/logging-interceptor.api | 8 ++ .../okhttp3/logging/HttpLoggingInterceptor.kt | 2 + .../okhttp3/logging/LoggingEventListener.kt | 2 + 7 files changed, 158 insertions(+) create mode 100644 okhttp-android/src/main/kotlin/okhttp3/android/AndroidLogging.kt create mode 100644 okhttp-android/src/test/kotlin/okhttp3/android/AndroidLoggingTest.kt diff --git a/okhttp-android/api/okhttp-android.api b/okhttp-android/api/okhttp-android.api index 8dacb26b8..a3884fa7c 100644 --- a/okhttp-android/api/okhttp-android.api +++ b/okhttp-android/api/okhttp-android.api @@ -10,3 +10,10 @@ public final class okhttp3/android/AndroidAsyncDns$Companion { public final fun getIPv6 ()Lokhttp3/android/AndroidAsyncDns; } +public final class okhttp3/android/AndroidLoggingKt { + public static final fun androidLogging (Lokhttp3/logging/HttpLoggingInterceptor$Companion;ILjava/lang/String;)Lokhttp3/logging/HttpLoggingInterceptor; + public static final fun androidLogging (Lokhttp3/logging/LoggingEventListener$Companion;ILjava/lang/String;)Lokhttp3/logging/LoggingEventListener$Factory; + public static synthetic fun androidLogging$default (Lokhttp3/logging/HttpLoggingInterceptor$Companion;ILjava/lang/String;ILjava/lang/Object;)Lokhttp3/logging/HttpLoggingInterceptor; + public static synthetic fun androidLogging$default (Lokhttp3/logging/LoggingEventListener$Companion;ILjava/lang/String;ILjava/lang/Object;)Lokhttp3/logging/LoggingEventListener$Factory; +} + diff --git a/okhttp-android/build.gradle.kts b/okhttp-android/build.gradle.kts index fe6093341..59a27124e 100644 --- a/okhttp-android/build.gradle.kts +++ b/okhttp-android/build.gradle.kts @@ -48,6 +48,7 @@ android { dependencies { api(libs.squareup.okio) api(projects.okhttp) + api(projects.loggingInterceptor) compileOnly(libs.androidx.annotation) compileOnly(libs.findbugs.jsr305) debugImplementation(libs.androidx.annotation) diff --git a/okhttp-android/src/main/kotlin/okhttp3/android/AndroidLogging.kt b/okhttp-android/src/main/kotlin/okhttp3/android/AndroidLogging.kt new file mode 100644 index 000000000..f39cc43cc --- /dev/null +++ b/okhttp-android/src/main/kotlin/okhttp3/android/AndroidLogging.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 Block, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3.android + +import android.util.Log +import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.LoggingEventListener + +/** + * An OkHttp [LoggingEventListener], with android Log as the target. + */ +fun LoggingEventListener.Companion.androidLogging( + priority: Int = Log.INFO, + tag: String = "OkHttp", +) = LoggingEventListener.Factory { Log.println(priority, tag, it) } + +/** + * An OkHttp [HttpLoggingInterceptor], with android Log as the target. + */ +fun HttpLoggingInterceptor.Companion.androidLogging( + priority: Int = Log.INFO, + tag: String = "OkHttp", +) = HttpLoggingInterceptor { Log.println(priority, tag, it) } diff --git a/okhttp-android/src/test/kotlin/okhttp3/android/AndroidLoggingTest.kt b/okhttp-android/src/test/kotlin/okhttp3/android/AndroidLoggingTest.kt new file mode 100644 index 000000000..38b3223c0 --- /dev/null +++ b/okhttp-android/src/test/kotlin/okhttp3/android/AndroidLoggingTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package okhttp3.android + +import android.util.Log +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.containsOnly +import assertk.assertions.isNull +import java.net.UnknownHostException +import okhttp3.ConnectionSpec +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.LoggingEventListener +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.shadows.ShadowLog + +@RunWith(RobolectricTestRunner::class) +class AndroidLoggingTest { + val clientBuilder = + OkHttpClient.Builder() + .connectionSpecs(listOf(ConnectionSpec.CLEARTEXT)) + .dns { + throw UnknownHostException("shortcircuit") + } + + val request = Request("http://google.com/robots.txt".toHttpUrl()) + + @Test + fun testHttpLoggingInterceptor() { + val interceptor = + HttpLoggingInterceptor.androidLogging(tag = "testHttpLoggingInterceptor").apply { + level = HttpLoggingInterceptor.Level.BASIC + } + + val client = clientBuilder.addInterceptor(interceptor).build() + + try { + client.newCall(request).execute() + } catch (uhe: UnknownHostException) { + // expected + } + + val logs = ShadowLog.getLogsForTag("testHttpLoggingInterceptor") + assertThat(logs.map { it.type }).containsOnly(Log.INFO) + assertThat(logs.map { it.msg }).containsExactly( + "--> GET http://google.com/robots.txt", + "<-- HTTP FAILED: java.net.UnknownHostException: shortcircuit", + ) + // We should consider if these logs should retain Exceptions + assertThat(logs.last().throwable).isNull() + } + + @Test + fun testLoggingEventListener() { + val client = + clientBuilder.eventListenerFactory(LoggingEventListener.androidLogging(tag = "testLoggingEventListener")).build() + + try { + client.newCall(request).execute() + } catch (uhe: UnknownHostException) { + // expected + } + + val logs = ShadowLog.getLogsForTag("testLoggingEventListener") + assertThat(logs.map { it.type }).containsOnly(Log.INFO) + assertThat( + logs.map { + it.msg.replace( + "\\[\\d+ ms] ".toRegex(), + "", + ) + }, + ).containsExactly( + "callStart: Request{method=GET, url=http://google.com/robots.txt}", + "proxySelectStart: http://google.com/", + "proxySelectEnd: [DIRECT]", + "dnsStart: google.com", + "callFailed: java.net.UnknownHostException: shortcircuit", + ) + // We should consider if these logs should retain Exceptions + assertThat(logs.last().throwable).isNull() + } +} diff --git a/okhttp-logging-interceptor/api/logging-interceptor.api b/okhttp-logging-interceptor/api/logging-interceptor.api index 4f2298e27..264c2e88f 100644 --- a/okhttp-logging-interceptor/api/logging-interceptor.api +++ b/okhttp-logging-interceptor/api/logging-interceptor.api @@ -1,4 +1,5 @@ public final class okhttp3/logging/HttpLoggingInterceptor : okhttp3/Interceptor { + public static final field Companion Lokhttp3/logging/HttpLoggingInterceptor$Companion; public final fun -deprecated_level ()Lokhttp3/logging/HttpLoggingInterceptor$Level; public fun ()V public fun (Lokhttp3/logging/HttpLoggingInterceptor$Logger;)V @@ -10,6 +11,9 @@ public final class okhttp3/logging/HttpLoggingInterceptor : okhttp3/Interceptor public final fun setLevel (Lokhttp3/logging/HttpLoggingInterceptor$Level;)Lokhttp3/logging/HttpLoggingInterceptor; } +public final class okhttp3/logging/HttpLoggingInterceptor$Companion { +} + public final class okhttp3/logging/HttpLoggingInterceptor$Level : java/lang/Enum { public static final field BASIC Lokhttp3/logging/HttpLoggingInterceptor$Level; public static final field BODY Lokhttp3/logging/HttpLoggingInterceptor$Level; @@ -30,6 +34,7 @@ public final class okhttp3/logging/HttpLoggingInterceptor$Logger$Companion { } public final class okhttp3/logging/LoggingEventListener : okhttp3/EventListener { + public static final field Companion Lokhttp3/logging/LoggingEventListener$Companion; public synthetic fun (Lokhttp3/logging/HttpLoggingInterceptor$Logger;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun cacheConditionalHit (Lokhttp3/Call;Lokhttp3/Response;)V public fun cacheHit (Lokhttp3/Call;Lokhttp3/Response;)V @@ -62,6 +67,9 @@ public final class okhttp3/logging/LoggingEventListener : okhttp3/EventListener public fun secureConnectStart (Lokhttp3/Call;)V } +public final class okhttp3/logging/LoggingEventListener$Companion { +} + public class okhttp3/logging/LoggingEventListener$Factory : okhttp3/EventListener$Factory { public fun ()V public fun (Lokhttp3/logging/HttpLoggingInterceptor$Logger;)V diff --git a/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt b/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt index 3f247d41c..5d03adb5c 100644 --- a/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt +++ b/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt @@ -330,4 +330,6 @@ class HttpLoggingInterceptor val contentType = response.body.contentType() return contentType != null && contentType.type == "text" && contentType.subtype == "event-stream" } + + companion object } diff --git a/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/LoggingEventListener.kt b/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/LoggingEventListener.kt index 35588a6bf..652136b55 100644 --- a/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/LoggingEventListener.kt +++ b/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/LoggingEventListener.kt @@ -240,4 +240,6 @@ class LoggingEventListener private constructor( ) : EventListener.Factory { override fun create(call: Call): EventListener = LoggingEventListener(logger) } + + companion object }