Log http requests, for easy debugging
This commit is contained in:
parent
f4170f55b7
commit
40d4e3fe83
|
@ -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"
|
||||
|
|
|
@ -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. <p> 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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<UserAgentInterceptor>())
|
||||
.addInterceptor(get<AccessTokenInterceptor>())
|
||||
.addInterceptor(get<HttpLoggingInterceptor>())
|
||||
.apply {
|
||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
addInterceptor(get<CurlLoggingInterceptor>())
|
||||
}
|
||||
}
|
||||
.addInterceptor(get<OkReplayInterceptor>())
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue