Skip to content

Commit 1f0cb4e

Browse files
committed
Add logging interceptor for simple request and response logging.
1 parent e0d9098 commit 1f0cb4e

File tree

7 files changed

+597
-0
lines changed

7 files changed

+597
-0
lines changed

okhttp-logging-interceptor/README.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Logging Interceptor
2+
===================
3+
4+
An [OkHttp interceptor][1] which logs HTTP request and response data.
5+
6+
```java
7+
OkHttpClient client = new OkHttpClient();
8+
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
9+
logging.setLevel(Level.BASIC);
10+
client.interceptors().add(logging);
11+
```
12+
13+
You can change the log level at any time by calling `setLevel`.
14+
15+
To log to a custom location, pass a `Logger` instance to the constructor.
16+
```java
17+
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new Logger() {
18+
@Override public void log(String message) {
19+
Timber.tag("OkHttp").d(message);
20+
}
21+
});
22+
```
23+
24+
**Warning**: The logs generated by this interceptor when using the `HEADERS` or `BODY` levels has
25+
the potential to leak sensitive information such as "Authorization" or "Cookie" headers and the
26+
contents of request and response bodies. This data should only be logged in a controlled way or in
27+
a non-production environment.
28+
29+
30+
Download
31+
--------
32+
33+
Get via Maven:
34+
```xml
35+
<dependency>
36+
<groupId>com.squareup.okhttp</groupId>
37+
<artifactId>logging-interceptor</artifactId>
38+
<version>(insert latest version)</version>
39+
</dependency>
40+
```
41+
42+
or via Gradle
43+
```groovy
44+
compile 'com.squareup.okhttp:logging-interceptor:(insert latest version)'
45+
```
46+
47+
48+
49+
[1]: https://github.com/square/okhttp/wiki/Interceptors

okhttp-logging-interceptor/pom.xml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.squareup.okhttp</groupId>
8+
<artifactId>parent</artifactId>
9+
<version>2.6.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>logging-interceptor</artifactId>
13+
<name>OkHttp Logging Interceptor</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.squareup.okhttp</groupId>
18+
<artifactId>okhttp</artifactId>
19+
<version>${project.version}</version>
20+
</dependency>
21+
22+
<dependency>
23+
<groupId>junit</groupId>
24+
<artifactId>junit</artifactId>
25+
<scope>test</scope>
26+
</dependency>
27+
<dependency>
28+
<groupId>com.squareup.okhttp</groupId>
29+
<artifactId>mockwebserver</artifactId>
30+
<version>${project.version}</version>
31+
<scope>test</scope>
32+
</dependency>
33+
<dependency>
34+
<groupId>com.squareup.okhttp</groupId>
35+
<artifactId>okhttp-testing-support</artifactId>
36+
<version>${project.version}</version>
37+
<scope>test</scope>
38+
</dependency>
39+
</dependencies>
40+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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.squareup.okhttp.logging;
17+
18+
import com.squareup.okhttp.Connection;
19+
import com.squareup.okhttp.Headers;
20+
import com.squareup.okhttp.HttpUrl;
21+
import com.squareup.okhttp.Interceptor;
22+
import com.squareup.okhttp.MediaType;
23+
import com.squareup.okhttp.OkHttpClient;
24+
import com.squareup.okhttp.Protocol;
25+
import com.squareup.okhttp.Request;
26+
import com.squareup.okhttp.RequestBody;
27+
import com.squareup.okhttp.Response;
28+
import com.squareup.okhttp.ResponseBody;
29+
import com.squareup.okhttp.internal.Platform;
30+
import java.io.IOException;
31+
import java.nio.charset.Charset;
32+
import java.util.concurrent.TimeUnit;
33+
import okio.Buffer;
34+
35+
/**
36+
* An OkHttp interceptor which logs request and response information. Can be applied as an
37+
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a
38+
* {@linkplain OkHttpClient#networkInterceptors() network interceptor}.
39+
* <p>
40+
* The format of the logs created by this class should not be considered stable and may change
41+
* slightly between releases. If you need a stable logging format, use your own interceptor.
42+
*/
43+
public final class HttpLoggingInterceptor implements Interceptor {
44+
private static final Charset UTF8 = Charset.forName("UTF-8");
45+
46+
public enum Level {
47+
/** No logs. */
48+
NONE,
49+
/**
50+
* Logs request and response lines.
51+
* <p>
52+
* Example:
53+
* <pre>{@code
54+
* --> POST /greeting HTTP/1.1 (3-byte body)
55+
*
56+
* <-- HTTP/1.1 200 OK (22ms, 6-byte body)
57+
* }</pre>
58+
*/
59+
BASIC,
60+
/**
61+
* Logs request and response lines and their respective headers.
62+
* <p>
63+
* Example:
64+
* <pre>{@code
65+
* --> POST /greeting HTTP/1.1
66+
* Host: example.com
67+
* Content-Type: plain/text
68+
* Content-Length: 3
69+
* --> END POST
70+
*
71+
* <-- HTTP/1.1 200 OK (22ms)
72+
* Content-Type: plain/text
73+
* Content-Length: 6
74+
* <-- END HTTP
75+
* }</pre>
76+
*/
77+
HEADERS,
78+
/**
79+
* Logs request and response lines and their respective headers and bodies (if present).
80+
* <p>
81+
* Example:
82+
* <pre>{@code
83+
* --> POST /greeting HTTP/1.1
84+
* Host: example.com
85+
* Content-Type: plain/text
86+
* Content-Length: 3
87+
*
88+
* Hi?
89+
* --> END GET
90+
*
91+
* <-- HTTP/1.1 200 OK (22ms)
92+
* Content-Type: plain/text
93+
* Content-Length: 6
94+
*
95+
* Hello!
96+
* <-- END HTTP
97+
* }</pre>
98+
*/
99+
BODY
100+
}
101+
102+
public interface Logger {
103+
void log(String message);
104+
105+
/** A {@link Logger} defaults output appropriate for the current platform. */
106+
Logger DEFAULT = new Logger() {
107+
@Override public void log(String message) {
108+
Platform.get().log(message);
109+
}
110+
};
111+
}
112+
113+
public HttpLoggingInterceptor() {
114+
this(Logger.DEFAULT);
115+
}
116+
117+
public HttpLoggingInterceptor(Logger logger) {
118+
this.logger = logger;
119+
}
120+
121+
private final Logger logger;
122+
123+
private volatile Level level = Level.NONE;
124+
125+
/** Change the level at which this interceptor logs. */
126+
public void setLevel(Level level) {
127+
this.level = level;
128+
}
129+
130+
@Override public Response intercept(Chain chain) throws IOException {
131+
Level level = this.level;
132+
133+
Request request = chain.request();
134+
if (level == Level.NONE) {
135+
return chain.proceed(request);
136+
}
137+
138+
boolean logBody = level == Level.BODY;
139+
boolean logHeaders = logBody || level == Level.HEADERS;
140+
141+
RequestBody requestBody = request.body();
142+
boolean hasRequestBody = requestBody != null;
143+
144+
Connection connection = chain.connection();
145+
Protocol protocol = connection != null ? connection.getProtocol() : Protocol.HTTP_1_1;
146+
String requestStartMessage =
147+
"--> " + request.method() + ' ' + requestPath(request.httpUrl()) + ' ' + protocol(protocol);
148+
if (!logHeaders && hasRequestBody) {
149+
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
150+
}
151+
logger.log(requestStartMessage);
152+
153+
if (logHeaders) {
154+
Headers headers = request.headers();
155+
for (int i = 0, count = headers.size(); i < count; i++) {
156+
logger.log(headers.name(i) + ": " + headers.value(i));
157+
}
158+
159+
if (logBody && hasRequestBody) {
160+
Buffer buffer = new Buffer();
161+
requestBody.writeTo(buffer);
162+
163+
Charset charset = UTF8;
164+
MediaType contentType = requestBody.contentType();
165+
if (contentType != null) {
166+
contentType.charset(UTF8);
167+
}
168+
169+
logger.log("");
170+
logger.log(buffer.readString(charset));
171+
}
172+
173+
String endMessage = "--> END " + request.method();
174+
if (logBody && hasRequestBody) {
175+
endMessage += " (" + requestBody.contentLength() + "-byte body)";
176+
}
177+
logger.log(endMessage);
178+
}
179+
180+
long startNs = System.nanoTime();
181+
Response response = chain.proceed(request);
182+
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
183+
184+
ResponseBody responseBody = response.body();
185+
logger.log("<-- " + protocol(response.protocol()) + ' ' + response.code() + ' '
186+
+ response.message() + " (" + tookMs + "ms"
187+
+ (!logHeaders ? ", " + responseBody.contentLength() + "-byte body" : "") + ')');
188+
189+
if (logHeaders) {
190+
Headers headers = response.headers();
191+
for (int i = 0, count = headers.size(); i < count; i++) {
192+
logger.log(headers.name(i) + ": " + headers.value(i));
193+
}
194+
195+
if (logBody) {
196+
Buffer buffer = new Buffer();
197+
responseBody.source().readAll(buffer);
198+
199+
Charset charset = UTF8;
200+
MediaType contentType = responseBody.contentType();
201+
if (contentType != null) {
202+
charset = contentType.charset(UTF8);
203+
}
204+
205+
if (responseBody.contentLength() > 0) {
206+
logger.log("");
207+
logger.log(buffer.clone().readString(charset));
208+
}
209+
210+
// Since we consumed the original, replace the one-shot body in the response with a new one.
211+
response = response.newBuilder()
212+
.body(ResponseBody.create(contentType, responseBody.contentLength(), buffer))
213+
.build();
214+
}
215+
216+
String endMessage = "<-- END HTTP";
217+
if (logBody) {
218+
endMessage += " (" + responseBody.contentLength() + "-byte body)";
219+
}
220+
logger.log(endMessage);
221+
}
222+
223+
return response;
224+
}
225+
226+
private static String protocol(Protocol protocol) {
227+
return protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1";
228+
}
229+
230+
private static String requestPath(HttpUrl url) {
231+
String path = url.encodedPath();
232+
String query = url.encodedQuery();
233+
return query != null ? (path + '?' + query) : path;
234+
}
235+
}

0 commit comments

Comments
 (0)