mirror of
https://github.com/square/okhttp.git
synced 2025-11-24 18:41:06 +03:00
Add a LoggingEventListener and use it in okcurl (#4353)
* Add LoggingEventListener and use it in okcurl * Fix tests failing in IP6 environment * Make less assumptions about test environment * Disable retry in test to make test sequence more predictable * Fix javadoc compilation There seems to be inconsistency between javdoc parsing between 'mvn verify' and Travis CI. Before the change, 'mvn clean verify' passes but Travis CI fails due to missing import of okhttp3.OkHttpClient. Just adding the missing import, causes 'mvn verify' to fail die to unused import. Changing the line wrapping seems to appease 'mvn verify'. * Address comments * Remove unused imports
This commit is contained in:
committed by
Jesse Wilson
parent
8a01554770
commit
ef34a41d09
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 okhttp3.logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nullable;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Connection;
|
||||
import okhttp3.EventListener;
|
||||
import okhttp3.Handshake;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* An OkHttp EventListener, which logs call events. Can be applied as an
|
||||
* {@linkplain OkHttpClient#eventListenerFactory() event listener factory}.
|
||||
*
|
||||
* <p>The format of the logs created by this class should not be considered stable and may change
|
||||
* slightly between releases. If you need a stable logging format, use your own event listener.
|
||||
*/
|
||||
public final class LoggingEventListener extends EventListener {
|
||||
private final HttpLoggingInterceptor.Logger logger;
|
||||
private long startNs;
|
||||
|
||||
private LoggingEventListener(HttpLoggingInterceptor.Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callStart(Call call) {
|
||||
startNs = System.nanoTime();
|
||||
|
||||
logWithTime("callStart: " + call.request());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dnsStart(Call call, String domainName) {
|
||||
logWithTime("dnsStart: " + domainName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
|
||||
logWithTime("dnsEnd: " + inetAddressList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
|
||||
logWithTime("connectStart: " + inetSocketAddress + " " + proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secureConnectStart(Call call) {
|
||||
logWithTime("secureConnectStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void secureConnectEnd(Call call, @Nullable Handshake handshake) {
|
||||
logWithTime("secureConnectEnd");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectEnd(
|
||||
Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol) {
|
||||
logWithTime("connectEnd: " + protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(
|
||||
Call call,
|
||||
InetSocketAddress inetSocketAddress,
|
||||
Proxy proxy,
|
||||
@Nullable Protocol protocol,
|
||||
IOException ioe) {
|
||||
logWithTime("connectFailed: " + protocol + " " + ioe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionAcquired(Call call, Connection connection) {
|
||||
logWithTime("connectionAcquired: " + connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionReleased(Call call, Connection connection) {
|
||||
logWithTime("connectionReleased");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestHeadersStart(Call call) {
|
||||
logWithTime("requestHeadersStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestHeadersEnd(Call call, Request request) {
|
||||
logWithTime("requestHeadersEnd");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBodyStart(Call call) {
|
||||
logWithTime("requestBodyStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBodyEnd(Call call, long byteCount) {
|
||||
logWithTime("requestBodyEnd: byteCount=" + byteCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void responseHeadersStart(Call call) {
|
||||
logWithTime("responseHeadersStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void responseHeadersEnd(Call call, Response response) {
|
||||
logWithTime("responseHeadersEnd: " + response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void responseBodyStart(Call call) {
|
||||
logWithTime("responseBodyStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void responseBodyEnd(Call call, long byteCount) {
|
||||
logWithTime("responseBodyEnd: byteCount=" + byteCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callEnd(Call call) {
|
||||
logWithTime("callEnd");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callFailed(Call call, IOException ioe) {
|
||||
logWithTime("callFailed: " + ioe);
|
||||
}
|
||||
|
||||
private void logWithTime(String message) {
|
||||
long timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
|
||||
logger.log("[" + timeMs + " ms] " + message);
|
||||
}
|
||||
|
||||
public static class Factory implements EventListener.Factory {
|
||||
private final HttpLoggingInterceptor.Logger logger;
|
||||
|
||||
public Factory() {
|
||||
this(HttpLoggingInterceptor.Logger.DEFAULT);
|
||||
}
|
||||
|
||||
public Factory(HttpLoggingInterceptor.Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventListener create(Call call) {
|
||||
return new LoggingEventListener(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -807,7 +807,7 @@ public final class HttpLoggingInterceptorTest {
|
||||
return new Request.Builder().url(url);
|
||||
}
|
||||
|
||||
private static class LogRecorder implements HttpLoggingInterceptor.Logger {
|
||||
static class LogRecorder implements HttpLoggingInterceptor.Logger {
|
||||
private final List<String> logs = new ArrayList<>();
|
||||
private int index;
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 okhttp3.logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import okhttp3.Dns;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.SocketPolicy;
|
||||
import okhttp3.tls.HandshakeCertificates;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static okhttp3.tls.internal.TlsUtil.localhost;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public final class LoggingEventListenerTest {
|
||||
private static final MediaType PLAIN = MediaType.get("text/plain");
|
||||
|
||||
@Rule public final MockWebServer server = new MockWebServer();
|
||||
|
||||
private final HandshakeCertificates handshakeCertificates = localhost();
|
||||
private final LogRecorder logRecorder = new LogRecorder();
|
||||
private final LoggingEventListener.Factory loggingEventListenerFactory =
|
||||
new LoggingEventListener.Factory(logRecorder);
|
||||
private OkHttpClient client;
|
||||
private HttpUrl url;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
client =
|
||||
new OkHttpClient.Builder()
|
||||
.eventListenerFactory(loggingEventListenerFactory)
|
||||
.sslSocketFactory(
|
||||
handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
|
||||
.retryOnConnectionFailure(false)
|
||||
.build();
|
||||
|
||||
url = server.url("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("Hello!").setHeader("Content-Type", PLAIN));
|
||||
Response response = client.newCall(request().build()).execute();
|
||||
assertNotNull(response.body());
|
||||
response.body().bytes();
|
||||
|
||||
logRecorder
|
||||
.assertLogMatch("callStart: Request\\{method=GET, url=" + url + ", tags=\\{\\}\\}")
|
||||
.assertLogMatch("dnsStart: " + url.host())
|
||||
.assertLogMatch("dnsEnd: \\[.+\\]")
|
||||
.assertLogMatch("connectStart: " + url.host() + "/.+ DIRECT")
|
||||
.assertLogMatch("connectEnd: http/1.1")
|
||||
.assertLogMatch(
|
||||
"connectionAcquired: Connection\\{"
|
||||
+ url.host()
|
||||
+ ":\\d+, proxy=DIRECT hostAddress="
|
||||
+ url.host()
|
||||
+ "/.+ cipherSuite=none protocol=http/1\\.1\\}")
|
||||
.assertLogMatch("requestHeadersStart")
|
||||
.assertLogMatch("requestHeadersEnd")
|
||||
.assertLogMatch("responseHeadersStart")
|
||||
.assertLogMatch(
|
||||
"responseHeadersEnd: Response\\{protocol=http/1\\.1, code=200, message=OK, url="
|
||||
+ url
|
||||
+ "}")
|
||||
.assertLogMatch("responseBodyStart")
|
||||
.assertLogMatch("responseBodyEnd: byteCount=6")
|
||||
.assertLogMatch("connectionReleased")
|
||||
.assertLogMatch("callEnd")
|
||||
.assertNoMoreLogs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void post() throws IOException {
|
||||
server.enqueue(new MockResponse());
|
||||
client.newCall(request().post(RequestBody.create(PLAIN, "Hello!")).build()).execute();
|
||||
|
||||
logRecorder
|
||||
.assertLogMatch("callStart: Request\\{method=POST, url=" + url + ", tags=\\{\\}\\}")
|
||||
.assertLogMatch("dnsStart: " + url.host())
|
||||
.assertLogMatch("dnsEnd: \\[.+\\]")
|
||||
.assertLogMatch("connectStart: " + url.host() + "/.+ DIRECT")
|
||||
.assertLogMatch("connectEnd: http/1.1")
|
||||
.assertLogMatch(
|
||||
"connectionAcquired: Connection\\{"
|
||||
+ url.host()
|
||||
+ ":\\d+, proxy=DIRECT hostAddress="
|
||||
+ url.host()
|
||||
+ "/.+ cipherSuite=none protocol=http/1\\.1\\}")
|
||||
.assertLogMatch("requestHeadersStart")
|
||||
.assertLogMatch("requestHeadersEnd")
|
||||
.assertLogMatch("requestBodyStart")
|
||||
.assertLogMatch("requestBodyEnd: byteCount=6")
|
||||
.assertLogMatch("responseHeadersStart")
|
||||
.assertLogMatch(
|
||||
"responseHeadersEnd: Response\\{protocol=http/1\\.1, code=200, message=OK, url="
|
||||
+ url
|
||||
+ "}")
|
||||
.assertLogMatch("responseBodyStart")
|
||||
.assertLogMatch("responseBodyEnd: byteCount=0")
|
||||
.assertLogMatch("connectionReleased")
|
||||
.assertLogMatch("callEnd")
|
||||
.assertNoMoreLogs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secureGet() throws Exception {
|
||||
server.useHttps(handshakeCertificates.sslSocketFactory(), false);
|
||||
url = server.url("/");
|
||||
|
||||
server.enqueue(new MockResponse());
|
||||
Response response = client.newCall(request().build()).execute();
|
||||
assertNotNull(response.body());
|
||||
response.body().bytes();
|
||||
|
||||
logRecorder
|
||||
.assertLogMatch("callStart: Request\\{method=GET, url=" + url + ", tags=\\{\\}\\}")
|
||||
.assertLogMatch("dnsStart: " + url.host())
|
||||
.assertLogMatch("dnsEnd: \\[.+\\]")
|
||||
.assertLogMatch("connectStart: " + url.host() + "/.+ DIRECT")
|
||||
.assertLogMatch("secureConnectStart")
|
||||
.assertLogMatch("secureConnectEnd")
|
||||
.assertLogMatch("connectEnd: h2")
|
||||
.assertLogMatch(
|
||||
"connectionAcquired: Connection\\{"
|
||||
+ url.host()
|
||||
+ ":\\d+, proxy=DIRECT hostAddress="
|
||||
+ url.host()
|
||||
+ "/.+ cipherSuite=.+ protocol=h2}")
|
||||
.assertLogMatch("requestHeadersStart")
|
||||
.assertLogMatch("requestHeadersEnd")
|
||||
.assertLogMatch("responseHeadersStart")
|
||||
.assertLogMatch(
|
||||
"responseHeadersEnd: Response\\{protocol=h2, code=200, message=, url=" + url + "}")
|
||||
.assertLogMatch("responseBodyStart")
|
||||
.assertLogMatch("responseBodyEnd: byteCount=0")
|
||||
.assertLogMatch("connectionReleased")
|
||||
.assertLogMatch("callEnd")
|
||||
.assertNoMoreLogs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dnsFail() throws IOException {
|
||||
client =
|
||||
new OkHttpClient.Builder()
|
||||
.dns(
|
||||
new Dns() {
|
||||
@Override
|
||||
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
|
||||
throw new UnknownHostException("reason");
|
||||
}
|
||||
})
|
||||
.eventListenerFactory(loggingEventListenerFactory)
|
||||
.build();
|
||||
|
||||
try {
|
||||
client.newCall(request().build()).execute();
|
||||
fail();
|
||||
} catch (UnknownHostException expected) {
|
||||
}
|
||||
|
||||
logRecorder
|
||||
.assertLogMatch("callStart: Request\\{method=GET, url=" + url + ", tags=\\{\\}\\}")
|
||||
.assertLogMatch("dnsStart: " + url.host())
|
||||
.assertLogMatch("callFailed: java.net.UnknownHostException: reason")
|
||||
.assertNoMoreLogs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectFail() {
|
||||
server.useHttps(handshakeCertificates.sslSocketFactory(), false);
|
||||
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
|
||||
url = server.url("/");
|
||||
|
||||
try {
|
||||
client.newCall(request().build()).execute();
|
||||
fail();
|
||||
} catch (IOException expected) {
|
||||
}
|
||||
|
||||
logRecorder
|
||||
.assertLogMatch("callStart: Request\\{method=GET, url=" + url + ", tags=\\{\\}\\}")
|
||||
.assertLogMatch("dnsStart: " + url.host())
|
||||
.assertLogMatch("dnsEnd: \\[.+\\]")
|
||||
.assertLogMatch("connectStart: " + url.host() + "/.+ DIRECT")
|
||||
.assertLogMatch("secureConnectStart")
|
||||
.assertLogMatch(
|
||||
"connectFailed: null javax\\.net\\.ssl\\.SSLProtocolException: Handshake message sequence violation, 1")
|
||||
.assertLogMatch(
|
||||
"callFailed: javax.net.ssl.SSLProtocolException: Handshake message sequence violation, 1")
|
||||
.assertNoMoreLogs();
|
||||
}
|
||||
|
||||
private Request.Builder request() {
|
||||
return new Request.Builder().url(url);
|
||||
}
|
||||
|
||||
private static class LogRecorder extends HttpLoggingInterceptorTest.LogRecorder {
|
||||
LogRecorder assertLogMatch(String pattern) {
|
||||
return (LogRecorder) super.assertLogMatch("\\[\\d+ ms] " + pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user