Skip to content
This repository was archived by the owner on Mar 19, 2024. It is now read-only.

Commit c0d2e20

Browse files
committed
Log http body if it is not binary
1 parent aa665a7 commit c0d2e20

File tree

3 files changed

+166
-13
lines changed

3 files changed

+166
-13
lines changed

owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/LogBuilder.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ object LogBuilder {
2929
fun logHttp(
3030
networkPetition: NetworkPetition,
3131
networkNode: NetworkNode,
32+
requestId: String? = "",
3233
description: String
33-
) = Timber.d("[Network, $networkPetition] [$networkNode] $description")
34+
) = Timber.d("[Network, $networkPetition] [$networkNode] [$requestId] $description")
3435
}
3536

3637
enum class NetworkPetition {

owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/LogInterceptor.kt

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,145 @@
2323
*/
2424
package com.owncloud.android.lib.common.http
2525

26+
import com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
2627
import com.owncloud.android.lib.common.http.LogBuilder.logHttp
2728
import com.owncloud.android.lib.common.http.NetworkNode.BODY
2829
import com.owncloud.android.lib.common.http.NetworkNode.HEADER
2930
import com.owncloud.android.lib.common.http.NetworkNode.INFO
3031
import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST
3132
import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE
33+
import okhttp3.Headers
3234
import okhttp3.Interceptor
35+
import okhttp3.RequestBody
3336
import okhttp3.Response
37+
import okhttp3.ResponseBody
38+
import okio.Buffer
39+
import java.nio.charset.Charset
40+
import java.nio.charset.StandardCharsets
41+
import kotlin.math.max
3442

3543
class LogInterceptor : Interceptor {
3644

3745
override fun intercept(chain: Interceptor.Chain): Response {
3846

39-
val response = chain.proceed(chain.request())
47+
if (!httpLogsEnabled) {
48+
return chain.proceed(chain.request())
49+
}
50+
51+
val request = chain.request().also {
52+
val requestId = it.headers[OC_X_REQUEST_ID]
53+
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}")
54+
logHeaders(requestId, it.headers, REQUEST)
55+
logRequestBody(requestId, it.body)
56+
}
57+
58+
val response = chain.proceed(request)
4059

4160
return response.also {
42-
if (httpLogsEnabled) {
43-
// Log request
44-
logHttp(REQUEST, INFO, "Type: ${it.request.method} URL: ${it.request.url}")
45-
it.request.headers.forEach { header -> logHttp(REQUEST, HEADER, header.toString()) }
46-
logHttp(REQUEST, BODY, it.request.body.toString())
47-
48-
// Log response
49-
logHttp(RESPONSE, INFO, "Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}")
50-
it.headers.forEach { header -> logHttp(RESPONSE, HEADER, header.toString()) }
51-
logHttp(RESPONSE, BODY, it.body.toString())
52-
}
61+
val requestId = it.request.headers[OC_X_REQUEST_ID]
62+
logHttp(
63+
RESPONSE,
64+
INFO,
65+
requestId,
66+
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}"
67+
)
68+
logHeaders(requestId, it.headers, RESPONSE)
69+
logResponseBody(requestId, it.body)
70+
}
71+
}
72+
73+
private fun logHeaders(requestId: String?, headers: Headers, networkPetition: NetworkPetition) {
74+
headers.forEach { header ->
75+
logHttp(networkPetition, HEADER, requestId, "${header.first}: ${header.second}")
5376
}
5477
}
5578

79+
private fun logRequestBody(requestId: String?, requestBodyParam: RequestBody?) {
80+
requestBodyParam?.let { requestBody ->
81+
82+
if (requestBody.isOneShot()) {
83+
logHttp(REQUEST, BODY, requestId, "One shot body -- Omitted")
84+
return@let
85+
}
86+
87+
if (requestBody.isDuplex()) {
88+
logHttp(REQUEST, BODY, requestId, "Duplex body -- Omitted")
89+
return@let
90+
}
91+
92+
val buffer = Buffer()
93+
requestBody.writeTo(buffer)
94+
95+
val contentType = requestBody.contentType()
96+
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
97+
98+
logHttp(REQUEST, BODY, requestId, "Length: ${requestBody.contentLength()} byte body")
99+
logHttp(REQUEST, BODY, requestId, "Type: ${requestBody.contentType()}")
100+
logHttp(REQUEST, BODY, requestId, "--> Body start for request")
101+
102+
if (buffer.isProbablyUtf8()) {
103+
if (requestBody.contentLength() < LIMIT_BODY_LOG) {
104+
logHttp(REQUEST, BODY, requestId, buffer.readString(charset))
105+
} else {
106+
logHttp(REQUEST, BODY, requestId, buffer.readString(LIMIT_BODY_LOG, charset))
107+
}
108+
logHttp(
109+
REQUEST,
110+
BODY,
111+
requestId,
112+
"<-- Body end for request -- Omitted: ${max(0, requestBody.contentLength() - LIMIT_BODY_LOG)} bytes"
113+
)
114+
} else {
115+
logHttp(
116+
REQUEST,
117+
BODY,
118+
requestId,
119+
"<-- Body end for request -- Binary -- Omitted: ${requestBody.contentLength()} bytes"
120+
)
121+
}
122+
123+
} ?: logHttp(REQUEST, BODY, requestId, "Empty body")
124+
}
125+
126+
private fun logResponseBody(requestId: String?, responseBodyParam: ResponseBody?) {
127+
responseBodyParam?.let { responseBody ->
128+
129+
val contentType = responseBody.contentType()
130+
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
131+
132+
logHttp(RESPONSE, BODY, requestId, "Length: ${responseBody.contentLength()} byte body")
133+
logHttp(RESPONSE, BODY, requestId, "Type: ${responseBody.contentType()}")
134+
logHttp(RESPONSE, BODY, requestId, "--> Body start for request")
135+
136+
val source = responseBody.source()
137+
source.request(LIMIT_BODY_LOG)
138+
val buffer = source.buffer
139+
140+
if (!buffer.isProbablyUtf8()) {
141+
logHttp(
142+
REQUEST,
143+
BODY,
144+
requestId,
145+
"<-- Body end for request -- Binary -- Omitted: ${responseBody.contentLength()} bytes"
146+
)
147+
}
148+
149+
if (responseBody.contentLength() < LIMIT_BODY_LOG) {
150+
logHttp(REQUEST, BODY, requestId, buffer.clone().readString(charset))
151+
} else {
152+
logHttp(REQUEST, BODY, requestId, buffer.clone().readString(LIMIT_BODY_LOG, charset))
153+
}
154+
logHttp(
155+
REQUEST,
156+
BODY,
157+
requestId,
158+
"<-- Body end for request -- Omitted: ${max(0, responseBody.contentLength() - LIMIT_BODY_LOG)} bytes"
159+
)
160+
} ?: logHttp(RESPONSE, BODY, requestId, "Empty body")
161+
}
162+
56163
companion object {
57164
var httpLogsEnabled: Boolean = false
165+
private const val LIMIT_BODY_LOG: Long = 1024
58166
}
59167
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (C) 2015 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.owncloud.android.lib.common.http
17+
18+
import java.io.EOFException
19+
import okio.Buffer
20+
21+
/**
22+
* Returns true if the body in question probably contains human readable text. Uses a small
23+
* sample of code points to detect unicode control characters commonly used in binary file
24+
* signatures.
25+
*/
26+
internal fun Buffer.isProbablyUtf8(): Boolean {
27+
try {
28+
val prefix = Buffer()
29+
val byteCount = size.coerceAtMost(64)
30+
copyTo(prefix, 0, byteCount)
31+
for (i in 0 until 16) {
32+
if (prefix.exhausted()) {
33+
break
34+
}
35+
val codePoint = prefix.readUtf8CodePoint()
36+
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
37+
return false
38+
}
39+
}
40+
return true
41+
} catch (_: EOFException) {
42+
return false // Truncated UTF-8 sequence.
43+
}
44+
}

0 commit comments

Comments
 (0)