Example: 35 | *
{@code
36 | * --> POST /greeting HTTP/1.1 (3-byte body)
37 | *
38 | * <-- HTTP/1.1 200 OK (22ms, 6-byte body)
39 | * }
40 | */
41 | BASIC,
42 | /**
43 | * Logs request and response lines and their respective headers.
44 | *
45 | * Example: 46 | *
{@code
47 | * --> POST /greeting HTTP/1.1
48 | * Host: example.com
49 | * Content-Type: plain/text
50 | * Content-Length: 3
51 | * --> END POST
52 | *
53 | * <-- HTTP/1.1 200 OK (22ms)
54 | * Content-Type: plain/text
55 | * Content-Length: 6
56 | * <-- END HTTP
57 | * }
58 | */
59 | HEADERS,
60 | /**
61 | * Logs request and response lines and their respective headers and bodies (if present).
62 | *
63 | * Example: 64 | *
{@code
65 | * --> POST /greeting HTTP/1.1
66 | * Host: example.com
67 | * Content-Type: plain/text
68 | * Content-Length: 3
69 | *
70 | * Hi?
71 | * --> END GET
72 | *
73 | * <-- HTTP/1.1 200 OK (22ms)
74 | * Content-Type: plain/text
75 | * Content-Length: 6
76 | *
77 | * Hello!
78 | * <-- END HTTP
79 | * }
80 | */
81 | BODY
82 | }
83 |
84 | public interface Logger {
85 | void log(String message);
86 |
87 | /** A {@link Logger} defaults output appropriate for the current platform. */
88 | Logger DEFAULT = new Logger() {
89 | @Override public void log(String message) {
90 | Platform.get().log(5,message,null);
91 | }
92 | };
93 | }
94 |
95 | public HttpLoggingInterceptor() {
96 | this(Logger.DEFAULT);
97 | }
98 |
99 | public HttpLoggingInterceptor(Logger logger) {
100 | this.logger = logger;
101 | }
102 |
103 | private final Logger logger;
104 |
105 | private volatile Level level = Level.NONE;
106 |
107 | /** Change the level at which this interceptor logs. */
108 | public HttpLoggingInterceptor setLevel(Level level) {
109 | if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
110 | this.level = level;
111 | return this;
112 | }
113 |
114 | public Level getLevel() {
115 | return level;
116 | }
117 |
118 | @Override public Response intercept(Chain chain) throws IOException {
119 | Level level = this.level;
120 |
121 | Request request = chain.request();
122 | if (level == Level.NONE) {
123 | return chain.proceed(request);
124 | }
125 |
126 | boolean logBody = level == Level.BODY;
127 | boolean logHeaders = logBody || level == Level.HEADERS;
128 |
129 | RequestBody requestBody = request.body();
130 | boolean hasRequestBody = requestBody != null;
131 |
132 | Connection connection = chain.connection();
133 | Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
134 | String requestStartMessage =
135 | "--> " + request.method() + ' ' + request.url() + ' ' + protocol(protocol);
136 | if (!logHeaders && hasRequestBody) {
137 | requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
138 | }
139 | logger.log(requestStartMessage);
140 |
141 | if (logHeaders) {
142 | if (hasRequestBody) {
143 | // Request body headers are only present when installed as a network interceptor. Force
144 | // them to be included (when available) so there values are known.
145 | if (requestBody.contentType() != null) {
146 | logger.log("Content-Type: " + requestBody.contentType());
147 | }
148 | if (requestBody.contentLength() != -1) {
149 | logger.log("Content-Length: " + requestBody.contentLength());
150 | }
151 | }
152 |
153 | Headers headers = request.headers();
154 | for (int i = 0, count = headers.size(); i < count; i++) {
155 | String name = headers.name(i);
156 | // Skip headers from the request body as they are explicitly logged above.
157 | if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
158 | logger.log(name + ": " + headers.value(i));
159 | }
160 | }
161 |
162 | if (!logBody || !hasRequestBody) {
163 | logger.log("--> END " + request.method());
164 | } else if (bodyEncoded(request.headers())) {
165 | logger.log("--> END " + request.method() + " (encoded body omitted)");
166 | } else {
167 | Buffer buffer = new Buffer();
168 | requestBody.writeTo(buffer);
169 |
170 | Charset charset = UTF8;
171 | MediaType contentType = requestBody.contentType();
172 | if (contentType != null) {
173 | charset = contentType.charset(UTF8);
174 | }
175 |
176 | logger.log("");
177 | logger.log(buffer.readString(charset));
178 |
179 | logger.log("--> END " + request.method()
180 | + " (" + requestBody.contentLength() + "-byte body)");
181 | }
182 | }
183 |
184 | long startNs = System.nanoTime();
185 | Response response = chain.proceed(request);
186 | long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
187 |
188 | ResponseBody responseBody = response.body();
189 | long contentLength = responseBody.contentLength();
190 | String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
191 | logger.log("<-- " + response.code() + ' ' + response.message() + ' '
192 | + response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
193 | + bodySize + " body" : "") + ')');
194 |
195 | if (logHeaders) {
196 | Headers headers = response.headers();
197 | for (int i = 0, count = headers.size(); i < count; i++) {
198 | logger.log(headers.name(i) + ": " + headers.value(i));
199 | }
200 |
201 | if (!logBody || !HttpEngine.hasBody(response)) {
202 | logger.log("<-- END HTTP");
203 | } else if (bodyEncoded(response.headers())) {
204 | logger.log("<-- END HTTP (encoded body omitted)");
205 | } else {
206 | BufferedSource source = responseBody.source();
207 | source.request(Long.MAX_VALUE); // Buffer the entire body.
208 | Buffer buffer = source.buffer();
209 |
210 | Charset charset = UTF8;
211 | MediaType contentType = responseBody.contentType();
212 | if (contentType != null) {
213 | charset = contentType.charset(UTF8);
214 | }
215 |
216 | if (contentLength != 0) {
217 | logger.log("");
218 | logger.log(buffer.clone().readString(charset));
219 | }
220 |
221 | logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
222 | }
223 | }
224 |
225 | return response;
226 | }
227 |
228 | private boolean bodyEncoded(Headers headers) {
229 | String contentEncoding = headers.get("Content-Encoding");
230 | return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
231 | }
232 |
233 | private static String protocol(Protocol protocol) {
234 | return protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1";
235 | }
236 | }
237 |
--------------------------------------------------------------------------------