From 588ca4afe84992433616841bea3cb67e20089811 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 8 Oct 2020 15:38:50 -0700 Subject: [PATCH 01/10] Add functions to HttpLogOptions to determine the log level for request and response logging --- .../core/http/policy/HttpLogOptions.java | 77 +++++++++++-- .../core/http/policy/HttpLoggingPolicy.java | 105 +++++++++++++++--- 2 files changed, 157 insertions(+), 25 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index cfded2cd3925..9d15ebb8c405 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -3,15 +3,21 @@ package com.azure.core.http.policy; -import com.azure.core.util.CoreUtils; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; import com.azure.core.util.ClientOptions; +import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; +import com.azure.core.util.logging.LogLevel; +import java.time.Duration; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; /** * The log configurations for HTTP messages. @@ -22,6 +28,8 @@ public class HttpLogOptions { private Set allowedHeaderNames; private Set allowedQueryParamNames; private boolean prettyPrintBody; + private Function requestLogLevelFunction; + private BiFunction responseLogLevelFunction; private final ClientLogger logger = new ClientLogger(HttpLogOptions.class); private static final int MAX_APPLICATION_ID_LENGTH = 24; @@ -96,10 +104,9 @@ public Set getAllowedHeaderNames() { * *

* This method sets the provided header names to be the whitelisted header names which will be logged for all HTTP - * requests and responses, overwriting any previously configured headers, including the default set. Additionally, - * users can use {@link HttpLogOptions#addAllowedHeaderName(String)} or - * {@link HttpLogOptions#getAllowedHeaderNames()} to add or remove more headers names to the existing set of - * allowed header names. + * requests and responses, overwriting any previously configured headers. Additionally, users can use {@link + * HttpLogOptions#addAllowedHeaderName(String)} or {@link HttpLogOptions#getAllowedHeaderNames()} to add or remove + * more headers names to the existing set of allowed header names. *

* * @param allowedHeaderNames The list of whitelisted header names from the user. @@ -202,12 +209,68 @@ public boolean isPrettyPrintBody() { /** * Sets flag to allow pretty printing of message bodies. * - * @param prettyPrintBody If true, pretty prints message bodies when logging. If the detailLevel does not - * include body logging, this flag does nothing. + * @param prettyPrintBody If true, pretty prints message bodies when logging. If the detailLevel does not include + * body logging, this flag does nothing. * @return The updated HttpLogOptions object. */ public HttpLogOptions setPrettyPrintBody(boolean prettyPrintBody) { this.prettyPrintBody = prettyPrintBody; return this; } + + /** + * Gets the {@link Function} used to determine which log level to log the outgoing request. + *

+ * By default {@link LogLevel#INFORMATIONAL} will be used. + * + * @return The {@link Function} used to determine the log level to log the outgoing request. + */ + public Function getRequestLogLevelFunction() { + return requestLogLevelFunction; + } + + /** + * Sets the {@link Function} used to determine which log level to log the outgoing request. + *

+ * By default {@link LogLevel#INFORMATIONAL} will be used. + * + * @param requestLogLevelFunction The {@link Function} used to determine the log level to log the outgoing request. + * @return The updated HttpLogOptions object. + */ + public HttpLogOptions setRequestLogLevelFunction(Function requestLogLevelFunction) { + this.requestLogLevelFunction = requestLogLevelFunction; + return this; + } + + /** + * Gets the {@link BiFunction} used to determine which log level to log the incoming response. + *

+ * The {@link HttpResponse} and the duration taken for a response to be returned will be passed to determine the log + * level. + *

+ * By default {@link LogLevel#INFORMATIONAL} will be used. + * + * @return The {@link BiFunction} used to determine the log level to log the incoming response. + */ + public BiFunction getResponseLogLevelFunction() { + return responseLogLevelFunction; + } + + /** + * Sets the {@link BiFunction} used to determine which log level to log the incoming response. + *

+ * The {@link HttpResponse} and the duration for a response to be returned will be passed to determine the log + * level. + *

+ * By default {@link LogLevel#INFORMATIONAL} will be used. + * + * @param responseLogLevelFunction The {@link BiFunction} used to determine the log level to log the incoming + * response. + * @return The updated HttpLogOptions object. + */ + public HttpLogOptions setResponseLogLevelFunction( + BiFunction responseLogLevelFunction) { + this.responseLogLevelFunction = responseLogLevelFunction; + return this; + } } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index d6e3362852cf..421c02634362 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -26,11 +26,13 @@ import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; +import java.time.Duration; import java.util.Collections; import java.util.Locale; -import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -40,12 +42,19 @@ public class HttpLoggingPolicy implements HttpPipelinePolicy { private static final ObjectMapper PRETTY_PRINTER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); private static final int MAX_BODY_LOG_SIZE = 1024 * 16; private static final String REDACTED_PLACEHOLDER = "REDACTED"; + private static final Function DEFAULT_REQUEST_LOG_LEVEL_FUNCTION = + httpRequest -> LogLevel.INFORMATIONAL; + private static final BiFunction DEFAULT_RESPONSE_LOG_LEVEL_FUNCTION = + ((httpResponse, duration) -> LogLevel.INFORMATIONAL); private final HttpLogDetailLevel httpLogDetailLevel; private final Set allowedHeaderNames; private final Set allowedQueryParameterNames; private final boolean prettyPrintBody; + private final Function requestLogLevelFunction; + private final BiFunction responseLogLevelFunction; + /** * Key for {@link Context} to pass request retry count metadata for logging. */ @@ -62,6 +71,8 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { this.allowedHeaderNames = Collections.emptySet(); this.allowedQueryParameterNames = Collections.emptySet(); this.prettyPrintBody = false; + this.requestLogLevelFunction = DEFAULT_REQUEST_LOG_LEVEL_FUNCTION; + this.responseLogLevelFunction = DEFAULT_RESPONSE_LOG_LEVEL_FUNCTION; } else { this.httpLogDetailLevel = httpLogOptions.getLogLevel(); this.allowedHeaderNames = httpLogOptions.getAllowedHeaderNames() @@ -73,6 +84,12 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { .map(queryParamName -> queryParamName.toLowerCase(Locale.ROOT)) .collect(Collectors.toSet()); this.prettyPrintBody = httpLogOptions.isPrettyPrintBody(); + this.requestLogLevelFunction = (httpLogOptions.getRequestLogLevelFunction() == null) + ? DEFAULT_REQUEST_LOG_LEVEL_FUNCTION + : httpLogOptions.getRequestLogLevelFunction(); + this.responseLogLevelFunction = (httpLogOptions.getResponseLogLevelFunction() == null) + ? DEFAULT_RESPONSE_LOG_LEVEL_FUNCTION + : httpLogOptions.getResponseLogLevelFunction(); } } @@ -86,7 +103,7 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN final ClientLogger logger = new ClientLogger((String) context.getData("caller-method").orElse("")); final long startNs = System.nanoTime(); - return logRequest(logger, context.getHttpRequest(), context.getData(RETRY_COUNT_CONTEXT)) + return logRequest(logger, context) .then(next.process()) .flatMap(response -> logResponse(logger, response, startNs)) .doOnError(throwable -> logger.warning("<-- HTTP FAILED: ", throwable)); @@ -99,10 +116,11 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN * @param request HTTP request being sent to Azure. * @return A Mono which will emit the string to log. */ - private Mono logRequest(final ClientLogger logger, final HttpRequest request, - final Optional optionalRetryCount) { + private Mono logRequest(final ClientLogger logger, final HttpPipelineCallContext callContext) { + final HttpRequest request = callContext.getHttpRequest(); - if (!logger.canLogAtLevel(LogLevel.INFORMATIONAL)) { + final LogLevel logLevel = sanitizeLogLevel(requestLogLevelFunction.apply(request)); + if (!logger.canLogAtLevel(logLevel)) { return Mono.empty(); } @@ -114,15 +132,18 @@ private Mono logRequest(final ClientLogger logger, final HttpRequest reque .append(getRedactedUrl(request.getUrl())) .append(System.lineSeparator()); - optionalRetryCount.ifPresent(o -> requestLogMessage.append("Try count: ") - .append(o) - .append(System.lineSeparator())); + Integer retryCount = getRequestRetryCount(callContext, logger); + if (retryCount != null) { + requestLogMessage.append("Try count: ") + .append(retryCount) + .append(System.lineSeparator()); + } } addHeadersToLogMessage(logger, request.getHeaders(), requestLogMessage); if (!httpLogDetailLevel.shouldLogBody()) { - return logAndReturn(logger, requestLogMessage, null); + return logAndReturn(logger, logLevel, requestLogMessage, null); } if (request.getBody() == null) { @@ -132,7 +153,7 @@ private Mono logRequest(final ClientLogger logger, final HttpRequest reque .append(request.getHttpMethod()) .append(System.lineSeparator()); - return logAndReturn(logger, requestLogMessage, null); + return logAndReturn(logger, logLevel, requestLogMessage, null); } String contentType = request.getHeaders().getValue("Content-Type"); @@ -169,7 +190,7 @@ private Mono logRequest(final ClientLogger logger, final HttpRequest reque .append(request.getHttpMethod()) .append(System.lineSeparator()); - return logAndReturn(logger, requestLogMessage, null); + return logAndReturn(logger, logLevel, requestLogMessage, null); } } @@ -182,12 +203,13 @@ private Mono logRequest(final ClientLogger logger, final HttpRequest reque * @return A Mono containing the HTTP response. */ private Mono logResponse(final ClientLogger logger, final HttpResponse response, long startNs) { - if (!logger.canLogAtLevel(LogLevel.INFORMATIONAL)) { + long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); + final LogLevel logLevel = sanitizeLogLevel(responseLogLevelFunction.apply(response, Duration.ofMillis(tookMs))); + + if (!logger.canLogAtLevel(logLevel)) { return Mono.just(response); } - long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); - String contentLengthString = response.getHeaderValue("Content-Length"); String bodySize = (CoreUtils.isNullOrEmpty(contentLengthString)) ? "unknown-length body" @@ -211,7 +233,7 @@ private Mono logResponse(final ClientLogger logger, final HttpResp if (!httpLogDetailLevel.shouldLogBody()) { responseLogMessage.append("<-- END HTTP"); - return logAndReturn(logger, responseLogMessage, response); + return logAndReturn(logger, logLevel, responseLogMessage, response); } String contentTypeHeader = response.getHeaderValue("Content-Type"); @@ -238,12 +260,32 @@ private Mono logResponse(final ClientLogger logger, final HttpResp .append(System.lineSeparator()) .append("<-- END HTTP"); - return logAndReturn(logger, responseLogMessage, response); + return logAndReturn(logger, logLevel, responseLogMessage, response); } } - private Mono logAndReturn(ClientLogger logger, StringBuilder logMessageBuilder, T data) { - logger.info(logMessageBuilder.toString()); + private Mono logAndReturn(ClientLogger logger, LogLevel logLevel, StringBuilder logMessageBuilder, T data) { + switch (logLevel) { + case VERBOSE: + logger.verbose(logMessageBuilder.toString()); + break; + + case INFORMATIONAL: + logger.info(logMessageBuilder.toString()); + break; + + case WARNING: + logger.warning(logMessageBuilder.toString()); + break; + + case ERROR: + logger.error(logMessageBuilder.toString()); + break; + + default: + break; + } + return Mono.justOrEmpty(data); } @@ -405,4 +447,31 @@ private static Mono writeBufferToBodyStream(WritableByteChannel chan return Mono.error(ex); } } + + /* + * Gets the request retry count to include in logging. + * + * If there is no value set or it isn't a valid number null will be returned indicating that retry count won't be + * logged. + */ + private static Integer getRequestRetryCount(HttpPipelineCallContext callContext, ClientLogger logger) { + Object rawRetryCount = callContext.getData(RETRY_COUNT_CONTEXT).orElse(null); + if (rawRetryCount == null) { + return null; + } + + try { + return Integer.valueOf(rawRetryCount.toString()); + } catch (NumberFormatException ex) { + logger.warning("Could not parse the request retry count: '{}'.", rawRetryCount); + return null; + } + } + + /* + * Sanitizes the log level for logging to INFORMATIONAL if NOT_SET was used. + */ + private static LogLevel sanitizeLogLevel(LogLevel logLevel) { + return (logLevel == LogLevel.NOT_SET) ? LogLevel.INFORMATIONAL : logLevel; + } } From 9e78ca71c650c908cc91bece4db7f508ae601462 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 15 Oct 2020 13:36:34 -0700 Subject: [PATCH 02/10] Added configuration for custom HTTP request and HTTP response logging --- .../core/http/policy/HttpLogOptions.java | 118 +++++++++++++++++- .../core/http/policy/HttpLoggingPolicy.java | 41 +++--- 2 files changed, 133 insertions(+), 26 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index 9d15ebb8c405..8da6fc1dce19 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -3,12 +3,13 @@ package com.azure.core.http.policy; -import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpPipelineCallContext; import com.azure.core.http.HttpResponse; import com.azure.core.util.ClientOptions; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.logging.LogLevel; +import reactor.core.publisher.Mono; import java.time.Duration; import java.util.Arrays; @@ -28,8 +29,13 @@ public class HttpLogOptions { private Set allowedHeaderNames; private Set allowedQueryParamNames; private boolean prettyPrintBody; - private Function requestLogLevelFunction; + + private LogLevel defaultLogLevel; + private Function requestLogLevelFunction; private BiFunction responseLogLevelFunction; + private Function> requestLoggingFunction; + private BiFunction> responseLoggingFunction; + private final ClientLogger logger = new ClientLogger(HttpLogOptions.class); private static final int MAX_APPLICATION_ID_LENGTH = 24; @@ -218,26 +224,56 @@ public HttpLogOptions setPrettyPrintBody(boolean prettyPrintBody) { return this; } + /** + * Gets the {@link LogLevel} used by default when logging requests and responses. + *

+ * Using {@link #setRequestLogLevelFunction(Function)} and {@link #setResponseLogLevelFunction(BiFunction)} can be + * used to set the {@link LogLevel} for each request and response being logged. + *

+ * Byd default {@link LogLevel#INFORMATIONAL} is used. + * + * @return The {@link LogLevel} used by default when logging requests and responses. + */ + public LogLevel getDefaultLogLevel() { + return defaultLogLevel; + } + + /** + * Sets the {@link LogLevel} used by default when logging requests and responses. + * + * @param defaultLogLevel The default log level. + * @return The updated HttpLogOptions object. + */ + public HttpLogOptions setDefaultLogLevel(LogLevel defaultLogLevel) { + this.defaultLogLevel = defaultLogLevel; + return this; + } + /** * Gets the {@link Function} used to determine which log level to log the outgoing request. *

+ * The {@link HttpPipelineCallContext} will be passed to determine the log level. + *

* By default {@link LogLevel#INFORMATIONAL} will be used. * * @return The {@link Function} used to determine the log level to log the outgoing request. */ - public Function getRequestLogLevelFunction() { + public Function getRequestLogLevelFunction() { return requestLogLevelFunction; } /** * Sets the {@link Function} used to determine which log level to log the outgoing request. *

+ * The {@link HttpPipelineCallContext} will be passed to determine the log level. + *

* By default {@link LogLevel#INFORMATIONAL} will be used. * * @param requestLogLevelFunction The {@link Function} used to determine the log level to log the outgoing request. * @return The updated HttpLogOptions object. */ - public HttpLogOptions setRequestLogLevelFunction(Function requestLogLevelFunction) { + public HttpLogOptions setRequestLogLevelFunction( + Function requestLogLevelFunction) { this.requestLogLevelFunction = requestLogLevelFunction; return this; } @@ -273,4 +309,78 @@ public HttpLogOptions setResponseLogLevelFunction( this.responseLogLevelFunction = responseLogLevelFunction; return this; } + + /** + * Gets the {@link Function} used to create a log message from a request. + *

+ * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or + * {@link #getRequestLogLevelFunction()} before calling this function. This will prevent log messages being created + * when they won't be logged. + *

+ * The {@link HttpPipelineCallContext} will be passed to generate the log message. + *

+ * A default logging function will be used if one isn't supplied. + * + * @return The {@link Function} used to create a log message from a request. + */ + public Function> getRequestLoggingFunction() { + return requestLoggingFunction; + } + + /** + * Sets the {@link Function} used to create a log message from a request. + *

+ * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or + * {@link #getRequestLogLevelFunction()} before calling this function. This will prevent log messages being created + * when they won't be logged. + *

+ * The {@link HttpPipelineCallContext} will be passed to generate the log message. + *

+ * A default logging function will be used if one isn't supplied. + * + * @param requestLoggingFunction The {@link Function} used to create a log message from a request. + * @return The updated HttpLoggingOptions object. + */ + public HttpLogOptions setRequestLoggingFunction( + Function> requestLoggingFunction) { + this.requestLoggingFunction = requestLoggingFunction; + return this; + } + + /** + * Gets the {@link BiFunction} used to create a log message from a response. + *

+ * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or + * {@link #getResponseLogLevelFunction()} before calling this function. This will prevent log messages being created + * when they won't be logged. + *

+ * The {@link HttpResponse} and duration for a response will be passed to generate the log message. + *

+ * A default logging function will be used if one isn't supplied. + * + * @return The {@link BiFunction} used to create a log message from a response. + */ + public BiFunction> getResponseLoggingFunction() { + return responseLoggingFunction; + } + + /** + * Sets the {@link BiFunction} used to create a log message from a response. + *

+ * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or + * {@link #getResponseLogLevelFunction()} before calling this function. This will prevent log messages being created + * when they won't be logged. + *

+ * The {@link HttpResponse} and duration for a response will be passed to generate the log message. + *

+ * A default logging function will be used if one isn't supplied. + * + * @param responseLoggingFunction The {@link BiFunction} used to create a log message from a response. + * @return The updated HttpLoggingOptions object. + */ + public HttpLogOptions setResponseLoggingFunction( + BiFunction> responseLoggingFunction) { + this.responseLoggingFunction = responseLoggingFunction; + return this; + } } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index 421c02634362..73852d6e3af5 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -42,18 +42,17 @@ public class HttpLoggingPolicy implements HttpPipelinePolicy { private static final ObjectMapper PRETTY_PRINTER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); private static final int MAX_BODY_LOG_SIZE = 1024 * 16; private static final String REDACTED_PLACEHOLDER = "REDACTED"; - private static final Function DEFAULT_REQUEST_LOG_LEVEL_FUNCTION = - httpRequest -> LogLevel.INFORMATIONAL; - private static final BiFunction DEFAULT_RESPONSE_LOG_LEVEL_FUNCTION = - ((httpResponse, duration) -> LogLevel.INFORMATIONAL); private final HttpLogDetailLevel httpLogDetailLevel; private final Set allowedHeaderNames; private final Set allowedQueryParameterNames; private final boolean prettyPrintBody; - private final Function requestLogLevelFunction; + private final LogLevel defaultLogLevel; + private final Function requestLogLevelFunction; private final BiFunction responseLogLevelFunction; + private final Function> requestLoggingFunction; + private final BiFunction> responseLoggingFunction; /** * Key for {@link Context} to pass request retry count metadata for logging. @@ -71,8 +70,10 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { this.allowedHeaderNames = Collections.emptySet(); this.allowedQueryParameterNames = Collections.emptySet(); this.prettyPrintBody = false; - this.requestLogLevelFunction = DEFAULT_REQUEST_LOG_LEVEL_FUNCTION; - this.responseLogLevelFunction = DEFAULT_RESPONSE_LOG_LEVEL_FUNCTION; + + this.defaultLogLevel = LogLevel.INFORMATIONAL; + this.requestLogLevelFunction = null; + this.responseLogLevelFunction = null; } else { this.httpLogDetailLevel = httpLogOptions.getLogLevel(); this.allowedHeaderNames = httpLogOptions.getAllowedHeaderNames() @@ -84,12 +85,12 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { .map(queryParamName -> queryParamName.toLowerCase(Locale.ROOT)) .collect(Collectors.toSet()); this.prettyPrintBody = httpLogOptions.isPrettyPrintBody(); - this.requestLogLevelFunction = (httpLogOptions.getRequestLogLevelFunction() == null) - ? DEFAULT_REQUEST_LOG_LEVEL_FUNCTION - : httpLogOptions.getRequestLogLevelFunction(); - this.responseLogLevelFunction = (httpLogOptions.getResponseLogLevelFunction() == null) - ? DEFAULT_RESPONSE_LOG_LEVEL_FUNCTION - : httpLogOptions.getResponseLogLevelFunction(); + + this.defaultLogLevel = (httpLogOptions.getDefaultLogLevel() == null) + ? LogLevel.INFORMATIONAL + : httpLogOptions.getDefaultLogLevel(); + this.requestLogLevelFunction = httpLogOptions.getRequestLogLevelFunction(); + this.responseLogLevelFunction = httpLogOptions.getResponseLogLevelFunction(); } } @@ -116,14 +117,9 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN * @param request HTTP request being sent to Azure. * @return A Mono which will emit the string to log. */ - private Mono logRequest(final ClientLogger logger, final HttpPipelineCallContext callContext) { + private Mono logRequest(final HttpPipelineCallContext callContext) { final HttpRequest request = callContext.getHttpRequest(); - final LogLevel logLevel = sanitizeLogLevel(requestLogLevelFunction.apply(request)); - if (!logger.canLogAtLevel(logLevel)) { - return Mono.empty(); - } - StringBuilder requestLogMessage = new StringBuilder(); if (httpLogDetailLevel.shouldLogUrl()) { requestLogMessage.append("--> ") @@ -143,7 +139,7 @@ private Mono logRequest(final ClientLogger logger, final HttpPipelineCallC addHeadersToLogMessage(logger, request.getHeaders(), requestLogMessage); if (!httpLogDetailLevel.shouldLogBody()) { - return logAndReturn(logger, logLevel, requestLogMessage, null); + return Mono.just(requestLogMessage.toString()); } if (request.getBody() == null) { @@ -153,7 +149,7 @@ private Mono logRequest(final ClientLogger logger, final HttpPipelineCallC .append(request.getHttpMethod()) .append(System.lineSeparator()); - return logAndReturn(logger, logLevel, requestLogMessage, null); + return Mono.just(requestLogMessage.toString()); } String contentType = request.getHeaders().getValue("Content-Type"); @@ -179,6 +175,7 @@ private Mono logRequest(final ClientLogger logger, final HttpPipelineCallC .append(System.lineSeparator()); logger.info(requestLogMessage.toString()); + })); return Mono.empty(); @@ -190,7 +187,7 @@ private Mono logRequest(final ClientLogger logger, final HttpPipelineCallC .append(request.getHttpMethod()) .append(System.lineSeparator()); - return logAndReturn(logger, logLevel, requestLogMessage, null); + return Mono.just(requestLogMessage.toString()); } } From e3b6b3fec9591d310f103295a3d3eb1069f1cc5a Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Mon, 19 Oct 2020 14:34:31 -0700 Subject: [PATCH 03/10] Add HttpRequestLogger and HttpResponseLogger interfaces which allows for custom logging strategies to be passed into HttpLoggingPolicy --- .../core/http/policy/HttpLogOptions.java | 226 ++++++------ .../core/http/policy/HttpLoggingPolicy.java | 328 +++++++++--------- .../core/http/policy/HttpRequestLogger.java | 33 ++ .../core/http/policy/HttpResponseLogger.java | 38 ++ .../http/BufferedHttpResponse.java | 14 +- 5 files changed, 366 insertions(+), 273 deletions(-) create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index 8da6fc1dce19..f764dd2dfa69 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -9,7 +9,6 @@ import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.logging.LogLevel; -import reactor.core.publisher.Mono; import java.time.Duration; import java.util.Arrays; @@ -17,8 +16,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Function; /** * The log configurations for HTTP messages. @@ -31,10 +28,8 @@ public class HttpLogOptions { private boolean prettyPrintBody; private LogLevel defaultLogLevel; - private Function requestLogLevelFunction; - private BiFunction responseLogLevelFunction; - private Function> requestLoggingFunction; - private BiFunction> responseLoggingFunction; + private HttpRequestLogger requestLogger; + private HttpResponseLogger responseLogger; private final ClientLogger logger = new ClientLogger(HttpLogOptions.class); @@ -227,10 +222,11 @@ public HttpLogOptions setPrettyPrintBody(boolean prettyPrintBody) { /** * Gets the {@link LogLevel} used by default when logging requests and responses. *

- * Using {@link #setRequestLogLevelFunction(Function)} and {@link #setResponseLogLevelFunction(BiFunction)} can be - * used to set the {@link LogLevel} for each request and response being logged. + * {@link HttpRequestLogger#getLogLevel(LogLevel, HttpPipelineCallContext)} and {@link + * HttpResponseLogger#getLogLevel(LogLevel, HttpResponse, Duration)} can be used to set the {@link LogLevel} for + * each request and response being logged. *

- * Byd default {@link LogLevel#INFORMATIONAL} is used. + * By default {@link LogLevel#INFORMATIONAL} is used. * * @return The {@link LogLevel} used by default when logging requests and responses. */ @@ -240,6 +236,12 @@ public LogLevel getDefaultLogLevel() { /** * Sets the {@link LogLevel} used by default when logging requests and responses. + *

+ * {@link HttpRequestLogger#getLogLevel(LogLevel, HttpPipelineCallContext)} and {@link + * HttpResponseLogger#getLogLevel(LogLevel, HttpResponse, Duration)} can be used to set the {@link LogLevel} for + * each request and response being logged. + *

+ * By default {@link LogLevel#INFORMATIONAL} is used. * * @param defaultLogLevel The default log level. * @return The updated HttpLogOptions object. @@ -250,137 +252,147 @@ public HttpLogOptions setDefaultLogLevel(LogLevel defaultLogLevel) { } /** - * Gets the {@link Function} used to determine which log level to log the outgoing request. - *

- * The {@link HttpPipelineCallContext} will be passed to determine the log level. + * Gets the {@link HttpRequestLogger} that will be used to log requests. *

- * By default {@link LogLevel#INFORMATIONAL} will be used. + * A default logger will be used if one isn't supplied. * - * @return The {@link Function} used to determine the log level to log the outgoing request. + * @return The {@link HttpRequestLogger} that will be used to log requests. */ - public Function getRequestLogLevelFunction() { - return requestLogLevelFunction; + public HttpRequestLogger getRequestLogger() { + return requestLogger; } /** - * Sets the {@link Function} used to determine which log level to log the outgoing request. + * Sets the {@link HttpRequestLogger} that will be used to log requests. *

- * The {@link HttpPipelineCallContext} will be passed to determine the log level. - *

- * By default {@link LogLevel#INFORMATIONAL} will be used. + * A default logger will be used if one isn't supplied. * - * @param requestLogLevelFunction The {@link Function} used to determine the log level to log the outgoing request. + * @param requestLogger The {@link HttpRequestLogger} that will be used to log requests. * @return The updated HttpLogOptions object. */ - public HttpLogOptions setRequestLogLevelFunction( - Function requestLogLevelFunction) { - this.requestLogLevelFunction = requestLogLevelFunction; + public HttpLogOptions setRequestLogger(HttpRequestLogger requestLogger) { + this.requestLogger = requestLogger; return this; } /** - * Gets the {@link BiFunction} used to determine which log level to log the incoming response. - *

- * The {@link HttpResponse} and the duration taken for a response to be returned will be passed to determine the log - * level. + * Gets the {@link HttpResponseLogger} that will be used to log responses. *

- * By default {@link LogLevel#INFORMATIONAL} will be used. + * A default logger will be used if one isn't supplied. * - * @return The {@link BiFunction} used to determine the log level to log the incoming response. + * @return The {@link HttpResponseLogger} that will be used to log responses. */ - public BiFunction getResponseLogLevelFunction() { - return responseLogLevelFunction; + public HttpResponseLogger getResponseLogger() { + return responseLogger; } /** - * Sets the {@link BiFunction} used to determine which log level to log the incoming response. - *

- * The {@link HttpResponse} and the duration for a response to be returned will be passed to determine the log - * level. + * Sets the {@link HttpResponseLogger} that will be used to log responses. *

- * By default {@link LogLevel#INFORMATIONAL} will be used. + * A default logger will be sued if one isn't supplied. * - * @param responseLogLevelFunction The {@link BiFunction} used to determine the log level to log the incoming - * response. + * @param responseLogger The {@link HttpResponseLogger} that will be used to log responses. * @return The updated HttpLogOptions object. */ - public HttpLogOptions setResponseLogLevelFunction( - BiFunction responseLogLevelFunction) { - this.responseLogLevelFunction = responseLogLevelFunction; + public HttpLogOptions setResponseLogger(HttpResponseLogger responseLogger) { + this.responseLogger = responseLogger; return this; } /** - * Gets the {@link Function} used to create a log message from a request. - *

- * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or - * {@link #getRequestLogLevelFunction()} before calling this function. This will prevent log messages being created - * when they won't be logged. - *

- * The {@link HttpPipelineCallContext} will be passed to generate the log message. - *

- * A default logging function will be used if one isn't supplied. - * - * @return The {@link Function} used to create a log message from a request. + * Options passed into HTTP request logging functions. */ - public Function> getRequestLoggingFunction() { - return requestLoggingFunction; - } + public static final class HttpRequestLoggingOptions { + private final ClientLogger logger; + private final LogLevel logLevel; + private final HttpPipelineCallContext httpPipelineCallContext; - /** - * Sets the {@link Function} used to create a log message from a request. - *

- * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or - * {@link #getRequestLogLevelFunction()} before calling this function. This will prevent log messages being created - * when they won't be logged. - *

- * The {@link HttpPipelineCallContext} will be passed to generate the log message. - *

- * A default logging function will be used if one isn't supplied. - * - * @param requestLoggingFunction The {@link Function} used to create a log message from a request. - * @return The updated HttpLoggingOptions object. - */ - public HttpLogOptions setRequestLoggingFunction( - Function> requestLoggingFunction) { - this.requestLoggingFunction = requestLoggingFunction; - return this; - } + HttpRequestLoggingOptions(ClientLogger logger, LogLevel logLevel, + HttpPipelineCallContext httpPipelineCallContext) { + this.logger = logger; + this.logLevel = logLevel; + this.httpPipelineCallContext = httpPipelineCallContext; + } - /** - * Gets the {@link BiFunction} used to create a log message from a response. - *

- * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or - * {@link #getResponseLogLevelFunction()} before calling this function. This will prevent log messages being created - * when they won't be logged. - *

- * The {@link HttpResponse} and duration for a response will be passed to generate the log message. - *

- * A default logging function will be used if one isn't supplied. - * - * @return The {@link BiFunction} used to create a log message from a response. - */ - public BiFunction> getResponseLoggingFunction() { - return responseLoggingFunction; + /** + * The {@link ClientLogger} used to log the request. + * + * @return The ClientLogger used to log the request. + */ + public ClientLogger getLogger() { + return logger; + } + + /** + * The {@link LogLevel} used when logging the request. + * + * @return The LogLevel used when logging the request. + */ + public LogLevel getLogLevel() { + return logLevel; + } + + /** + * The contextual information for the request such as headers, body, and metadata. + * + * @return The contextual information for the request. + */ + public HttpPipelineCallContext getHttpPipelineCallContext() { + return httpPipelineCallContext; + } } /** - * Sets the {@link BiFunction} used to create a log message from a response. - *

- * The logger will validate that it can log at the level returned by either {@link #getDefaultLogLevel()} or - * {@link #getResponseLogLevelFunction()} before calling this function. This will prevent log messages being created - * when they won't be logged. - *

- * The {@link HttpResponse} and duration for a response will be passed to generate the log message. - *

- * A default logging function will be used if one isn't supplied. - * - * @param responseLoggingFunction The {@link BiFunction} used to create a log message from a response. - * @return The updated HttpLoggingOptions object. + * Options passed into HTTP response logging functions. */ - public HttpLogOptions setResponseLoggingFunction( - BiFunction> responseLoggingFunction) { - this.responseLoggingFunction = responseLoggingFunction; - return this; + public static final class HttpResponseLoggingOptions { + private final ClientLogger logger; + private final LogLevel logLevel; + private final HttpResponse httpResponse; + private final Duration httpResponseDuration; + + HttpResponseLoggingOptions(ClientLogger logger, LogLevel logLevel, HttpResponse httpResponse, + Duration httpResponseDuration) { + this.logger = logger; + this.logLevel = logLevel; + this.httpResponse = httpResponse; + this.httpResponseDuration = httpResponseDuration; + } + + /** + * The {@link ClientLogger} used to log the response. + * + * @return The ClientLogger used to log the response. + */ + public ClientLogger getLogger() { + return logger; + } + + /** + * The {@link LogLevel} used when logging the response. + * + * @return The LogLevel used when logging the response. + */ + public LogLevel getLogLevel() { + return logLevel; + } + + /** + * The HTTP response being logged. + * + * @return The HTTP response being logged. + */ + public HttpResponse getHttpResponse() { + return httpResponse; + } + + /** + * The duration of time between sending the request and receiving the response. + * + * @return The duration of time between sending the request and receiving the response. + */ + public Duration getHttpResponseDuration() { + return httpResponseDuration; + } } } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index 73852d6e3af5..a279fa699f76 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -30,9 +30,6 @@ import java.util.Collections; import java.util.Locale; import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -49,10 +46,8 @@ public class HttpLoggingPolicy implements HttpPipelinePolicy { private final boolean prettyPrintBody; private final LogLevel defaultLogLevel; - private final Function requestLogLevelFunction; - private final BiFunction responseLogLevelFunction; - private final Function> requestLoggingFunction; - private final BiFunction> responseLoggingFunction; + private final HttpRequestLogger requestLogger; + private final HttpResponseLogger responseLogger; /** * Key for {@link Context} to pass request retry count metadata for logging. @@ -72,8 +67,8 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { this.prettyPrintBody = false; this.defaultLogLevel = LogLevel.INFORMATIONAL; - this.requestLogLevelFunction = null; - this.responseLogLevelFunction = null; + this.requestLogger = new DefaultHttpRequestLogger(); + this.responseLogger = new DefaultHttpResponseLogger(); } else { this.httpLogDetailLevel = httpLogOptions.getLogLevel(); this.allowedHeaderNames = httpLogOptions.getAllowedHeaderNames() @@ -89,8 +84,12 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { this.defaultLogLevel = (httpLogOptions.getDefaultLogLevel() == null) ? LogLevel.INFORMATIONAL : httpLogOptions.getDefaultLogLevel(); - this.requestLogLevelFunction = httpLogOptions.getRequestLogLevelFunction(); - this.responseLogLevelFunction = httpLogOptions.getResponseLogLevelFunction(); + this.requestLogger = (httpLogOptions.getRequestLogger() == null) + ? new DefaultHttpRequestLogger() + : httpLogOptions.getRequestLogger(); + this.responseLogger = (httpLogOptions.getResponseLogger() == null) + ? new DefaultHttpResponseLogger() + : httpLogOptions.getResponseLogger(); } } @@ -104,164 +103,182 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN final ClientLogger logger = new ClientLogger((String) context.getData("caller-method").orElse("")); final long startNs = System.nanoTime(); - return logRequest(logger, context) + return logRequest(logger, defaultLogLevel, requestLogger, context) .then(next.process()) - .flatMap(response -> logResponse(logger, response, startNs)) + .flatMap(response -> logResponse(logger, defaultLogLevel, responseLogger, response, + Duration.ofNanos(System.nanoTime() - startNs))) .doOnError(throwable -> logger.warning("<-- HTTP FAILED: ", throwable)); } - /* - * Logs the HTTP request. - * - * @param logger Logger used to log the request. - * @param request HTTP request being sent to Azure. - * @return A Mono which will emit the string to log. - */ - private Mono logRequest(final HttpPipelineCallContext callContext) { - final HttpRequest request = callContext.getHttpRequest(); - - StringBuilder requestLogMessage = new StringBuilder(); - if (httpLogDetailLevel.shouldLogUrl()) { - requestLogMessage.append("--> ") - .append(request.getHttpMethod()) - .append(" ") - .append(getRedactedUrl(request.getUrl())) - .append(System.lineSeparator()); - - Integer retryCount = getRequestRetryCount(callContext, logger); - if (retryCount != null) { - requestLogMessage.append("Try count: ") - .append(retryCount) - .append(System.lineSeparator()); - } - } + private static Mono logRequest(ClientLogger clientLogger, LogLevel defaultLogLevel, + HttpRequestLogger requestLogger, HttpPipelineCallContext callContext) { + LogLevel logLevel = requestLogger.getLogLevel(defaultLogLevel, callContext); - addHeadersToLogMessage(logger, request.getHeaders(), requestLogMessage); + return (logLevel == LogLevel.NOT_SET || !clientLogger.canLogAtLevel(logLevel)) + ? Mono.empty() + : requestLogger.logRequest(clientLogger, logLevel, callContext); + } - if (!httpLogDetailLevel.shouldLogBody()) { - return Mono.just(requestLogMessage.toString()); - } + private static Mono logResponse(ClientLogger clientLogger, LogLevel defaultLogLevel, + HttpResponseLogger responseLogger, HttpResponse httpResponse, Duration httpResponseDuration) { + LogLevel logLevel = responseLogger.getLogLevel(defaultLogLevel, httpResponse, httpResponseDuration); - if (request.getBody() == null) { - requestLogMessage.append("(empty body)") - .append(System.lineSeparator()) - .append("--> END ") - .append(request.getHttpMethod()) - .append(System.lineSeparator()); + return (logLevel == LogLevel.NOT_SET || !clientLogger.canLogAtLevel(logLevel)) + ? Mono.empty() + : responseLogger.logResponse(clientLogger, logLevel, httpResponse, httpResponseDuration); + } - return Mono.just(requestLogMessage.toString()); + private final class DefaultHttpRequestLogger implements HttpRequestLogger { + @Override + public LogLevel getLogLevel(LogLevel defaultLogLevel, HttpPipelineCallContext callContext) { + return (defaultLogLevel == null) ? LogLevel.INFORMATIONAL : defaultLogLevel; } - String contentType = request.getHeaders().getValue("Content-Type"); - long contentLength = getContentLength(logger, request.getHeaders()); + @Override + public Mono logRequest(ClientLogger logger, LogLevel logLevel, HttpPipelineCallContext callContext) { + final HttpRequest request = callContext.getHttpRequest(); - if (shouldBodyBeLogged(contentType, contentLength)) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) contentLength); - WritableByteChannel bodyContentChannel = Channels.newChannel(outputStream); + StringBuilder requestLogMessage = new StringBuilder(); + if (httpLogDetailLevel.shouldLogUrl()) { + requestLogMessage.append("--> ") + .append(request.getHttpMethod()) + .append(" ") + .append(getRedactedUrl(request.getUrl(), allowedQueryParameterNames)) + .append(System.lineSeparator()); - // Add non-mutating operators to the data stream. - request.setBody( - request.getBody() - .flatMap(byteBuffer -> writeBufferToBodyStream(bodyContentChannel, byteBuffer)) - .doFinally(ignored -> { - requestLogMessage.append(contentLength) - .append("-byte body:") - .append(System.lineSeparator()) - .append(prettyPrintIfNeeded(logger, contentType, - convertStreamToString(outputStream, logger))) - .append(System.lineSeparator()) - .append("--> END ") - .append(request.getHttpMethod()) - .append(System.lineSeparator()); + Integer retryCount = getRequestRetryCount(callContext, logger); + if (retryCount != null) { + requestLogMessage.append("Try count: ") + .append(retryCount) + .append(System.lineSeparator()); + } + } - logger.info(requestLogMessage.toString()); + if (httpLogDetailLevel.shouldLogHeaders() && logger.canLogAtLevel(LogLevel.VERBOSE)) { + addHeadersToLogMessage(allowedHeaderNames, request.getHeaders(), requestLogMessage); + } - })); + if (!httpLogDetailLevel.shouldLogBody()) { + return logAndReturn(logger, logLevel, requestLogMessage, null); + } - return Mono.empty(); - } else { - requestLogMessage.append(contentLength) - .append("-byte body: (content not logged)") - .append(System.lineSeparator()) - .append("--> END ") - .append(request.getHttpMethod()) - .append(System.lineSeparator()); - - return Mono.just(requestLogMessage.toString()); - } - } + if (request.getBody() == null) { + requestLogMessage.append("(empty body)") + .append(System.lineSeparator()) + .append("--> END ") + .append(request.getHttpMethod()) + .append(System.lineSeparator()); - /* - * Logs thr HTTP response. - * - * @param logger Logger used to log the response. - * @param response HTTP response returned from Azure. - * @param startNs Nanosecond representation of when the request was sent. - * @return A Mono containing the HTTP response. - */ - private Mono logResponse(final ClientLogger logger, final HttpResponse response, long startNs) { - long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); - final LogLevel logLevel = sanitizeLogLevel(responseLogLevelFunction.apply(response, Duration.ofMillis(tookMs))); + return logAndReturn(logger, logLevel, requestLogMessage, null); + } - if (!logger.canLogAtLevel(logLevel)) { - return Mono.just(response); + String contentType = request.getHeaders().getValue("Content-Type"); + long contentLength = getContentLength(logger, request.getHeaders()); + + if (shouldBodyBeLogged(contentType, contentLength)) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) contentLength); + WritableByteChannel bodyContentChannel = Channels.newChannel(outputStream); + + // Add non-mutating operators to the data stream. + request.setBody( + request.getBody() + .flatMap(byteBuffer -> writeBufferToBodyStream(bodyContentChannel, byteBuffer)) + .doFinally(ignored -> { + requestLogMessage.append(contentLength) + .append("-byte body:") + .append(System.lineSeparator()) + .append(prettyPrintIfNeeded(logger, prettyPrintBody, contentType, + convertStreamToString(outputStream, logger))) + .append(System.lineSeparator()) + .append("--> END ") + .append(request.getHttpMethod()) + .append(System.lineSeparator()); + + logAndReturn(logger, logLevel, requestLogMessage, null); + })); + + return Mono.empty(); + } else { + requestLogMessage.append(contentLength) + .append("-byte body: (content not logged)") + .append(System.lineSeparator()) + .append("--> END ") + .append(request.getHttpMethod()) + .append(System.lineSeparator()); + + return logAndReturn(logger, logLevel, requestLogMessage, null); + } } + } - String contentLengthString = response.getHeaderValue("Content-Length"); - String bodySize = (CoreUtils.isNullOrEmpty(contentLengthString)) - ? "unknown-length body" - : contentLengthString + "-byte body"; - - StringBuilder responseLogMessage = new StringBuilder(); - if (httpLogDetailLevel.shouldLogUrl()) { - responseLogMessage.append("<-- ") - .append(response.getStatusCode()) - .append(" ") - .append(getRedactedUrl(response.getRequest().getUrl())) - .append(" (") - .append(tookMs) - .append(" ms, ") - .append(bodySize) - .append(")") - .append(System.lineSeparator()); + private final class DefaultHttpResponseLogger implements HttpResponseLogger { + @Override + public LogLevel getLogLevel(LogLevel defaultLogLevel, HttpResponse response, Duration responseDuration) { + return (defaultLogLevel == null) ? LogLevel.INFORMATIONAL : defaultLogLevel; } - addHeadersToLogMessage(logger, response.getHeaders(), responseLogMessage); + @Override + public Mono logResponse(ClientLogger logger, LogLevel logLevel, HttpResponse response, + Duration responseDuration) { + String contentLengthString = response.getHeaderValue("Content-Length"); + String bodySize = (CoreUtils.isNullOrEmpty(contentLengthString)) + ? "unknown-length body" + : contentLengthString + "-byte body"; + + StringBuilder responseLogMessage = new StringBuilder(); + if (httpLogDetailLevel.shouldLogUrl()) { + responseLogMessage.append("<-- ") + .append(response.getStatusCode()) + .append(" ") + .append(getRedactedUrl(response.getRequest().getUrl(), allowedQueryParameterNames)) + .append(" (") + .append(responseDuration.toMillis()) + .append(" ms, ") + .append(bodySize) + .append(")") + .append(System.lineSeparator()); + } + + if (httpLogDetailLevel.shouldLogHeaders() && logger.canLogAtLevel(LogLevel.VERBOSE)) { + addHeadersToLogMessage(allowedHeaderNames, response.getHeaders(), responseLogMessage); + } - if (!httpLogDetailLevel.shouldLogBody()) { - responseLogMessage.append("<-- END HTTP"); - return logAndReturn(logger, logLevel, responseLogMessage, response); - } + if (!httpLogDetailLevel.shouldLogBody()) { + responseLogMessage.append("<-- END HTTP"); + return logAndReturn(logger, logLevel, responseLogMessage, response); + } - String contentTypeHeader = response.getHeaderValue("Content-Type"); - long contentLength = getContentLength(logger, response.getHeaders()); - - if (shouldBodyBeLogged(contentTypeHeader, contentLength)) { - HttpResponse bufferedResponse = response.buffer(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) contentLength); - WritableByteChannel bodyContentChannel = Channels.newChannel(outputStream); - return bufferedResponse.getBody() - .flatMap(byteBuffer -> writeBufferToBodyStream(bodyContentChannel, byteBuffer)) - .doFinally(ignored -> { - responseLogMessage.append("Response body:") - .append(System.lineSeparator()) - .append(prettyPrintIfNeeded(logger, contentTypeHeader, - convertStreamToString(outputStream, logger))) - .append(System.lineSeparator()) - .append("<-- END HTTP"); - - logger.info(responseLogMessage.toString()); - }).then(Mono.just(bufferedResponse)); - } else { - responseLogMessage.append("(body content not logged)") - .append(System.lineSeparator()) - .append("<-- END HTTP"); + String contentTypeHeader = response.getHeaderValue("Content-Type"); + long contentLength = getContentLength(logger, response.getHeaders()); - return logAndReturn(logger, logLevel, responseLogMessage, response); + if (shouldBodyBeLogged(contentTypeHeader, contentLength)) { + HttpResponse bufferedResponse = response.buffer(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream((int) contentLength); + WritableByteChannel bodyContentChannel = Channels.newChannel(outputStream); + return bufferedResponse.getBody() + .flatMap(byteBuffer -> writeBufferToBodyStream(bodyContentChannel, byteBuffer)) + .doFinally(ignored -> { + responseLogMessage.append("Response body:") + .append(System.lineSeparator()) + .append(prettyPrintIfNeeded(logger, prettyPrintBody, contentTypeHeader, + convertStreamToString(outputStream, logger))) + .append(System.lineSeparator()) + .append("<-- END HTTP"); + + logAndReturn(logger, logLevel, responseLogMessage, response); + }).then(Mono.just(bufferedResponse)); + } else { + responseLogMessage.append("(body content not logged)") + .append(System.lineSeparator()) + .append("<-- END HTTP"); + + return logAndReturn(logger, logLevel, responseLogMessage, response); + } } } - private Mono logAndReturn(ClientLogger logger, LogLevel logLevel, StringBuilder logMessageBuilder, T data) { + private static Mono logAndReturn(ClientLogger logger, LogLevel logLevel, StringBuilder logMessageBuilder, + T data) { switch (logLevel) { case VERBOSE: logger.verbose(logMessageBuilder.toString()); @@ -292,9 +309,9 @@ private Mono logAndReturn(ClientLogger logger, LogLevel logLevel, StringB * @param url URL where the request is being sent. * @return A URL with query parameters redacted based on configurations in this policy. */ - private String getRedactedUrl(URL url) { + private static String getRedactedUrl(URL url, Set allowedQueryParameterNames) { return UrlBuilder.parse(url) - .setQuery(getAllowedQueryString(url.getQuery())) + .setQuery(getAllowedQueryString(url.getQuery(), allowedQueryParameterNames)) .toString(); } @@ -304,7 +321,7 @@ private String getRedactedUrl(URL url) { * @param queryString Query parameter string from the request URL. * @return A query parameter string redacted based on the configurations in this policy. */ - private String getAllowedQueryString(String queryString) { + private static String getAllowedQueryString(String queryString, Set allowedQueryParameterNames) { if (CoreUtils.isNullOrEmpty(queryString)) { return ""; } @@ -339,12 +356,7 @@ private String getAllowedQueryString(String queryString) { * @param sb StringBuilder that is generating the log message. * @param logLevel Log level the environment is configured to use. */ - private void addHeadersToLogMessage(ClientLogger logger, HttpHeaders headers, StringBuilder sb) { - // Either headers shouldn't be logged or the logging level isn't set to VERBOSE, don't add headers. - if (!httpLogDetailLevel.shouldLogHeaders() || !logger.canLogAtLevel(LogLevel.VERBOSE)) { - return; - } - + private static void addHeadersToLogMessage(Set allowedHeaderNames, HttpHeaders headers, StringBuilder sb) { for (HttpHeader header : headers) { String headerName = header.getName(); sb.append(headerName).append(":"); @@ -367,7 +379,8 @@ private void addHeadersToLogMessage(ClientLogger logger, HttpHeaders headers, St * @param body Body of the request or response. * @return The body pretty printed if it is JSON, otherwise the unmodified body. */ - private String prettyPrintIfNeeded(ClientLogger logger, String contentType, String body) { + private static String prettyPrintIfNeeded(ClientLogger logger, boolean prettyPrintBody, String contentType, + String body) { String result = body; if (prettyPrintBody && contentType != null && (contentType.startsWith(ContentType.APPLICATION_JSON) || contentType.startsWith("text/json"))) { @@ -388,7 +401,7 @@ private String prettyPrintIfNeeded(ClientLogger logger, String contentType, Stri * @param headers HTTP headers that are checked for containing Content-Length. * @return */ - private long getContentLength(ClientLogger logger, HttpHeaders headers) { + private static long getContentLength(ClientLogger logger, HttpHeaders headers) { long contentLength = 0; String contentLengthString = headers.getValue("Content-Length"); @@ -416,7 +429,7 @@ private long getContentLength(ClientLogger logger, HttpHeaders headers) { * @param contentLength Content-Length header represented as a numeric. * @return A flag indicating if the request or response body should be logged. */ - private boolean shouldBodyBeLogged(String contentTypeHeader, long contentLength) { + private static boolean shouldBodyBeLogged(String contentTypeHeader, long contentLength) { return !ContentType.APPLICATION_OCTET_STREAM.equalsIgnoreCase(contentTypeHeader) && contentLength != 0 && contentLength < MAX_BODY_LOG_SIZE; @@ -464,11 +477,4 @@ private static Integer getRequestRetryCount(HttpPipelineCallContext callContext, return null; } } - - /* - * Sanitizes the log level for logging to INFORMATIONAL if NOT_SET was used. - */ - private static LogLevel sanitizeLogLevel(LogLevel logLevel) { - return (logLevel == LogLevel.NOT_SET) ? LogLevel.INFORMATIONAL : logLevel; - } } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java new file mode 100644 index 000000000000..b2233d95ec99 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.policy; + +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.util.logging.ClientLogger; +import com.azure.core.util.logging.LogLevel; +import reactor.core.publisher.Mono; + +/** + * Manages logging HTTP requests in {@link HttpLoggingPolicy}. + */ +public interface HttpRequestLogger { + /** + * Retrieves the {@link LogLevel} used to log the current request. + * + * @param defaultLogLevel The default log level to use. + * @param callContext The contextual information about the request, including headers, body, and metadata. + * @return The {@link LogLevel} used to log the current request. + */ + LogLevel getLogLevel(LogLevel defaultLogLevel, HttpPipelineCallContext callContext); + + /** + * Logs the request. + * + * @param logger The {@link ClientLogger} used to log the request. + * @param logLevel The {@link LogLevel} used to loge the request. + * @param callContext The contextual information about the request, including headers, body, and metadata. + * @return A reactive response that will indicate that the response has been logged. + */ + Mono logRequest(ClientLogger logger, LogLevel logLevel, HttpPipelineCallContext callContext); +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java new file mode 100644 index 000000000000..e99f06fdd25d --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.policy; + +import com.azure.core.http.HttpResponse; +import com.azure.core.util.logging.ClientLogger; +import com.azure.core.util.logging.LogLevel; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +/** + * Manages logging HTTP responses in {@link HttpLoggingPolicy}. + */ +public interface HttpResponseLogger { + /** + * Retrieves the {@link LogLevel} used to log the current response. + * + * @param defaultLogLevel The default log level to use. + * @param response The HTTP response. + * @param responseDuration The duration between sending the request and receiving the response. + * @return The {@link LogLevel} used to log the current request. + */ + LogLevel getLogLevel(LogLevel defaultLogLevel, HttpResponse response, Duration responseDuration); + + /** + * Logs the request. + * + * @param logger The {@link ClientLogger} used to log the request. + * @param logLevel The {@link LogLevel} used to loge the request. + * @param response The HTTP response. + * @param responseDuration The duration between sending the request and receiving the response. + * @return A reactive response that will indicate that the response has been logged. + */ + Mono logResponse(ClientLogger logger, LogLevel logLevel, HttpResponse response, + Duration responseDuration); +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/BufferedHttpResponse.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/BufferedHttpResponse.java index ec0fb4462e92..19f37e9e0264 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/BufferedHttpResponse.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/BufferedHttpResponse.java @@ -6,7 +6,6 @@ import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpResponse; import com.azure.core.util.CoreUtils; -import com.azure.core.util.FluxUtil; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,10 +27,15 @@ public final class BufferedHttpResponse extends HttpResponse { public BufferedHttpResponse(HttpResponse innerHttpResponse) { super(innerHttpResponse.getRequest()); this.innerHttpResponse = innerHttpResponse; - this.cachedBody = FluxUtil.collectBytesInByteBufferStream(innerHttpResponse.getBody()) - .map(ByteBuffer::wrap) - .flux() - .cache(); + this.cachedBody = innerHttpResponse.getBody() + .map(buffer -> { + ByteBuffer cachedBuffer = ByteBuffer.allocate(buffer.remaining()); + cachedBuffer.put(buffer); + cachedBuffer.rewind(); + + return cachedBuffer; + }).cache() + .map(ByteBuffer::duplicate); } @Override From 0633212d2ef3181abbe497f2ac7822688908b91d Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Mon, 19 Oct 2020 14:40:27 -0700 Subject: [PATCH 04/10] Removed options bags classes that were unused prototypes --- .../core/http/policy/HttpLogOptions.java | 97 ------------------- 1 file changed, 97 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index f764dd2dfa69..7fa40e9d5f99 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -298,101 +298,4 @@ public HttpLogOptions setResponseLogger(HttpResponseLogger responseLogger) { this.responseLogger = responseLogger; return this; } - - /** - * Options passed into HTTP request logging functions. - */ - public static final class HttpRequestLoggingOptions { - private final ClientLogger logger; - private final LogLevel logLevel; - private final HttpPipelineCallContext httpPipelineCallContext; - - HttpRequestLoggingOptions(ClientLogger logger, LogLevel logLevel, - HttpPipelineCallContext httpPipelineCallContext) { - this.logger = logger; - this.logLevel = logLevel; - this.httpPipelineCallContext = httpPipelineCallContext; - } - - /** - * The {@link ClientLogger} used to log the request. - * - * @return The ClientLogger used to log the request. - */ - public ClientLogger getLogger() { - return logger; - } - - /** - * The {@link LogLevel} used when logging the request. - * - * @return The LogLevel used when logging the request. - */ - public LogLevel getLogLevel() { - return logLevel; - } - - /** - * The contextual information for the request such as headers, body, and metadata. - * - * @return The contextual information for the request. - */ - public HttpPipelineCallContext getHttpPipelineCallContext() { - return httpPipelineCallContext; - } - } - - /** - * Options passed into HTTP response logging functions. - */ - public static final class HttpResponseLoggingOptions { - private final ClientLogger logger; - private final LogLevel logLevel; - private final HttpResponse httpResponse; - private final Duration httpResponseDuration; - - HttpResponseLoggingOptions(ClientLogger logger, LogLevel logLevel, HttpResponse httpResponse, - Duration httpResponseDuration) { - this.logger = logger; - this.logLevel = logLevel; - this.httpResponse = httpResponse; - this.httpResponseDuration = httpResponseDuration; - } - - /** - * The {@link ClientLogger} used to log the response. - * - * @return The ClientLogger used to log the response. - */ - public ClientLogger getLogger() { - return logger; - } - - /** - * The {@link LogLevel} used when logging the response. - * - * @return The LogLevel used when logging the response. - */ - public LogLevel getLogLevel() { - return logLevel; - } - - /** - * The HTTP response being logged. - * - * @return The HTTP response being logged. - */ - public HttpResponse getHttpResponse() { - return httpResponse; - } - - /** - * The duration of time between sending the request and receiving the response. - * - * @return The duration of time between sending the request and receiving the response. - */ - public Duration getHttpResponseDuration() { - return httpResponseDuration; - } - } } From 53882456d596dc733945d804fa96a43d3feafba2 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 21 Oct 2020 10:49:59 -0700 Subject: [PATCH 05/10] Return HttpResponse when not logging it --- .../main/java/com/azure/core/http/policy/HttpLoggingPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index a279fa699f76..1df503886734 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -124,7 +124,7 @@ private static Mono logResponse(ClientLogger clientLogger, LogLeve LogLevel logLevel = responseLogger.getLogLevel(defaultLogLevel, httpResponse, httpResponseDuration); return (logLevel == LogLevel.NOT_SET || !clientLogger.canLogAtLevel(logLevel)) - ? Mono.empty() + ? Mono.just(httpResponse) : responseLogger.logResponse(clientLogger, logLevel, httpResponse, httpResponseDuration); } From ec0060c150749bc46dd62311c00aea02bb4bd28a Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 21 Oct 2020 13:09:22 -0700 Subject: [PATCH 06/10] Remove LogLevel parameter from interface APIs, changed getLogLevel to a default interface method, removed defaultLogLevel from HttpLogOptions --- .../core/http/policy/HttpLogOptions.java | 35 ------------- .../core/http/policy/HttpLoggingPolicy.java | 50 ++++++------------- .../core/http/policy/HttpRequestLogger.java | 12 +++-- .../core/http/policy/HttpResponseLogger.java | 19 +++---- 4 files changed, 31 insertions(+), 85 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index 7fa40e9d5f99..103373f18dbf 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -3,14 +3,11 @@ package com.azure.core.http.policy; -import com.azure.core.http.HttpPipelineCallContext; -import com.azure.core.http.HttpResponse; import com.azure.core.util.ClientOptions; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.logging.LogLevel; -import java.time.Duration; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -219,38 +216,6 @@ public HttpLogOptions setPrettyPrintBody(boolean prettyPrintBody) { return this; } - /** - * Gets the {@link LogLevel} used by default when logging requests and responses. - *

- * {@link HttpRequestLogger#getLogLevel(LogLevel, HttpPipelineCallContext)} and {@link - * HttpResponseLogger#getLogLevel(LogLevel, HttpResponse, Duration)} can be used to set the {@link LogLevel} for - * each request and response being logged. - *

- * By default {@link LogLevel#INFORMATIONAL} is used. - * - * @return The {@link LogLevel} used by default when logging requests and responses. - */ - public LogLevel getDefaultLogLevel() { - return defaultLogLevel; - } - - /** - * Sets the {@link LogLevel} used by default when logging requests and responses. - *

- * {@link HttpRequestLogger#getLogLevel(LogLevel, HttpPipelineCallContext)} and {@link - * HttpResponseLogger#getLogLevel(LogLevel, HttpResponse, Duration)} can be used to set the {@link LogLevel} for - * each request and response being logged. - *

- * By default {@link LogLevel#INFORMATIONAL} is used. - * - * @param defaultLogLevel The default log level. - * @return The updated HttpLogOptions object. - */ - public HttpLogOptions setDefaultLogLevel(LogLevel defaultLogLevel) { - this.defaultLogLevel = defaultLogLevel; - return this; - } - /** * Gets the {@link HttpRequestLogger} that will be used to log requests. *

diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index 1df503886734..e450b6556acf 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -45,7 +45,6 @@ public class HttpLoggingPolicy implements HttpPipelinePolicy { private final Set allowedQueryParameterNames; private final boolean prettyPrintBody; - private final LogLevel defaultLogLevel; private final HttpRequestLogger requestLogger; private final HttpResponseLogger responseLogger; @@ -66,7 +65,6 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { this.allowedQueryParameterNames = Collections.emptySet(); this.prettyPrintBody = false; - this.defaultLogLevel = LogLevel.INFORMATIONAL; this.requestLogger = new DefaultHttpRequestLogger(); this.responseLogger = new DefaultHttpResponseLogger(); } else { @@ -81,9 +79,6 @@ public HttpLoggingPolicy(HttpLogOptions httpLogOptions) { .collect(Collectors.toSet()); this.prettyPrintBody = httpLogOptions.isPrettyPrintBody(); - this.defaultLogLevel = (httpLogOptions.getDefaultLogLevel() == null) - ? LogLevel.INFORMATIONAL - : httpLogOptions.getDefaultLogLevel(); this.requestLogger = (httpLogOptions.getRequestLogger() == null) ? new DefaultHttpRequestLogger() : httpLogOptions.getRequestLogger(); @@ -103,39 +98,22 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN final ClientLogger logger = new ClientLogger((String) context.getData("caller-method").orElse("")); final long startNs = System.nanoTime(); - return logRequest(logger, defaultLogLevel, requestLogger, context) + return requestLogger.logRequest(logger, context) .then(next.process()) - .flatMap(response -> logResponse(logger, defaultLogLevel, responseLogger, response, + .flatMap(response -> responseLogger.logResponse(logger, response, Duration.ofNanos(System.nanoTime() - startNs))) .doOnError(throwable -> logger.warning("<-- HTTP FAILED: ", throwable)); } - private static Mono logRequest(ClientLogger clientLogger, LogLevel defaultLogLevel, - HttpRequestLogger requestLogger, HttpPipelineCallContext callContext) { - LogLevel logLevel = requestLogger.getLogLevel(defaultLogLevel, callContext); - - return (logLevel == LogLevel.NOT_SET || !clientLogger.canLogAtLevel(logLevel)) - ? Mono.empty() - : requestLogger.logRequest(clientLogger, logLevel, callContext); - } - - private static Mono logResponse(ClientLogger clientLogger, LogLevel defaultLogLevel, - HttpResponseLogger responseLogger, HttpResponse httpResponse, Duration httpResponseDuration) { - LogLevel logLevel = responseLogger.getLogLevel(defaultLogLevel, httpResponse, httpResponseDuration); - - return (logLevel == LogLevel.NOT_SET || !clientLogger.canLogAtLevel(logLevel)) - ? Mono.just(httpResponse) - : responseLogger.logResponse(clientLogger, logLevel, httpResponse, httpResponseDuration); - } - private final class DefaultHttpRequestLogger implements HttpRequestLogger { @Override - public LogLevel getLogLevel(LogLevel defaultLogLevel, HttpPipelineCallContext callContext) { - return (defaultLogLevel == null) ? LogLevel.INFORMATIONAL : defaultLogLevel; - } + public Mono logRequest(ClientLogger logger, HttpPipelineCallContext callContext) { + final LogLevel logLevel = getLogLevel(callContext); + + if (!logger.canLogAtLevel(logLevel)) { + return Mono.empty(); + } - @Override - public Mono logRequest(ClientLogger logger, LogLevel logLevel, HttpPipelineCallContext callContext) { final HttpRequest request = callContext.getHttpRequest(); StringBuilder requestLogMessage = new StringBuilder(); @@ -213,13 +191,13 @@ public Mono logRequest(ClientLogger logger, LogLevel logLevel, HttpPipelin private final class DefaultHttpResponseLogger implements HttpResponseLogger { @Override - public LogLevel getLogLevel(LogLevel defaultLogLevel, HttpResponse response, Duration responseDuration) { - return (defaultLogLevel == null) ? LogLevel.INFORMATIONAL : defaultLogLevel; - } + public Mono logResponse(ClientLogger logger, HttpResponse response, Duration responseDuration) { + final LogLevel logLevel = getLogLevel(response, responseDuration); + + if (!logger.canLogAtLevel(logLevel)) { + return Mono.just(response); + } - @Override - public Mono logResponse(ClientLogger logger, LogLevel logLevel, HttpResponse response, - Duration responseDuration) { String contentLengthString = response.getHeaderValue("Content-Length"); String bodySize = (CoreUtils.isNullOrEmpty(contentLengthString)) ? "unknown-length body" diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java index b2233d95ec99..60b7f517afbd 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java @@ -14,20 +14,22 @@ public interface HttpRequestLogger { /** * Retrieves the {@link LogLevel} used to log the current request. + *

+ * By default this will return {@link LogLevel#INFORMATIONAL}. * - * @param defaultLogLevel The default log level to use. * @param callContext The contextual information about the request, including headers, body, and metadata. * @return The {@link LogLevel} used to log the current request. */ - LogLevel getLogLevel(LogLevel defaultLogLevel, HttpPipelineCallContext callContext); + default LogLevel getLogLevel(HttpPipelineCallContext callContext) { + return LogLevel.INFORMATIONAL; + } /** * Logs the request. * * @param logger The {@link ClientLogger} used to log the request. - * @param logLevel The {@link LogLevel} used to loge the request. * @param callContext The contextual information about the request, including headers, body, and metadata. - * @return A reactive response that will indicate that the response has been logged. + * @return A reactive response that will indicate that the request has been logged. */ - Mono logRequest(ClientLogger logger, LogLevel logLevel, HttpPipelineCallContext callContext); + Mono logRequest(ClientLogger logger, HttpPipelineCallContext callContext); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java index e99f06fdd25d..2ccf02b91494 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java @@ -16,23 +16,24 @@ public interface HttpResponseLogger { /** * Retrieves the {@link LogLevel} used to log the current response. + *

+ * By default this will return {@link LogLevel#INFORMATIONAL}. * - * @param defaultLogLevel The default log level to use. * @param response The HTTP response. * @param responseDuration The duration between sending the request and receiving the response. - * @return The {@link LogLevel} used to log the current request. + * @return The {@link LogLevel} used to log the current response. */ - LogLevel getLogLevel(LogLevel defaultLogLevel, HttpResponse response, Duration responseDuration); + default LogLevel getLogLevel(HttpResponse response, Duration responseDuration) { + return LogLevel.INFORMATIONAL; + } /** - * Logs the request. + * Logs the response. * - * @param logger The {@link ClientLogger} used to log the request. - * @param logLevel The {@link LogLevel} used to loge the request. + * @param logger The {@link ClientLogger} used to log the response. * @param response The HTTP response. * @param responseDuration The duration between sending the request and receiving the response. - * @return A reactive response that will indicate that the response has been logged. + * @return A reactive response that returns the response that was logged. */ - Mono logResponse(ClientLogger logger, LogLevel logLevel, HttpResponse response, - Duration responseDuration); + Mono logResponse(ClientLogger logger, HttpResponse response, Duration responseDuration); } From a241bba9bb42efcbd31e84621af8c3e5397ee561 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 21 Oct 2020 13:40:44 -0700 Subject: [PATCH 07/10] Javadoc updates --- .../core/http/HttpPipelineCallContext.java | 2 +- .../core/http/policy/HttpLogOptions.java | 24 +++++++++---------- .../core/http/policy/HttpRequestLogger.java | 16 +++++++------ .../core/http/policy/HttpResponseLogger.java | 10 ++++---- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java index 569ed90718bc..0fc1d1b6c5bc 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java @@ -9,7 +9,7 @@ import java.util.Optional; /** - * Type representing context local to a single http request and it's response. + * Represents the information used to make a single HTTP request. */ public final class HttpPipelineCallContext { private HttpRequest httpRequest; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index 103373f18dbf..a4369469ea90 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -217,22 +217,22 @@ public HttpLogOptions setPrettyPrintBody(boolean prettyPrintBody) { } /** - * Gets the {@link HttpRequestLogger} that will be used to log requests. + * Gets the {@link HttpRequestLogger} that will be used to log HTTP requests. *

- * A default logger will be used if one isn't supplied. + * A default {@link HttpRequestLogger} will be used if one isn't supplied. * - * @return The {@link HttpRequestLogger} that will be used to log requests. + * @return The {@link HttpRequestLogger} that will be used to log HTTP requests. */ public HttpRequestLogger getRequestLogger() { return requestLogger; } /** - * Sets the {@link HttpRequestLogger} that will be used to log requests. + * Sets the {@link HttpRequestLogger} that will be used to log HTTP requests. *

- * A default logger will be used if one isn't supplied. + * A default {@link HttpRequestLogger} will be used if one isn't supplied. * - * @param requestLogger The {@link HttpRequestLogger} that will be used to log requests. + * @param requestLogger The {@link HttpRequestLogger} that will be used to log HTTP requests. * @return The updated HttpLogOptions object. */ public HttpLogOptions setRequestLogger(HttpRequestLogger requestLogger) { @@ -241,22 +241,22 @@ public HttpLogOptions setRequestLogger(HttpRequestLogger requestLogger) { } /** - * Gets the {@link HttpResponseLogger} that will be used to log responses. + * Gets the {@link HttpResponseLogger} that will be used to log HTTP responses. *

- * A default logger will be used if one isn't supplied. + * A default {@link HttpResponseLogger} will be used if one isn't supplied. * - * @return The {@link HttpResponseLogger} that will be used to log responses. + * @return The {@link HttpResponseLogger} that will be used to log HTTP responses. */ public HttpResponseLogger getResponseLogger() { return responseLogger; } /** - * Sets the {@link HttpResponseLogger} that will be used to log responses. + * Sets the {@link HttpResponseLogger} that will be used to log HTTP responses. *

- * A default logger will be sued if one isn't supplied. + * A default {@link HttpResponseLogger} will be used if one isn't supplied. * - * @param responseLogger The {@link HttpResponseLogger} that will be used to log responses. + * @param responseLogger The {@link HttpResponseLogger} that will be used to log HTTP responses. * @return The updated HttpLogOptions object. */ public HttpLogOptions setResponseLogger(HttpResponseLogger responseLogger) { diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java index 60b7f517afbd..fcc66fa83a86 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java @@ -13,23 +13,25 @@ */ public interface HttpRequestLogger { /** - * Retrieves the {@link LogLevel} used to log the current request. + * Gets the {@link LogLevel} used to log the HTTP request. *

* By default this will return {@link LogLevel#INFORMATIONAL}. * - * @param callContext The contextual information about the request, including headers, body, and metadata. - * @return The {@link LogLevel} used to log the current request. + * @param callContext The information used to send the HTTP request. + * @return The {@link LogLevel} used to log the HTTP request. */ default LogLevel getLogLevel(HttpPipelineCallContext callContext) { return LogLevel.INFORMATIONAL; } /** - * Logs the request. + * Logs the HTTP request. + *

+ * To get the {@link LogLevel} used to log the HTTP request use {@link #getLogLevel(HttpPipelineCallContext)}. * - * @param logger The {@link ClientLogger} used to log the request. - * @param callContext The contextual information about the request, including headers, body, and metadata. - * @return A reactive response that will indicate that the request has been logged. + * @param logger The {@link ClientLogger} used to log the HTTP request. + * @param callContext The information used to send the HTTP request. + * @return A reactive response that indicates that the HTTP request has been logged. */ Mono logRequest(ClientLogger logger, HttpPipelineCallContext callContext); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java index 2ccf02b91494..1e0c3a39fb7d 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java @@ -15,25 +15,27 @@ */ public interface HttpResponseLogger { /** - * Retrieves the {@link LogLevel} used to log the current response. + * Gets the {@link LogLevel} used to log the HTTP response. *

* By default this will return {@link LogLevel#INFORMATIONAL}. * * @param response The HTTP response. * @param responseDuration The duration between sending the request and receiving the response. - * @return The {@link LogLevel} used to log the current response. + * @return The {@link LogLevel} used to log the HTTP response. */ default LogLevel getLogLevel(HttpResponse response, Duration responseDuration) { return LogLevel.INFORMATIONAL; } /** - * Logs the response. + * Logs the HTTP response. + *

+ * To get the {@link LogLevel} used to log the HTTP response use {@link #getLogLevel(HttpResponse, Duration)}. * * @param logger The {@link ClientLogger} used to log the response. * @param response The HTTP response. * @param responseDuration The duration between sending the request and receiving the response. - * @return A reactive response that returns the response that was logged. + * @return A reactive response that returns the HTTP response that was logged. */ Mono logResponse(ClientLogger logger, HttpResponse response, Duration responseDuration); } From a8cfbc39734d0e8107eb95e5caffea83580a9e74 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 22 Oct 2020 13:20:08 -0700 Subject: [PATCH 08/10] Add logger option bag classes to pass information for logging the request or response, made HttpPipelineCallContext's Context property accessible via getContext --- .../core/http/HttpPipelineCallContext.java | 9 +++ .../core/http/policy/HttpLogOptions.java | 2 - .../core/http/policy/HttpLoggingPolicy.java | 42 +++++++++---- .../core/http/policy/HttpRequestLogger.java | 12 ++-- .../policy/HttpRequestLoggingOptions.java | 49 +++++++++++++++ .../core/http/policy/HttpResponseLogger.java | 15 ++--- .../policy/HttpResponseLoggingOptions.java | 63 +++++++++++++++++++ 7 files changed, 163 insertions(+), 29 deletions(-) create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java index 0fc1d1b6c5bc..688eafe0a397 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java @@ -66,6 +66,15 @@ public Optional getData(String key) { return this.data.getData(key); } + /** + * Gets the context associated to the request. + * + * @return The context associated to the request. + */ + public Context getContext() { + return data; + } + /** * Gets the HTTP request. * diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java index a4369469ea90..ac35be06f925 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogOptions.java @@ -6,7 +6,6 @@ import com.azure.core.util.ClientOptions; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; -import com.azure.core.util.logging.LogLevel; import java.util.Arrays; import java.util.HashSet; @@ -24,7 +23,6 @@ public class HttpLogOptions { private Set allowedQueryParamNames; private boolean prettyPrintBody; - private LogLevel defaultLogLevel; private HttpRequestLogger requestLogger; private HttpResponseLogger responseLogger; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index e450b6556acf..f086a022f1cb 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -40,6 +40,8 @@ public class HttpLoggingPolicy implements HttpPipelinePolicy { private static final int MAX_BODY_LOG_SIZE = 1024 * 16; private static final String REDACTED_PLACEHOLDER = "REDACTED"; + private final ClientLogger logger = new ClientLogger(HttpLoggingPolicy.class); + private final HttpLogDetailLevel httpLogDetailLevel; private final Set allowedHeaderNames; private final Set allowedQueryParameterNames; @@ -98,23 +100,34 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN final ClientLogger logger = new ClientLogger((String) context.getData("caller-method").orElse("")); final long startNs = System.nanoTime(); - return requestLogger.logRequest(logger, context) + return requestLogger.logRequest(logger, getRequestLoggingOptions(context)) .then(next.process()) - .flatMap(response -> responseLogger.logResponse(logger, response, - Duration.ofNanos(System.nanoTime() - startNs))) + .flatMap(response -> responseLogger.logResponse(logger, + getResponseLoggingOptions(response, startNs, context))) .doOnError(throwable -> logger.warning("<-- HTTP FAILED: ", throwable)); } + private HttpRequestLoggingOptions getRequestLoggingOptions(HttpPipelineCallContext callContext) { + return new HttpRequestLoggingOptions(callContext.getHttpRequest(), callContext.getContext(), + getRequestRetryCount(callContext.getContext(), getHttpLoggingPolicyLogger())); + } + + private HttpResponseLoggingOptions getResponseLoggingOptions(HttpResponse httpResponse, long startNs, + HttpPipelineCallContext callContext) { + return new HttpResponseLoggingOptions(httpResponse, Duration.ofNanos(System.nanoTime() - startNs), + callContext.getContext(), getRequestRetryCount(callContext.getContext(), getHttpLoggingPolicyLogger())); + } + private final class DefaultHttpRequestLogger implements HttpRequestLogger { @Override - public Mono logRequest(ClientLogger logger, HttpPipelineCallContext callContext) { - final LogLevel logLevel = getLogLevel(callContext); + public Mono logRequest(ClientLogger logger, HttpRequestLoggingOptions loggingOptions) { + final LogLevel logLevel = getLogLevel(loggingOptions); if (!logger.canLogAtLevel(logLevel)) { return Mono.empty(); } - final HttpRequest request = callContext.getHttpRequest(); + final HttpRequest request = loggingOptions.getHttpRequest(); StringBuilder requestLogMessage = new StringBuilder(); if (httpLogDetailLevel.shouldLogUrl()) { @@ -124,7 +137,7 @@ public Mono logRequest(ClientLogger logger, HttpPipelineCallContext callCo .append(getRedactedUrl(request.getUrl(), allowedQueryParameterNames)) .append(System.lineSeparator()); - Integer retryCount = getRequestRetryCount(callContext, logger); + Integer retryCount = loggingOptions.getTryCount(); if (retryCount != null) { requestLogMessage.append("Try count: ") .append(retryCount) @@ -191,8 +204,9 @@ public Mono logRequest(ClientLogger logger, HttpPipelineCallContext callCo private final class DefaultHttpResponseLogger implements HttpResponseLogger { @Override - public Mono logResponse(ClientLogger logger, HttpResponse response, Duration responseDuration) { - final LogLevel logLevel = getLogLevel(response, responseDuration); + public Mono logResponse(ClientLogger logger, HttpResponseLoggingOptions loggingOptions) { + final LogLevel logLevel = getLogLevel(loggingOptions); + final HttpResponse response = loggingOptions.getHttpResponse(); if (!logger.canLogAtLevel(logLevel)) { return Mono.just(response); @@ -210,7 +224,7 @@ public Mono logResponse(ClientLogger logger, HttpResponse response .append(" ") .append(getRedactedUrl(response.getRequest().getUrl(), allowedQueryParameterNames)) .append(" (") - .append(responseDuration.toMillis()) + .append(loggingOptions.getResponseDuration().toMillis()) .append(" ms, ") .append(bodySize) .append(")") @@ -255,6 +269,10 @@ public Mono logResponse(ClientLogger logger, HttpResponse response } } + private ClientLogger getHttpLoggingPolicyLogger() { + return this.logger; + } + private static Mono logAndReturn(ClientLogger logger, LogLevel logLevel, StringBuilder logMessageBuilder, T data) { switch (logLevel) { @@ -442,8 +460,8 @@ private static Mono writeBufferToBodyStream(WritableByteChannel chan * If there is no value set or it isn't a valid number null will be returned indicating that retry count won't be * logged. */ - private static Integer getRequestRetryCount(HttpPipelineCallContext callContext, ClientLogger logger) { - Object rawRetryCount = callContext.getData(RETRY_COUNT_CONTEXT).orElse(null); + private static Integer getRequestRetryCount(Context context, ClientLogger logger) { + Object rawRetryCount = context.getData(RETRY_COUNT_CONTEXT).orElse(null); if (rawRetryCount == null) { return null; } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java index fcc66fa83a86..dfbfefdb04ba 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java @@ -3,7 +3,6 @@ package com.azure.core.http.policy; -import com.azure.core.http.HttpPipelineCallContext; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.logging.LogLevel; import reactor.core.publisher.Mono; @@ -11,27 +10,28 @@ /** * Manages logging HTTP requests in {@link HttpLoggingPolicy}. */ +@FunctionalInterface public interface HttpRequestLogger { /** * Gets the {@link LogLevel} used to log the HTTP request. *

* By default this will return {@link LogLevel#INFORMATIONAL}. * - * @param callContext The information used to send the HTTP request. + * @param loggingOptions The information available during request logging. * @return The {@link LogLevel} used to log the HTTP request. */ - default LogLevel getLogLevel(HttpPipelineCallContext callContext) { + default LogLevel getLogLevel(HttpRequestLoggingOptions loggingOptions) { return LogLevel.INFORMATIONAL; } /** * Logs the HTTP request. *

- * To get the {@link LogLevel} used to log the HTTP request use {@link #getLogLevel(HttpPipelineCallContext)}. + * To get the {@link LogLevel} used to log the HTTP request use {@link #getLogLevel(HttpRequestLoggingOptions)}. * * @param logger The {@link ClientLogger} used to log the HTTP request. - * @param callContext The information used to send the HTTP request. + * @param loggingOptions The information available during request logging. * @return A reactive response that indicates that the HTTP request has been logged. */ - Mono logRequest(ClientLogger logger, HttpPipelineCallContext callContext); + Mono logRequest(ClientLogger logger, HttpRequestLoggingOptions loggingOptions); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java new file mode 100644 index 000000000000..e038e17859a5 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.policy; + +import com.azure.core.http.HttpRequest; +import com.azure.core.util.Context; + +/** + * Options class containing information available during HTTP request logging. + */ +public final class HttpRequestLoggingOptions { + private final HttpRequest httpRequest; + private final Context context; + private final Integer tryCount; + + HttpRequestLoggingOptions(HttpRequest httpRequest, Context context, Integer tryCount) { + this.httpRequest = httpRequest; + this.context = context; + this.tryCount = tryCount; + } + + /** + * Gets the HTTP request being sent. + * + * @return The HTTP request. + */ + public HttpRequest getHttpRequest() { + return httpRequest; + } + + /** + * Gets the contextual information about the HTTP request. + * + * @return The contextual information. + */ + public Context getContext() { + return context; + } + + /** + * Gets the try count for the HTTP request. + * + * @return The HTTP request try count. + */ + public Integer getTryCount() { + return tryCount; + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java index 1e0c3a39fb7d..60fd327bea26 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java @@ -8,34 +8,31 @@ import com.azure.core.util.logging.LogLevel; import reactor.core.publisher.Mono; -import java.time.Duration; - /** * Manages logging HTTP responses in {@link HttpLoggingPolicy}. */ +@FunctionalInterface public interface HttpResponseLogger { /** * Gets the {@link LogLevel} used to log the HTTP response. *

* By default this will return {@link LogLevel#INFORMATIONAL}. * - * @param response The HTTP response. - * @param responseDuration The duration between sending the request and receiving the response. + * @param loggingOptions The information available during response logging. * @return The {@link LogLevel} used to log the HTTP response. */ - default LogLevel getLogLevel(HttpResponse response, Duration responseDuration) { + default LogLevel getLogLevel(HttpResponseLoggingOptions loggingOptions) { return LogLevel.INFORMATIONAL; } /** * Logs the HTTP response. *

- * To get the {@link LogLevel} used to log the HTTP response use {@link #getLogLevel(HttpResponse, Duration)}. + * To get the {@link LogLevel} used to log the HTTP response use {@link #getLogLevel(HttpResponseLoggingOptions)}. * * @param logger The {@link ClientLogger} used to log the response. - * @param response The HTTP response. - * @param responseDuration The duration between sending the request and receiving the response. + * @param loggingOptions The information available during response logging. * @return A reactive response that returns the HTTP response that was logged. */ - Mono logResponse(ClientLogger logger, HttpResponse response, Duration responseDuration); + Mono logResponse(ClientLogger logger, HttpResponseLoggingOptions loggingOptions); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java new file mode 100644 index 000000000000..fa961398a865 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.policy; + +import com.azure.core.http.HttpResponse; +import com.azure.core.util.Context; + +import java.time.Duration; + +/** + * Options class containing information available during HTTP response logging. + */ +public final class HttpResponseLoggingOptions { + private final HttpResponse httpResponse; + private final Duration responseDuration; + private final Context context; + private final Integer tryCount; + + HttpResponseLoggingOptions(HttpResponse httpResponse, Duration responseDuration, Context context, + Integer tryCount) { + this.httpResponse = httpResponse; + this.responseDuration = responseDuration; + this.context = context; + this.tryCount = tryCount; + } + + /** + * Gets the HTTP response being received. + * + * @return The HTTP response being received. + */ + public HttpResponse getHttpResponse() { + return httpResponse; + } + + /** + * Gets the duration between the HTTP request being sent and the HTTP response being received. + * + * @return The duration between the HTTP request being sent and the HTTP response being received. + */ + public Duration getResponseDuration() { + return responseDuration; + } + + /** + * Gets the contextual information about the HTTP response. + * + * @return The contextual information. + */ + public Context getContext() { + return context; + } + + /** + * Gets the try count for the HTTP request associated to the HTTP response. + * + * @return The HTTP request try count. + */ + public Integer getTryCount() { + return tryCount; + } +} From 6198bbbeb2cf654731358a41314e95be04537bd2 Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Tue, 27 Apr 2021 13:35:34 -0700 Subject: [PATCH 09/10] Use accessor pattern instead of making API public --- .../core/http/HttpPipelineCallContext.java | 7 +++- .../core/http/policy/HttpLoggingPolicy.java | 9 +++-- .../http/HttpPipelineCallContextHelper.java | 36 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/HttpPipelineCallContextHelper.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java index 0ccc9a335d46..362422b3ee85 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/HttpPipelineCallContext.java @@ -3,6 +3,7 @@ package com.azure.core.http; +import com.azure.core.implementation.http.HttpPipelineCallContextHelper; import com.azure.core.util.Context; import java.util.Objects; @@ -46,6 +47,10 @@ public final class HttpPipelineCallContext { this.data = data; } + static { + HttpPipelineCallContextHelper.setAccessor(HttpPipelineCallContext::getContext); + } + /** * Stores a key-value data in the context. * @@ -71,7 +76,7 @@ public Optional getData(String key) { * * @return The context associated to the HTTP call. */ - public Context getContext() { + Context getContext() { return data; } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index f086a022f1cb..a5c5a4a39589 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -10,6 +10,7 @@ import com.azure.core.http.HttpPipelineNextPolicy; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; +import com.azure.core.implementation.http.HttpPipelineCallContextHelper; import com.azure.core.util.Context; import com.azure.core.util.CoreUtils; import com.azure.core.util.UrlBuilder; @@ -108,14 +109,16 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN } private HttpRequestLoggingOptions getRequestLoggingOptions(HttpPipelineCallContext callContext) { - return new HttpRequestLoggingOptions(callContext.getHttpRequest(), callContext.getContext(), - getRequestRetryCount(callContext.getContext(), getHttpLoggingPolicyLogger())); + return new HttpRequestLoggingOptions(callContext.getHttpRequest(), + HttpPipelineCallContextHelper.getContext(callContext), + getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext), getHttpLoggingPolicyLogger())); } private HttpResponseLoggingOptions getResponseLoggingOptions(HttpResponse httpResponse, long startNs, HttpPipelineCallContext callContext) { return new HttpResponseLoggingOptions(httpResponse, Duration.ofNanos(System.nanoTime() - startNs), - callContext.getContext(), getRequestRetryCount(callContext.getContext(), getHttpLoggingPolicyLogger())); + HttpPipelineCallContextHelper.getContext(callContext), + getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext), getHttpLoggingPolicyLogger())); } private final class DefaultHttpRequestLogger implements HttpRequestLogger { diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/HttpPipelineCallContextHelper.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/HttpPipelineCallContextHelper.java new file mode 100644 index 000000000000..571a1bf9d3d3 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/HttpPipelineCallContextHelper.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation.http; + +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.util.Context; + +/** + * Helper class to access private values of {@link HttpPipelineCallContext} across package boundaries. + */ +public final class HttpPipelineCallContextHelper { + private static HttpPipelineCallContextAccessor accessor; + + private HttpPipelineCallContextHelper() { } + + /** + * Type defining the methods to set the non-public properties of an {@link HttpPipelineCallContext} instance. + */ + public interface HttpPipelineCallContextAccessor { + Context getContext(HttpPipelineCallContext callContext); + } + + /** + * The method called from {@link HttpPipelineCallContext} to set it's accessor. + * + * @param callContextAccessor The accessor. + */ + public static void setAccessor(final HttpPipelineCallContextAccessor callContextAccessor) { + accessor = callContextAccessor; + } + + public static Context getContext(HttpPipelineCallContext callContext) { + return accessor.getContext(callContext); + } +} From f907d050169877f1d1878f1aeef6c6b20a4cc018 Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 15 Jul 2021 14:44:02 -0700 Subject: [PATCH 10/10] Rename Options to Context --- .../azure/core/http/policy/HttpLoggingPolicy.java | 12 ++++++------ .../azure/core/http/policy/HttpRequestLogger.java | 6 +++--- ...ngOptions.java => HttpRequestLoggingContext.java} | 4 ++-- .../azure/core/http/policy/HttpResponseLogger.java | 6 +++--- ...gOptions.java => HttpResponseLoggingContext.java} | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) rename sdk/core/azure-core/src/main/java/com/azure/core/http/policy/{HttpRequestLoggingOptions.java => HttpRequestLoggingContext.java} (90%) rename sdk/core/azure-core/src/main/java/com/azure/core/http/policy/{HttpResponseLoggingOptions.java => HttpResponseLoggingContext.java} (93%) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index a5c5a4a39589..3a9e76e24bea 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -108,22 +108,22 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN .doOnError(throwable -> logger.warning("<-- HTTP FAILED: ", throwable)); } - private HttpRequestLoggingOptions getRequestLoggingOptions(HttpPipelineCallContext callContext) { - return new HttpRequestLoggingOptions(callContext.getHttpRequest(), + private HttpRequestLoggingContext getRequestLoggingOptions(HttpPipelineCallContext callContext) { + return new HttpRequestLoggingContext(callContext.getHttpRequest(), HttpPipelineCallContextHelper.getContext(callContext), getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext), getHttpLoggingPolicyLogger())); } - private HttpResponseLoggingOptions getResponseLoggingOptions(HttpResponse httpResponse, long startNs, + private HttpResponseLoggingContext getResponseLoggingOptions(HttpResponse httpResponse, long startNs, HttpPipelineCallContext callContext) { - return new HttpResponseLoggingOptions(httpResponse, Duration.ofNanos(System.nanoTime() - startNs), + return new HttpResponseLoggingContext(httpResponse, Duration.ofNanos(System.nanoTime() - startNs), HttpPipelineCallContextHelper.getContext(callContext), getRequestRetryCount(HttpPipelineCallContextHelper.getContext(callContext), getHttpLoggingPolicyLogger())); } private final class DefaultHttpRequestLogger implements HttpRequestLogger { @Override - public Mono logRequest(ClientLogger logger, HttpRequestLoggingOptions loggingOptions) { + public Mono logRequest(ClientLogger logger, HttpRequestLoggingContext loggingOptions) { final LogLevel logLevel = getLogLevel(loggingOptions); if (!logger.canLogAtLevel(logLevel)) { @@ -207,7 +207,7 @@ public Mono logRequest(ClientLogger logger, HttpRequestLoggingOptions logg private final class DefaultHttpResponseLogger implements HttpResponseLogger { @Override - public Mono logResponse(ClientLogger logger, HttpResponseLoggingOptions loggingOptions) { + public Mono logResponse(ClientLogger logger, HttpResponseLoggingContext loggingOptions) { final LogLevel logLevel = getLogLevel(loggingOptions); final HttpResponse response = loggingOptions.getHttpResponse(); diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java index dfbfefdb04ba..5f7f845c0fd8 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLogger.java @@ -20,18 +20,18 @@ public interface HttpRequestLogger { * @param loggingOptions The information available during request logging. * @return The {@link LogLevel} used to log the HTTP request. */ - default LogLevel getLogLevel(HttpRequestLoggingOptions loggingOptions) { + default LogLevel getLogLevel(HttpRequestLoggingContext loggingOptions) { return LogLevel.INFORMATIONAL; } /** * Logs the HTTP request. *

- * To get the {@link LogLevel} used to log the HTTP request use {@link #getLogLevel(HttpRequestLoggingOptions)}. + * To get the {@link LogLevel} used to log the HTTP request use {@link #getLogLevel(HttpRequestLoggingContext)}. * * @param logger The {@link ClientLogger} used to log the HTTP request. * @param loggingOptions The information available during request logging. * @return A reactive response that indicates that the HTTP request has been logged. */ - Mono logRequest(ClientLogger logger, HttpRequestLoggingOptions loggingOptions); + Mono logRequest(ClientLogger logger, HttpRequestLoggingContext loggingOptions); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingContext.java similarity index 90% rename from sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java rename to sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingContext.java index e038e17859a5..8b2fd8106ef1 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpRequestLoggingContext.java @@ -9,12 +9,12 @@ /** * Options class containing information available during HTTP request logging. */ -public final class HttpRequestLoggingOptions { +public final class HttpRequestLoggingContext { private final HttpRequest httpRequest; private final Context context; private final Integer tryCount; - HttpRequestLoggingOptions(HttpRequest httpRequest, Context context, Integer tryCount) { + HttpRequestLoggingContext(HttpRequest httpRequest, Context context, Integer tryCount) { this.httpRequest = httpRequest; this.context = context; this.tryCount = tryCount; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java index 60fd327bea26..b71dbd9e1cce 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLogger.java @@ -21,18 +21,18 @@ public interface HttpResponseLogger { * @param loggingOptions The information available during response logging. * @return The {@link LogLevel} used to log the HTTP response. */ - default LogLevel getLogLevel(HttpResponseLoggingOptions loggingOptions) { + default LogLevel getLogLevel(HttpResponseLoggingContext loggingOptions) { return LogLevel.INFORMATIONAL; } /** * Logs the HTTP response. *

- * To get the {@link LogLevel} used to log the HTTP response use {@link #getLogLevel(HttpResponseLoggingOptions)}. + * To get the {@link LogLevel} used to log the HTTP response use {@link #getLogLevel(HttpResponseLoggingContext)}. * * @param logger The {@link ClientLogger} used to log the response. * @param loggingOptions The information available during response logging. * @return A reactive response that returns the HTTP response that was logged. */ - Mono logResponse(ClientLogger logger, HttpResponseLoggingOptions loggingOptions); + Mono logResponse(ClientLogger logger, HttpResponseLoggingContext loggingOptions); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingContext.java similarity index 93% rename from sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java rename to sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingContext.java index fa961398a865..ac113230cbee 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpResponseLoggingContext.java @@ -11,13 +11,13 @@ /** * Options class containing information available during HTTP response logging. */ -public final class HttpResponseLoggingOptions { +public final class HttpResponseLoggingContext { private final HttpResponse httpResponse; private final Duration responseDuration; private final Context context; private final Integer tryCount; - HttpResponseLoggingOptions(HttpResponse httpResponse, Duration responseDuration, Context context, + HttpResponseLoggingContext(HttpResponse httpResponse, Duration responseDuration, Context context, Integer tryCount) { this.httpResponse = httpResponse; this.responseDuration = responseDuration;