diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 3d1c7e1397..9b5c1c8662 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 0000000000..edca01562a --- /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 0000000000..655134d1e3
--- /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 1aa733f7dc..f1e1a82b59 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