mirror of
https://github.com/owncloud/android-library.git
synced 2025-06-07 16:06:08 +00:00
Log http body if it is not binary
This commit is contained in:
parent
aa665a7295
commit
c0d2e20bb1
@ -29,8 +29,9 @@ object LogBuilder {
|
|||||||
fun logHttp(
|
fun logHttp(
|
||||||
networkPetition: NetworkPetition,
|
networkPetition: NetworkPetition,
|
||||||
networkNode: NetworkNode,
|
networkNode: NetworkNode,
|
||||||
|
requestId: String? = "",
|
||||||
description: String
|
description: String
|
||||||
) = Timber.d("[Network, $networkPetition] [$networkNode] $description")
|
) = Timber.d("[Network, $networkPetition] [$networkNode] [$requestId] $description")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class NetworkPetition {
|
enum class NetworkPetition {
|
||||||
|
@ -23,37 +23,145 @@
|
|||||||
*/
|
*/
|
||||||
package com.owncloud.android.lib.common.http
|
package com.owncloud.android.lib.common.http
|
||||||
|
|
||||||
|
import com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
|
||||||
import com.owncloud.android.lib.common.http.LogBuilder.logHttp
|
import com.owncloud.android.lib.common.http.LogBuilder.logHttp
|
||||||
import com.owncloud.android.lib.common.http.NetworkNode.BODY
|
import com.owncloud.android.lib.common.http.NetworkNode.BODY
|
||||||
import com.owncloud.android.lib.common.http.NetworkNode.HEADER
|
import com.owncloud.android.lib.common.http.NetworkNode.HEADER
|
||||||
import com.owncloud.android.lib.common.http.NetworkNode.INFO
|
import com.owncloud.android.lib.common.http.NetworkNode.INFO
|
||||||
import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST
|
import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST
|
||||||
import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE
|
import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE
|
||||||
|
import okhttp3.Headers
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.RequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import okio.Buffer
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class LogInterceptor : Interceptor {
|
class LogInterceptor : Interceptor {
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
|
||||||
val response = chain.proceed(chain.request())
|
if (!httpLogsEnabled) {
|
||||||
|
return chain.proceed(chain.request())
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = chain.request().also {
|
||||||
|
val requestId = it.headers[OC_X_REQUEST_ID]
|
||||||
|
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}")
|
||||||
|
logHeaders(requestId, it.headers, REQUEST)
|
||||||
|
logRequestBody(requestId, it.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
|
||||||
return response.also {
|
return response.also {
|
||||||
if (httpLogsEnabled) {
|
val requestId = it.request.headers[OC_X_REQUEST_ID]
|
||||||
// Log request
|
logHttp(
|
||||||
logHttp(REQUEST, INFO, "Type: ${it.request.method} URL: ${it.request.url}")
|
RESPONSE,
|
||||||
it.request.headers.forEach { header -> logHttp(REQUEST, HEADER, header.toString()) }
|
INFO,
|
||||||
logHttp(REQUEST, BODY, it.request.body.toString())
|
requestId,
|
||||||
|
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}"
|
||||||
// Log response
|
)
|
||||||
logHttp(RESPONSE, INFO, "Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}")
|
logHeaders(requestId, it.headers, RESPONSE)
|
||||||
it.headers.forEach { header -> logHttp(RESPONSE, HEADER, header.toString()) }
|
logResponseBody(requestId, it.body)
|
||||||
logHttp(RESPONSE, BODY, it.body.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun logHeaders(requestId: String?, headers: Headers, networkPetition: NetworkPetition) {
|
||||||
|
headers.forEach { header ->
|
||||||
|
logHttp(networkPetition, HEADER, requestId, "${header.first}: ${header.second}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logRequestBody(requestId: String?, requestBodyParam: RequestBody?) {
|
||||||
|
requestBodyParam?.let { requestBody ->
|
||||||
|
|
||||||
|
if (requestBody.isOneShot()) {
|
||||||
|
logHttp(REQUEST, BODY, requestId, "One shot body -- Omitted")
|
||||||
|
return@let
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestBody.isDuplex()) {
|
||||||
|
logHttp(REQUEST, BODY, requestId, "Duplex body -- Omitted")
|
||||||
|
return@let
|
||||||
|
}
|
||||||
|
|
||||||
|
val buffer = Buffer()
|
||||||
|
requestBody.writeTo(buffer)
|
||||||
|
|
||||||
|
val contentType = requestBody.contentType()
|
||||||
|
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
|
||||||
|
|
||||||
|
logHttp(REQUEST, BODY, requestId, "Length: ${requestBody.contentLength()} byte body")
|
||||||
|
logHttp(REQUEST, BODY, requestId, "Type: ${requestBody.contentType()}")
|
||||||
|
logHttp(REQUEST, BODY, requestId, "--> Body start for request")
|
||||||
|
|
||||||
|
if (buffer.isProbablyUtf8()) {
|
||||||
|
if (requestBody.contentLength() < LIMIT_BODY_LOG) {
|
||||||
|
logHttp(REQUEST, BODY, requestId, buffer.readString(charset))
|
||||||
|
} else {
|
||||||
|
logHttp(REQUEST, BODY, requestId, buffer.readString(LIMIT_BODY_LOG, charset))
|
||||||
|
}
|
||||||
|
logHttp(
|
||||||
|
REQUEST,
|
||||||
|
BODY,
|
||||||
|
requestId,
|
||||||
|
"<-- Body end for request -- Omitted: ${max(0, requestBody.contentLength() - LIMIT_BODY_LOG)} bytes"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logHttp(
|
||||||
|
REQUEST,
|
||||||
|
BODY,
|
||||||
|
requestId,
|
||||||
|
"<-- Body end for request -- Binary -- Omitted: ${requestBody.contentLength()} bytes"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} ?: logHttp(REQUEST, BODY, requestId, "Empty body")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logResponseBody(requestId: String?, responseBodyParam: ResponseBody?) {
|
||||||
|
responseBodyParam?.let { responseBody ->
|
||||||
|
|
||||||
|
val contentType = responseBody.contentType()
|
||||||
|
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
|
||||||
|
|
||||||
|
logHttp(RESPONSE, BODY, requestId, "Length: ${responseBody.contentLength()} byte body")
|
||||||
|
logHttp(RESPONSE, BODY, requestId, "Type: ${responseBody.contentType()}")
|
||||||
|
logHttp(RESPONSE, BODY, requestId, "--> Body start for request")
|
||||||
|
|
||||||
|
val source = responseBody.source()
|
||||||
|
source.request(LIMIT_BODY_LOG)
|
||||||
|
val buffer = source.buffer
|
||||||
|
|
||||||
|
if (!buffer.isProbablyUtf8()) {
|
||||||
|
logHttp(
|
||||||
|
REQUEST,
|
||||||
|
BODY,
|
||||||
|
requestId,
|
||||||
|
"<-- Body end for request -- Binary -- Omitted: ${responseBody.contentLength()} bytes"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseBody.contentLength() < LIMIT_BODY_LOG) {
|
||||||
|
logHttp(REQUEST, BODY, requestId, buffer.clone().readString(charset))
|
||||||
|
} else {
|
||||||
|
logHttp(REQUEST, BODY, requestId, buffer.clone().readString(LIMIT_BODY_LOG, charset))
|
||||||
|
}
|
||||||
|
logHttp(
|
||||||
|
REQUEST,
|
||||||
|
BODY,
|
||||||
|
requestId,
|
||||||
|
"<-- Body end for request -- Omitted: ${max(0, responseBody.contentLength() - LIMIT_BODY_LOG)} bytes"
|
||||||
|
)
|
||||||
|
} ?: logHttp(RESPONSE, BODY, requestId, "Empty body")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var httpLogsEnabled: Boolean = false
|
var httpLogsEnabled: Boolean = false
|
||||||
|
private const val LIMIT_BODY_LOG: Long = 1024
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 com.owncloud.android.lib.common.http
|
||||||
|
|
||||||
|
import java.io.EOFException
|
||||||
|
import okio.Buffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the body in question probably contains human readable text. Uses a small
|
||||||
|
* sample of code points to detect unicode control characters commonly used in binary file
|
||||||
|
* signatures.
|
||||||
|
*/
|
||||||
|
internal fun Buffer.isProbablyUtf8(): Boolean {
|
||||||
|
try {
|
||||||
|
val prefix = Buffer()
|
||||||
|
val byteCount = size.coerceAtMost(64)
|
||||||
|
copyTo(prefix, 0, byteCount)
|
||||||
|
for (i in 0 until 16) {
|
||||||
|
if (prefix.exhausted()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val codePoint = prefix.readUtf8CodePoint()
|
||||||
|
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (_: EOFException) {
|
||||||
|
return false // Truncated UTF-8 sequence.
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user