From 40d4e3fe83a86b965d13c86d045aee554cd96068 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Mar 2019 12:42:11 +0100 Subject: [PATCH] Log http requests, for easy debugging --- matrix-sdk-android/build.gradle | 30 ++++++ .../interceptors/CurlLoggingInterceptor.kt | 99 +++++++++++++++++++ .../interceptors/FormattedJsonHttpLogger.kt | 76 ++++++++++++++ .../android/internal/di/NetworkModule.kt | 17 +++- .../interceptors/CurlLoggingInterceptor.kt | 33 +++++++ .../interceptors/FormattedJsonHttpLogger.kt | 30 ++++++ 6 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt create mode 100644 matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt create mode 100644 matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt create mode 100644 matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 3d1c7e13..9b5c1c86 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -30,10 +30,25 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + resValue "string", "git_sdk_revision", "\"${gitRevision()}\"" + resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\"" + resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\"" } buildTypes { + + debug { + // Set to true to log privacy or sensible data, such as token + buildConfigField "boolean", "LOG_PRIVATE_DATA", "false" + + // Set to BODY instead of NONE to enable logging + buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE" + } + release { + buildConfigField "boolean", "LOG_PRIVATE_DATA", "false" + buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE" + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } @@ -44,6 +59,21 @@ android { } } +static def gitRevision() { + def cmd = "git rev-parse --short HEAD" + return cmd.execute().text.trim() +} + +static def gitRevisionUnixDate() { + def cmd = "git show -s --format=%ct HEAD^{commit}" + return cmd.execute().text.trim() +} + +static def gitRevisionDate() { + def cmd = "git show -s --format=%ci HEAD^{commit}" + return cmd.execute().text.trim() +} + dependencies { def arrow_version = "0.8.0" diff --git a/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt new file mode 100644 index 00000000..edca0156 --- /dev/null +++ b/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 Jeff Gilfelt. + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.network.interceptors + +import okhttp3.Interceptor +import okhttp3.Response +import okhttp3.logging.HttpLoggingInterceptor +import okio.Buffer +import java.io.IOException +import java.nio.charset.Charset + +/** + * An OkHttp interceptor that logs requests as curl shell commands. They can then + * be copied, pasted and executed inside a terminal environment. This might be + * useful for troubleshooting client/server API interaction during development, + * making it easy to isolate and share requests made by the app.

Warning: The + * logs generated by this interceptor have the potential to leak sensitive + * information. It should only be used in a controlled manner or in a + * non-production environment. + */ +internal class CurlLoggingInterceptor(private val logger: HttpLoggingInterceptor.Logger = HttpLoggingInterceptor.Logger.DEFAULT) + : Interceptor { + + /** + * Set any additional curl command options (see 'curl --help'). + */ + var curlOptions: String? = null + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + var compressed = false + + var curlCmd = "curl" + if (curlOptions != null) { + curlCmd += " " + curlOptions!! + } + curlCmd += " -X " + request.method() + + val requestBody = request.body() + if (requestBody != null) { + val buffer = Buffer() + requestBody.writeTo(buffer) + var charset: Charset? = UTF8 + val contentType = requestBody.contentType() + if (contentType != null) { + charset = contentType.charset(UTF8) + } + // try to keep to a single line and use a subshell to preserve any line breaks + curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'" + } + + val headers = request.headers() + var i = 0 + val count = headers.size() + while (i < count) { + val name = headers.name(i) + val value = headers.value(i) + if ("Accept-Encoding".equals(name, ignoreCase = true) && "gzip".equals(value, ignoreCase = true)) { + compressed = true + } + curlCmd += " -H \"$name: $value\"" + i++ + } + + curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url().toString() + // Replace localhost for emulator by localhost for shell + .replace("://10.0.2.2:8080/".toRegex(), "://127.0.0.1:8080/") + + "'") + + // Add Json formatting + curlCmd += " | python -m json.tool" + + logger.log("--- cURL (" + request.url() + ")") + logger.log(curlCmd) + + return chain.proceed(request) + } + + companion object { + private val UTF8 = Charset.forName("UTF-8") + } +} diff --git a/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt new file mode 100644 index 00000000..655134d1 --- /dev/null +++ b/matrix-sdk-android/src/debug/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.network.interceptors + +import androidx.annotation.NonNull +import im.vector.matrix.android.BuildConfig +import okhttp3.logging.HttpLoggingInterceptor +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import timber.log.Timber + +class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { + + companion object { + private const val INDENT_SPACE = 2 + } + + /** + * Log the message and try to log it again as a JSON formatted string + * Note: it can consume a lot of memory but it is only in DEBUG mode + * + * @param message + */ + @Synchronized + override fun log(@NonNull message: String) { + // In RELEASE there is no log, but for sure, test again BuildConfig.DEBUG + if (BuildConfig.DEBUG) { + Timber.v(message) + + if (message.startsWith("{")) { + // JSON Detected + try { + val o = JSONObject(message) + logJson(o.toString(INDENT_SPACE)) + } catch (e: JSONException) { + // Finally this is not a JSON string... + Timber.e(e) + } + + } else if (message.startsWith("[")) { + // JSON Array detected + try { + val o = JSONArray(message) + logJson(o.toString(INDENT_SPACE)) + } catch (e: JSONException) { + // Finally not JSON... + Timber.e(e) + } + + } + // Else not a json string to log + } + } + + private fun logJson(formattedJson: String) { + val arr = formattedJson.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (s in arr) { + Timber.v(s) + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt index 1aa733f7..f1e1a82b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt @@ -19,14 +19,14 @@ package im.vector.matrix.android.internal.di import com.facebook.stetho.okhttp3.StethoInterceptor import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.internal.network.* -import im.vector.matrix.android.internal.network.UnitConverterFactory +import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor +import im.vector.matrix.android.internal.network.interceptors.FormattedJsonHttpLogger import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okreplay.OkReplayInterceptor import org.koin.dsl.module.module import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory -import timber.log.Timber import java.util.concurrent.TimeUnit class NetworkModule { @@ -46,12 +46,16 @@ class NetworkModule { } single { - val logger = HttpLoggingInterceptor.Logger { message -> Timber.v(message) } + val logger = FormattedJsonHttpLogger() val interceptor = HttpLoggingInterceptor(logger) - interceptor.level = HttpLoggingInterceptor.Level.BASIC + interceptor.level = BuildConfig.OKHTTP_LOGGING_LEVEL interceptor } + single { + CurlLoggingInterceptor() + } + single { OkReplayInterceptor() } @@ -69,6 +73,11 @@ class NetworkModule { .addInterceptor(get()) .addInterceptor(get()) .addInterceptor(get()) + .apply { + if (BuildConfig.LOG_PRIVATE_DATA) { + addInterceptor(get()) + } + } .addInterceptor(get()) .build() } diff --git a/matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt new file mode 100644 index 00000000..998cdc7e --- /dev/null +++ b/matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/CurlLoggingInterceptor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 Jeff Gilfelt. + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.network.interceptors + +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +/** + * No op interceptor + */ +internal class CurlLoggingInterceptor : Interceptor { + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + return chain.proceed(chain.request()) + } +} diff --git a/matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt new file mode 100644 index 00000000..d2fb4ca8 --- /dev/null +++ b/matrix-sdk-android/src/release/java/im/vector/matrix/android/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.network.interceptors + +import androidx.annotation.NonNull +import okhttp3.logging.HttpLoggingInterceptor + +/** + * No op logger + */ +internal class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { + + @Synchronized + override fun log(@NonNull message: String) { + } +}