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

Commit 6193782

Browse files
authored
Merge pull request #345 from owncloud/add_network_logs
Log network calls
2 parents e28335a + 2b64b83 commit 6193782

File tree

6 files changed

+268
-7
lines changed

6 files changed

+268
-7
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public class HttpClient {
5757
private static OkHttpClient sOkHttpClient;
5858
private static Context sContext;
5959
private static HashMap<String, List<Cookie>> sCookieStore = new HashMap<>();
60+
private static LogInterceptor sLogInterceptor;
6061

6162
public static OkHttpClient getOkHttpClient() {
6263
if (sOkHttpClient == null) {
@@ -110,6 +111,7 @@ public List<Cookie> loadForRequest(HttpUrl url) {
110111
};
111112

112113
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
114+
.addNetworkInterceptor(getLogInterceptor())
113115
.protocols(Arrays.asList(Protocol.HTTP_1_1))
114116
.readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
115117
.writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
@@ -120,6 +122,7 @@ public List<Cookie> loadForRequest(HttpUrl url) {
120122
.cookieJar(cookieJar);
121123
// TODO: Not verifying the hostname against certificate. ask owncloud security human if this is ok.
122124
//.hostnameVerifier(new BrowserCompatHostnameVerifier());
125+
123126
sOkHttpClient = clientBuilder.build();
124127

125128
} catch (Exception e) {
@@ -137,6 +140,13 @@ public static void setContext(Context context) {
137140
sContext = context;
138141
}
139142

143+
public static LogInterceptor getLogInterceptor() {
144+
if (sLogInterceptor == null) {
145+
sLogInterceptor = new LogInterceptor();
146+
}
147+
return sLogInterceptor;
148+
}
149+
140150
public List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) {
141151
return sCookieStore.get(httpUrl.host());
142152
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ public class HttpConstants {
5151
public static final String ACCEPT_ENCODING_IDENTITY = "identity";
5252
public static final String OC_FILE_REMOTE_ID = "OC-FileId";
5353

54+
/***********************************************************************************************************
55+
************************************************ CONTENT TYPES ********************************************
56+
***********************************************************************************************************/
57+
58+
public static final String CONTENT_TYPE_XML = "application/xml";
59+
public static final String CONTENT_TYPE_JSON = "application/json";
60+
public static final String CONTENT_TYPE_WWW_FORM = "application/x-www-form-urlencoded";
61+
5462
/***********************************************************************************************************
5563
************************************************ STATUS CODES *********************************************
5664
***********************************************************************************************************/
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* ownCloud Android Library is available under MIT license
2+
* Copyright (C) 2020 ownCloud GmbH.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18+
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19+
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
package com.owncloud.android.lib.common.http
24+
25+
import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_JSON
26+
import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_WWW_FORM
27+
import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_XML
28+
import okhttp3.MediaType
29+
import timber.log.Timber
30+
import java.util.Locale
31+
32+
object LogBuilder {
33+
fun logHttp(
34+
networkPetition: NetworkPetition,
35+
networkNode: NetworkNode,
36+
requestId: String? = "",
37+
description: String
38+
) = Timber.d("[Network, $networkPetition] [$networkNode] [$requestId] $description")
39+
}
40+
41+
enum class NetworkPetition {
42+
REQUEST, RESPONSE;
43+
44+
override fun toString(): String = super.toString().toLowerCase(Locale.ROOT)
45+
}
46+
47+
enum class NetworkNode {
48+
INFO, HEADER, BODY;
49+
50+
override fun toString(): String = super.toString().toLowerCase(Locale.ROOT)
51+
}
52+
53+
/**
54+
* Check whether a media type is loggable.
55+
*
56+
* @return true if its type is text, xml, json, or x-www-form-urlencoded.
57+
*/
58+
fun MediaType?.isLoggable(): Boolean =
59+
this?.let { mediaType ->
60+
val mediaTypeString = mediaType.toString()
61+
(mediaType.type == "text" ||
62+
mediaTypeString.contains(CONTENT_TYPE_XML) ||
63+
mediaTypeString.contains(CONTENT_TYPE_JSON) ||
64+
mediaTypeString.contains(CONTENT_TYPE_WWW_FORM))
65+
} ?: false
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/* ownCloud Android Library is available under MIT license
2+
* Copyright (C) 2020 ownCloud GmbH.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18+
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19+
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*
23+
*/
24+
package com.owncloud.android.lib.common.http
25+
26+
import com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER
27+
import com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
28+
import com.owncloud.android.lib.common.http.LogBuilder.logHttp
29+
import com.owncloud.android.lib.common.http.NetworkNode.BODY
30+
import com.owncloud.android.lib.common.http.NetworkNode.HEADER
31+
import com.owncloud.android.lib.common.http.NetworkNode.INFO
32+
import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST
33+
import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE
34+
import okhttp3.Headers
35+
import okhttp3.Interceptor
36+
import okhttp3.RequestBody
37+
import okhttp3.Response
38+
import okhttp3.ResponseBody
39+
import okio.Buffer
40+
import java.nio.charset.Charset
41+
import java.nio.charset.StandardCharsets
42+
import kotlin.math.max
43+
44+
class LogInterceptor : Interceptor {
45+
46+
override fun intercept(chain: Interceptor.Chain): Response {
47+
48+
if (!httpLogsEnabled) {
49+
return chain.proceed(chain.request())
50+
}
51+
52+
val request = chain.request().also {
53+
val requestId = it.headers[OC_X_REQUEST_ID]
54+
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}")
55+
logHeaders(requestId, it.headers, REQUEST)
56+
logRequestBody(requestId, it.body)
57+
}
58+
59+
val response = chain.proceed(request)
60+
61+
return response.also {
62+
val requestId = it.request.headers[OC_X_REQUEST_ID]
63+
logHttp(
64+
RESPONSE,
65+
INFO,
66+
requestId,
67+
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}"
68+
)
69+
logHeaders(requestId, it.headers, RESPONSE)
70+
logResponseBody(requestId, it.body)
71+
}
72+
}
73+
74+
private fun logHeaders(requestId: String?, headers: Headers, networkPetition: NetworkPetition) {
75+
headers.forEach { header ->
76+
val headerValue: String = if (header.first.equals(AUTHORIZATION_HEADER, true)) {
77+
"[redacted]"
78+
} else {
79+
header.second
80+
}
81+
logHttp(networkPetition, HEADER, requestId, "${header.first}: $headerValue")
82+
}
83+
}
84+
85+
private fun logRequestBody(requestId: String?, requestBodyParam: RequestBody?) {
86+
requestBodyParam?.let { requestBody ->
87+
88+
if (requestBody.isOneShot()) {
89+
logHttp(REQUEST, BODY, requestId, "One shot body -- Omitted")
90+
return@let
91+
}
92+
93+
if (requestBody.isDuplex()) {
94+
logHttp(REQUEST, BODY, requestId, "Duplex body -- Omitted")
95+
return@let
96+
}
97+
98+
val buffer = Buffer()
99+
requestBody.writeTo(buffer)
100+
101+
val contentType = requestBody.contentType()
102+
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
103+
104+
logHttp(REQUEST, BODY, requestId, "Length: ${requestBody.contentLength()} byte body")
105+
logHttp(REQUEST, BODY, requestId, "Type: ${requestBody.contentType()}")
106+
logHttp(REQUEST, BODY, requestId, "--> Body start for request")
107+
108+
if (contentType.isLoggable()) {
109+
if (requestBody.contentLength() < LIMIT_BODY_LOG) {
110+
logHttp(REQUEST, BODY, requestId, buffer.readString(charset))
111+
} else {
112+
logHttp(REQUEST, BODY, requestId, buffer.readString(LIMIT_BODY_LOG, charset))
113+
}
114+
logHttp(
115+
REQUEST,
116+
BODY,
117+
requestId,
118+
"<-- Body end for request -- Omitted: ${max(0, requestBody.contentLength() - LIMIT_BODY_LOG)} bytes"
119+
)
120+
} else {
121+
logHttp(
122+
REQUEST,
123+
BODY,
124+
requestId,
125+
"<-- Body end for request -- Binary -- Omitted: ${requestBody.contentLength()} bytes"
126+
)
127+
}
128+
129+
} ?: logHttp(REQUEST, BODY, requestId, "Empty body")
130+
}
131+
132+
private fun logResponseBody(requestId: String?, responseBodyParam: ResponseBody?) {
133+
responseBodyParam?.let { responseBody ->
134+
135+
val contentType = responseBody.contentType()
136+
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
137+
138+
logHttp(RESPONSE, BODY, requestId, "Length: ${responseBody.contentLength()} byte body")
139+
logHttp(RESPONSE, BODY, requestId, "Type: ${responseBody.contentType()}")
140+
logHttp(RESPONSE, BODY, requestId, "--> Body start for response")
141+
142+
val source = responseBody.source()
143+
source.request(LIMIT_BODY_LOG)
144+
val buffer = source.buffer
145+
146+
if (contentType.isLoggable()) {
147+
148+
if (responseBody.contentLength() < LIMIT_BODY_LOG) {
149+
logHttp(RESPONSE, BODY, requestId, buffer.clone().readString(charset))
150+
} else {
151+
logHttp(RESPONSE, BODY, requestId, buffer.clone().readString(LIMIT_BODY_LOG, charset))
152+
}
153+
logHttp(
154+
RESPONSE,
155+
BODY,
156+
requestId,
157+
"<-- Body end for response -- Omitted: ${
158+
max(
159+
0,
160+
responseBody.contentLength() - LIMIT_BODY_LOG
161+
)
162+
} bytes"
163+
)
164+
} else {
165+
logHttp(
166+
RESPONSE,
167+
BODY,
168+
requestId,
169+
"<-- Body end for response -- Binary -- Omitted: ${responseBody.contentLength()} bytes"
170+
)
171+
}
172+
} ?: logHttp(RESPONSE, BODY, requestId, "Empty body")
173+
}
174+
175+
companion object {
176+
var httpLogsEnabled: Boolean = false
177+
private const val LIMIT_BODY_LOG: Long = 1024
178+
}
179+
}

owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,12 @@ public void writeTo(BufferedSink sink) {
8484
long maxCount = Math.min(mOffset + mChunkSize, mChannel.size());
8585
while (mChannel.position() < maxCount) {
8686

87-
Timber.v("Sink buffer size: %s", sink.buffer().size());
88-
8987
readCount = mChannel.read(mBuffer);
9088

91-
Timber.v("Read " + readCount + " bytes from file channel to " + mBuffer.toString());
92-
93-
sink.buffer().write(mBuffer.array(), 0, readCount);
89+
sink.getBuffer().write(mBuffer.array(), 0, readCount);
9490

9591
sink.flush();
9692

97-
Timber.v("Write " + readCount + " bytes to sink buffer with size " + sink.buffer().size());
98-
9993
mBuffer.clear();
10094
if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
10195
mTransferred += readCount;

owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public FileRequestBody(File file, MediaType contentType) {
5353
mContentType = contentType;
5454
}
5555

56+
@Override
57+
public boolean isOneShot() {
58+
return true;
59+
}
60+
5661
@Override
5762
public MediaType contentType() {
5863
return mContentType;

0 commit comments

Comments
 (0)