Adding changes for logging request and resp

This commit is contained in:
markiantorno 2021-11-08 16:10:04 -05:00
parent 83b1702cc5
commit 7bcd2b6434
15 changed files with 243 additions and 154 deletions

View File

@ -0,0 +1 @@
* Updating client logger to log both req and resp

View File

@ -1,33 +1,33 @@
package org.hl7.fhir.r4.context; package org.hl7.fhir.r4.context;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */

View File

@ -276,15 +276,9 @@ public class FHIRToolingClient {
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) { if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())); byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
if (client.getLogger() != null) {
client.getLogger().logRequest("POST", url.toString(), null, body);
}
result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(), result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); "POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else { } else {
if (client.getLogger() != null) {
client.getLogger().logRequest("GET", url.toString(), null, null);
}
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} }
if (result.isUnsuccessfulRequest()) { if (result.isUnsuccessfulRequest()) {

View File

@ -19,6 +19,7 @@ public class Client {
public static final String DEFAULT_CHARSET = "UTF-8"; public static final String DEFAULT_CHARSET = "UTF-8";
private static final long DEFAULT_TIMEOUT = 5000; private static final long DEFAULT_TIMEOUT = 5000;
private ToolingClientLogger logger; private ToolingClientLogger logger;
private FhirLoggingInterceptor fhirLoggingInterceptor;
private int retryCount; private int retryCount;
private long timeout = DEFAULT_TIMEOUT; private long timeout = DEFAULT_TIMEOUT;
@ -28,6 +29,7 @@ public class Client {
public void setLogger(ToolingClientLogger logger) { public void setLogger(ToolingClientLogger logger) {
this.logger = logger; this.logger = logger;
this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger);
} }
public int getRetryCount() { public int getRetryCount() {
@ -167,7 +169,7 @@ public class Client {
int retryCount, int retryCount,
long timeout) throws IOException { long timeout) throws IOException {
return new FhirRequestBuilder(request) return new FhirRequestBuilder(request)
.withLogger(logger) .withLogger(fhirLoggingInterceptor)
.withResourceFormat(resourceFormat) .withResourceFormat(resourceFormat)
.withRetryCount(retryCount) .withRetryCount(retryCount)
.withMessage(message) .withMessage(message)
@ -183,7 +185,7 @@ public class Client {
int retryCount, int retryCount,
long timeout) throws IOException { long timeout) throws IOException {
return new FhirRequestBuilder(request) return new FhirRequestBuilder(request)
.withLogger(logger) .withLogger(fhirLoggingInterceptor)
.withResourceFormat(resourceFormat) .withResourceFormat(resourceFormat)
.withRetryCount(retryCount) .withRetryCount(retryCount)
.withMessage(message) .withMessage(message)

View File

@ -0,0 +1,55 @@
package org.hl7.fhir.r4.utils.client.network;
import okhttp3.*;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FhirLoggingInterceptor implements Interceptor {
private ToolingClientLogger logger;
public FhirLoggingInterceptor(ToolingClientLogger logger) {
this.logger = logger;
}
public FhirLoggingInterceptor setLogger(ToolingClientLogger logger) {
this.logger = logger;
return this;
}
@NotNull
@Override
public Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
// Log Request
Request request = chain.request();
logger.logRequest(request.method(), request.url().toString(), new ArrayList<>(request.headers().names()),
request.body() != null ? request.body().toString().getBytes() : null);
// Log Response
Response response = null;
response = chain.proceed(chain.request());
MediaType contentType = null;
byte[] bodyBytes = null;
if (response.body() != null) {
contentType = response.body().contentType();
bodyBytes = response.body().bytes();
}
// Get Headers as List
List<String> headerList = new ArrayList<>();
Map<String, List<String>> headerMap = response.headers().toMultimap();
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
logger.logResponse(Integer.toString(response.code()), headerList, bodyBytes);
// Reading byte[] clears body. Need to recreate.
ResponseBody body = ResponseBody.create(bodyBytes, contentType);
return response.newBuilder().body(body).build();
}
}

View File

@ -45,10 +45,11 @@ public class FhirRequestBuilder {
* Time unit for {@link FhirRequestBuilder#timeout}. * Time unit for {@link FhirRequestBuilder#timeout}.
*/ */
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
/** /**
* {@link ToolingClientLogger} for log output. * {@link FhirLoggingInterceptor} for log output.
*/ */
private ToolingClientLogger logger = null; private FhirLoggingInterceptor logger = null;
public FhirRequestBuilder(Request.Builder httpRequest) { public FhirRequestBuilder(Request.Builder httpRequest) {
this.httpRequest = httpRequest; this.httpRequest = httpRequest;
@ -160,7 +161,11 @@ public class FhirRequestBuilder {
.build(); .build();
}; };
return okHttpClient.newBuilder() OkHttpClient.Builder builder = okHttpClient.newBuilder();
if (logger != null) builder.addInterceptor(logger);
builder.addInterceptor(new RetryInterceptor(retryCount));
return builder.connectTimeout(timeout, timeoutUnit)
.addInterceptor(new RetryInterceptor(retryCount)) .addInterceptor(new RetryInterceptor(retryCount))
.connectTimeout(timeout, timeoutUnit) .connectTimeout(timeout, timeoutUnit)
.writeTimeout(timeout, timeoutUnit) .writeTimeout(timeout, timeoutUnit)
@ -189,7 +194,7 @@ public class FhirRequestBuilder {
return this; return this;
} }
public FhirRequestBuilder withLogger(ToolingClientLogger logger) { public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
this.logger = logger; this.logger = logger;
return this; return this;
} }
@ -228,7 +233,6 @@ public class FhirRequestBuilder {
if (response.body() != null) { if (response.body() != null) {
try { try {
byte[] body = response.body().bytes(); byte[] body = response.body().bytes();
log(response.code(), response.headers(), body);
resource = (T) getParser(format).parse(body); resource = (T) getParser(format).parse(body);
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) { if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
error = (OperationOutcome) resource; error = (OperationOutcome) resource;
@ -255,7 +259,6 @@ public class FhirRequestBuilder {
OperationOutcome error = null; OperationOutcome error = null;
try { try {
byte[] body = response.body().bytes(); byte[] body = response.body().bytes();
log(response.code(), response.headers(), body);
String contentType = response.header("Content-Type"); String contentType = response.header("Content-Type");
if (body != null) { if (body != null) {
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) { if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
@ -301,30 +304,4 @@ public class FhirRequestBuilder {
throw new EFhirClientException("Invalid format: " + format); throw new EFhirClientException("Invalid format: " + format);
} }
} }
/**
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
* {@link FhirRequestBuilder#logger} is null, no action is taken.
*
* @param responseCode HTTP response code
* @param responseHeaders {@link Headers} from response
* @param responseBody Byte array response
*/
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
if (logger != null) {
List<String> headerList = new ArrayList<>(Collections.emptyList());
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
try {
logger.logResponse(Integer.toString(responseCode), headerList, responseBody);
} catch (Exception e) {
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
}
}
// else { // TODO fix logs
// System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
// "initialize your logger?");
// }
}
} }

View File

@ -107,6 +107,12 @@
<version>4.9.0</version> <version>4.9.0</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.9.0</version>
<optional>true</optional>
</dependency>
<!-- Test dependencies --> <!-- Test dependencies -->
<dependency> <dependency>

View File

@ -1,33 +1,33 @@
package org.hl7.fhir.r5.context; package org.hl7.fhir.r5.context;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
@ -42,7 +42,7 @@ import org.hl7.fhir.utilities.Utilities;
public class HTMLClientLogger extends BaseLogger implements ToolingClientLogger { public class HTMLClientLogger extends BaseLogger implements ToolingClientLogger {
private static final boolean DEBUG = false; private static final boolean DEBUG = true;
private PrintStream file; private PrintStream file;

View File

@ -308,15 +308,9 @@ public class FHIRToolingClient {
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) { if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())); byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
if (client.getLogger() != null) {
client.getLogger().logRequest("POST", url.toString(), null, body);
}
result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(), result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); "POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else { } else {
if (client.getLogger() != null) {
client.getLogger().logRequest("GET", url.toString(), null, null);
}
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} }
if (result.isUnsuccessfulRequest()) { if (result.isUnsuccessfulRequest()) {

View File

@ -4,6 +4,7 @@ import okhttp3.Headers;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.logging.HttpLoggingInterceptor;
import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.utils.client.EFhirClientException; import org.hl7.fhir.r5.utils.client.EFhirClientException;
@ -19,6 +20,7 @@ public class Client {
public static final String DEFAULT_CHARSET = "UTF-8"; public static final String DEFAULT_CHARSET = "UTF-8";
private static final long DEFAULT_TIMEOUT = 5000; private static final long DEFAULT_TIMEOUT = 5000;
private ToolingClientLogger logger; private ToolingClientLogger logger;
private FhirLoggingInterceptor fhirLoggingInterceptor;
private int retryCount; private int retryCount;
private long timeout = DEFAULT_TIMEOUT; private long timeout = DEFAULT_TIMEOUT;
@ -28,6 +30,7 @@ public class Client {
public void setLogger(ToolingClientLogger logger) { public void setLogger(ToolingClientLogger logger) {
this.logger = logger; this.logger = logger;
this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger);
} }
public int getRetryCount() { public int getRetryCount() {
@ -168,7 +171,7 @@ public class Client {
int retryCount, int retryCount,
long timeout) throws IOException { long timeout) throws IOException {
return new FhirRequestBuilder(request) return new FhirRequestBuilder(request)
.withLogger(logger) .withLogger(fhirLoggingInterceptor)
.withResourceFormat(resourceFormat) .withResourceFormat(resourceFormat)
.withRetryCount(retryCount) .withRetryCount(retryCount)
.withMessage(message) .withMessage(message)
@ -184,7 +187,7 @@ public class Client {
int retryCount, int retryCount,
long timeout) throws IOException { long timeout) throws IOException {
return new FhirRequestBuilder(request) return new FhirRequestBuilder(request)
.withLogger(logger) .withLogger(fhirLoggingInterceptor)
.withResourceFormat(resourceFormat) .withResourceFormat(resourceFormat)
.withRetryCount(retryCount) .withRetryCount(retryCount)
.withMessage(message) .withMessage(message)

View File

@ -0,0 +1,55 @@
package org.hl7.fhir.r5.utils.client.network;
import okhttp3.*;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FhirLoggingInterceptor implements Interceptor {
private ToolingClientLogger logger;
public FhirLoggingInterceptor(ToolingClientLogger logger) {
this.logger = logger;
}
public FhirLoggingInterceptor setLogger(ToolingClientLogger logger) {
this.logger = logger;
return this;
}
@NotNull
@Override
public Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
// Log Request
Request request = chain.request();
logger.logRequest(request.method(), request.url().toString(), new ArrayList<>(request.headers().names()),
request.body() != null ? request.body().toString().getBytes() : null);
// Log Response
Response response = null;
response = chain.proceed(chain.request());
MediaType contentType = null;
byte[] bodyBytes = null;
if (response.body() != null) {
contentType = response.body().contentType();
bodyBytes = response.body().bytes();
}
// Get Headers as List
List<String> headerList = new ArrayList<>();
Map<String, List<String>> headerMap = response.headers().toMultimap();
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
logger.logResponse(Integer.toString(response.code()), headerList, bodyBytes);
// Reading byte[] clears body. Need to recreate.
ResponseBody body = ResponseBody.create(bodyBytes, contentType);
return response.newBuilder().body(body).build();
}
}

View File

@ -45,10 +45,11 @@ public class FhirRequestBuilder {
* Time unit for {@link FhirRequestBuilder#timeout}. * Time unit for {@link FhirRequestBuilder#timeout}.
*/ */
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
/** /**
* {@link ToolingClientLogger} for log output. * {@link FhirLoggingInterceptor} for log output.
*/ */
private ToolingClientLogger logger = null; private FhirLoggingInterceptor logger = null;
public FhirRequestBuilder(Request.Builder httpRequest) { public FhirRequestBuilder(Request.Builder httpRequest) {
this.httpRequest = httpRequest; this.httpRequest = httpRequest;
@ -160,9 +161,11 @@ public class FhirRequestBuilder {
.build(); .build();
}; };
return okHttpClient.newBuilder() OkHttpClient.Builder builder = okHttpClient.newBuilder();
.addInterceptor(new RetryInterceptor(retryCount)) if (logger != null) builder.addInterceptor(logger);
.connectTimeout(timeout, timeoutUnit) builder.addInterceptor(new RetryInterceptor(retryCount));
return builder.connectTimeout(timeout, timeoutUnit)
.writeTimeout(timeout, timeoutUnit) .writeTimeout(timeout, timeoutUnit)
.readTimeout(timeout, timeoutUnit) .readTimeout(timeout, timeoutUnit)
.proxyAuthenticator(proxyAuthenticator) .proxyAuthenticator(proxyAuthenticator)
@ -189,7 +192,7 @@ public class FhirRequestBuilder {
return this; return this;
} }
public FhirRequestBuilder withLogger(ToolingClientLogger logger) { public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
this.logger = logger; this.logger = logger;
return this; return this;
} }
@ -228,7 +231,6 @@ public class FhirRequestBuilder {
if (response.body() != null) { if (response.body() != null) {
try { try {
byte[] body = response.body().bytes(); byte[] body = response.body().bytes();
log(response.code(), response.headers(), body);
resource = (T) getParser(format).parse(body); resource = (T) getParser(format).parse(body);
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) { if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
error = (OperationOutcome) resource; error = (OperationOutcome) resource;
@ -255,7 +257,6 @@ public class FhirRequestBuilder {
OperationOutcome error = null; OperationOutcome error = null;
try { try {
byte[] body = response.body().bytes(); byte[] body = response.body().bytes();
log(response.code(), response.headers(), body);
String contentType = response.header("Content-Type"); String contentType = response.header("Content-Type");
if (body != null) { if (body != null) {
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) { if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
@ -301,30 +302,4 @@ public class FhirRequestBuilder {
throw new EFhirClientException("Invalid format: " + format); throw new EFhirClientException("Invalid format: " + format);
} }
} }
/**
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
* {@link FhirRequestBuilder#logger} is null, no action is taken.
*
* @param responseCode HTTP response code
* @param responseHeaders {@link Headers} from response
* @param responseBody Byte array response
*/
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
if (logger != null) {
List<String> headerList = new ArrayList<>(Collections.emptyList());
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
try {
logger.logResponse(Integer.toString(responseCode), headerList, responseBody);
} catch (Exception e) {
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
}
}
// else { // TODO fix logs
// System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
// "initialize your logger?");
// }
}
} }

View File

@ -4,9 +4,11 @@ import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest; import okhttp3.mockwebserver.RecordedRequest;
import org.hl7.fhir.r5.context.HTMLClientLogger;
import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.*;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.mockito.Mockito;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -143,4 +145,24 @@ class ClientTest {
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(), Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
"POST request payload does not match send data."); "POST request payload does not match send data.");
} }
@Test
@DisplayName("Testing the logger works.")
void test_logger() throws IOException, URISyntaxException, InterruptedException {
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false);
server.enqueue(
new MockResponse()
.setResponseCode(200)
.setBody(new String(payload))
);
HTMLClientLogger mockLogger = Mockito.mock(HTMLClientLogger.class);
client.setLogger(mockLogger);
client.issuePostRequest(new URI(serverUrl.toString()), payload,
"xml", null, TIMEOUT);
server.takeRequest();
Mockito.verify(mockLogger, Mockito.times(1))
.logRequest(Mockito.anyString(), Mockito.anyString(), Mockito.anyList(), Mockito.any());
Mockito.verify(mockLogger, Mockito.times(1))
.logResponse(Mockito.anyString(), Mockito.anyList(), Mockito.any());
}
} }

View File

@ -86,6 +86,12 @@
<version>${validator_test_case_version}</version> <version>${validator_test_case_version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.9.0</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,5 +1,5 @@
package org.hl7.fhir.utilities; package org.hl7.fhir.utilities;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
@ -28,17 +28,16 @@ package org.hl7.fhir.utilities;
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
import java.util.List; import java.util.List;
public interface ToolingClientLogger { public interface ToolingClientLogger {
public void logRequest(String method, String url, List<String> headers, byte[] body); void logRequest(String method, String url, List<String> headers, byte[] body);
public void logResponse(String outcome, List<String> headers, byte[] body); void logResponse(String outcome, List<String> headers, byte[] body);
public String getLastId(); String getLastId();
public void clearLastId(); void clearLastId();
} }