Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion sdk/clientcore/core/checkstyle-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<suppress files="io.clientcore.core.http.client.DefaultHttpClientBuilder.java" checks="com.azure.tools.checkstyle.checks.ServiceClientBuilderCheck" />
<suppress files="io.clientcore.core.http.client.implementation.InputStreamTimeoutResponseSubscriber.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="io.clientcore.core.http.pipeline.HttpInstrumentationPolicy.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="io.clientcore.core.http.pipeline.HttpLoggingPolicy.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="io.clientcore.core.implementation.MethodHandleReflectiveInvoker.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="io.clientcore.core.implementation.http.rest.LengthValidatingInputStream.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="io.clientcore.core.serialization.json.JsonReader.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
Expand Down
4 changes: 0 additions & 4 deletions sdk/clientcore/core/spotbugs-exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@
<Bug pattern="DB_DUPLICATE_SWITCH_CLAUSES" />
<Class name="io.clientcore.core.serialization.xml.implementation.aalto.out.CharXmlWriter" />
</Match>
<Match>
<Bug pattern="DCN_NULLPOINTER_EXCEPTION" />
<Class name="io.clientcore.core.http.pipeline.HttpLoggingPolicy" />
</Match>
<Match>
<Bug pattern="DLS_DEAD_LOCAL_STORE" />
<Or>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package io.clientcore.core.http.models;

import io.clientcore.core.instrumentation.InstrumentationOptions;
import io.clientcore.core.util.configuration.Configuration;
import io.clientcore.core.util.configuration.ConfigurationProperty;
import io.clientcore.core.util.configuration.ConfigurationPropertyBuilder;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
* Configuration options for HTTP instrumentation.
* <p>
* The instrumentation emits distributed traces following <a href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md">OpenTelemetry HTTP semantic conventions</a>
* and, when enabled, detailed HTTP logs.
* <p>
* The following information is recorded on distributed traces:
* <ul>
* <li>Request method, URI. The URI is sanitized based on allowed query parameters configurable with {@link #setAllowedQueryParamNames(Set)} and {@link #addAllowedQueryParamName(String)}</li>
* <li>Response status code</li>
* <li>Error details if the request fails</li>
* <li>Time it takes to receive response</li>
* <li>Correlation identifiers</li>
* </ul>
*
The following information is recorded on detailed HTTP logs:
* <ul>
* <li>Request method, URI, and body size. URI is sanitized based on allowed query parameters configurable with {@link #setAllowedQueryParamNames(Set)} and {@link #addAllowedQueryParamName(String)}</li>
* <li>Response status code and body size</li>
* <li>Request and response headers from allow-list configured via {@link #setAllowedHeaderNames(Set)} and {@link #addAllowedHeaderName(HttpHeaderName)}.</li>
* <li>Error details if the request fails</li>
* <li>Time it takes to receive response</li>
* <li>Correlation identifiers</li>
* <li>When content logging is enabled via {@link #setContentLoggingEnabled(boolean)}: request and response body, and time-to-last-byte</li>
* </ul>
*
* Client libraries auto-discover global OpenTelemetry SDK instance configured by the java agent or
* in the application code. Just create a client instance as usual as shown in the following code snippet:
*
* <p><strong>Clients auto-discover global OpenTelemetry</strong></p>
*
* <!-- src_embed io.clientcore.core.telemetry.useglobalopentelemetry -->
* <pre>
*
* AutoConfiguredOpenTelemetrySdk.initialize&#40;&#41;;
*
* SampleClient client = new SampleClientBuilder&#40;&#41;.build&#40;&#41;;
*
* &#47;&#47; this call will be traced using OpenTelemetry SDK initialized globally
* client.clientCall&#40;&#41;;
*
* </pre>
* <!-- end io.clientcore.core.telemetry.useglobalopentelemetry -->
* <p>
*
* Alternatively, application developers can pass OpenTelemetry SDK instance explicitly to the client libraries.
*
* <p><strong>Pass configured OpenTelemetry instance explicitly</strong></p>
*
* <!-- src_embed io.clientcore.core.telemetry.useexplicitopentelemetry -->
* <pre>
*
* OpenTelemetry openTelemetry = AutoConfiguredOpenTelemetrySdk.initialize&#40;&#41;.getOpenTelemetrySdk&#40;&#41;;
* HttpInstrumentationOptions instrumentationOptions = new HttpInstrumentationOptions&#40;&#41;
* .setTelemetryProvider&#40;openTelemetry&#41;;
*
* SampleClient client = new SampleClientBuilder&#40;&#41;.instrumentationOptions&#40;instrumentationOptions&#41;.build&#40;&#41;;
*
* &#47;&#47; this call will be traced using OpenTelemetry SDK provided explicitly
* client.clientCall&#40;&#41;;
*
* </pre>
* <!-- end io.clientcore.core.telemetry.useexplicitopentelemetry -->
*/
public final class HttpInstrumentationOptions extends InstrumentationOptions {
private boolean isHttpLoggingEnabled;
private boolean isContentLoggingEnabled;
private boolean isRedactedHeaderNamesLoggingEnabled;
private Set<HttpHeaderName> allowedHeaderNames;
private Set<String> allowedQueryParamNames;
private static final List<HttpHeaderName> DEFAULT_HEADERS_ALLOWLIST
= Arrays.asList(HttpHeaderName.TRACEPARENT, HttpHeaderName.ACCEPT, HttpHeaderName.CACHE_CONTROL,
HttpHeaderName.CONNECTION, HttpHeaderName.CONTENT_LENGTH, HttpHeaderName.CONTENT_TYPE, HttpHeaderName.DATE,
HttpHeaderName.ETAG, HttpHeaderName.EXPIRES, HttpHeaderName.IF_MATCH, HttpHeaderName.IF_MODIFIED_SINCE,
HttpHeaderName.IF_NONE_MATCH, HttpHeaderName.IF_UNMODIFIED_SINCE, HttpHeaderName.LAST_MODIFIED,
HttpHeaderName.PRAGMA, HttpHeaderName.RETRY_AFTER, HttpHeaderName.SERVER, HttpHeaderName.TRANSFER_ENCODING,
HttpHeaderName.USER_AGENT, HttpHeaderName.WWW_AUTHENTICATE);

private static final List<String> DEFAULT_QUERY_PARAMS_ALLOWLIST = Collections.singletonList("api-version");

private static final ConfigurationProperty<Boolean> HTTP_LOGGING_ENABLED
= ConfigurationPropertyBuilder.ofBoolean("http.logging.enabled")
.shared(true)
.environmentVariableName(Configuration.PROPERTY_HTTP_LOGGING_ENABLED)
.defaultValue(false)
.build();

private static final boolean DEFAULT_HTTP_LOGGING_ENABLED
= Configuration.getGlobalConfiguration().get(HTTP_LOGGING_ENABLED);

/**
* Creates a new instance using default options:
* <ul>
* <li>Detailed HTTP logging is disabled.</li>
* <li>Distributed tracing is enabled.</li>
* </ul>
*/
public HttpInstrumentationOptions() {
super();
isHttpLoggingEnabled = DEFAULT_HTTP_LOGGING_ENABLED;
isContentLoggingEnabled = false;
isRedactedHeaderNamesLoggingEnabled = true;
allowedHeaderNames = new HashSet<>(DEFAULT_HEADERS_ALLOWLIST);
allowedQueryParamNames = new HashSet<>(DEFAULT_QUERY_PARAMS_ALLOWLIST);
}

/**
* Flag indicating whether detailed HTTP request and response logging is enabled.
* False by default.
* <p>
* When HTTP logging is disabled, basic information about the request and response is still recorded
* on distributed tracing spans.
*
* @return True if logging is enabled, false otherwise.
*/
public boolean isHttpLoggingEnabled() {
return isHttpLoggingEnabled;
}

/**
* Flag indicating whether HTTP request and response header values are added to the logs
* when their name is not explicitly allowed via {@link HttpInstrumentationOptions#setAllowedHeaderNames(Set)} or
* {@link HttpInstrumentationOptions#addAllowedHeaderName(HttpHeaderName)}.
* True by default.
*
* @return True if redacted header names logging is enabled, false otherwise.
*/
public boolean isRedactedHeaderNamesLoggingEnabled() {
return isRedactedHeaderNamesLoggingEnabled;
}

/**
* Enables or disables logging of redacted header names.
* @param redactedHeaderNamesLoggingEnabled True to enable logging of redacted header names, false otherwise.
* Default is true.
* @return The updated {@link HttpInstrumentationOptions} object.
*/
public HttpInstrumentationOptions setRedactedHeaderNamesLoggingEnabled(boolean redactedHeaderNamesLoggingEnabled) {
isRedactedHeaderNamesLoggingEnabled = redactedHeaderNamesLoggingEnabled;
return this;
}

/**
* Flag indicating whether HTTP request and response body is logged.
* False by default.
* <p>
* Note: even when content logging is explicitly enabled, content is not logged
* for requests and responses where the content length is not known or greater than 16KB.
*
* @return True if content logging is enabled, false otherwise.
*/
public boolean isContentLoggingEnabled() {
return isContentLoggingEnabled;
}

/**
* Enables or disables logging of HTTP request and response.
* False by default.
* <p>
* When HTTP logging is disabled, basic information about the request and response is still recorded
* via distributed tracing.
*
* @param isHttpLoggingEnabled True to enable detailed HTTP logging, false otherwise.
* @return The updated {@link HttpInstrumentationOptions} object.
*/
public HttpInstrumentationOptions setHttpLoggingEnabled(boolean isHttpLoggingEnabled) {
this.isHttpLoggingEnabled = isHttpLoggingEnabled;
return this;
}

/**
* Enables or disables logging of HTTP request and response body. False by default.
* Enabling content logging also enables HTTP logging in general.
* <p>
* Note: even when content logging is explicitly enabled, content is not logged for requests and responses where the
* content length is not known or greater than 16KB.
*
* @param isContentLoggingEnabled True to enable content logging, false otherwise.
* @return The updated {@link HttpInstrumentationOptions} object.
*/
public HttpInstrumentationOptions setContentLoggingEnabled(boolean isContentLoggingEnabled) {
this.isHttpLoggingEnabled |= isContentLoggingEnabled;
this.isContentLoggingEnabled = isContentLoggingEnabled;
return this;
}

/**
* Gets the allowed headers that should be logged when they appear on the request or response.
*
* @return The list of allowed headers.
*/
public Set<HttpHeaderName> getAllowedHeaderNames() {
return Collections.unmodifiableSet(allowedHeaderNames);
}

/**
* Sets the given allowed headers that should be logged.
* Note: headers are not recorded on traces.
*
* <p>
* This method sets the provided header names to be the allowed header names which will be logged for all HTTP
* requests and responses, overwriting any previously configured headers. Additionally, users can use
* {@link HttpInstrumentationOptions#addAllowedHeaderName(HttpHeaderName)} or {@link HttpInstrumentationOptions#getAllowedHeaderNames()} to add or
* remove more headers names to the existing set of allowed header names.
* </p>
*
* @param allowedHeaderNames The list of allowed header names from the user.
*
* @return The updated HttpLogOptions object.
*/
public HttpInstrumentationOptions setAllowedHeaderNames(final Set<HttpHeaderName> allowedHeaderNames) {
this.allowedHeaderNames = allowedHeaderNames == null ? new HashSet<>() : allowedHeaderNames;

return this;
}

/**
* Sets the given allowed header to the default header set that should be logged when they appear on the request or response.
* <p>
* Note: headers are not recorded on traces.
*
* @param allowedHeaderName The allowed header name from the user.
*
* @return The updated HttpLogOptions object.
*
* @throws NullPointerException If {@code allowedHeaderName} is {@code null}.
*/
public HttpInstrumentationOptions addAllowedHeaderName(final HttpHeaderName allowedHeaderName) {
Objects.requireNonNull(allowedHeaderName);
this.allowedHeaderNames.add(allowedHeaderName);

return this;
}

/**
* Gets the allowed query parameters.
*
* @return The list of allowed query parameters.
*/
public Set<String> getAllowedQueryParamNames() {
return Collections.unmodifiableSet(allowedQueryParamNames);
}

/**
* Sets the given allowed query params to be recorded on logs and traces.
*
* @param allowedQueryParamNames The list of allowed query params from the user.
*
* @return The updated {@code allowedQueryParamName} object.
*/
public HttpInstrumentationOptions setAllowedQueryParamNames(final Set<String> allowedQueryParamNames) {
this.allowedQueryParamNames = allowedQueryParamNames == null ? new HashSet<>() : allowedQueryParamNames;

return this;
}

/**
* Sets the given allowed query param that can be recorded on logs and traces.
*
* @param allowedQueryParamName The allowed query param name from the user.
*
* @return The updated {@link HttpInstrumentationOptions} object.
*
* @throws NullPointerException If {@code allowedQueryParamName} is {@code null}.
*/
public HttpInstrumentationOptions addAllowedQueryParamName(final String allowedQueryParamName) {
this.allowedQueryParamNames.add(allowedQueryParamName);
return this;
}

@Override
public HttpInstrumentationOptions setTracingEnabled(boolean isTracingEnabled) {
super.setTracingEnabled(isTracingEnabled);
return this;
}

@Override
public HttpInstrumentationOptions setTelemetryProvider(Object telemetryProvider) {
super.setTelemetryProvider(telemetryProvider);
return this;
}
}
Loading
Loading