Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change HttpLogOptions to be HttpInstrumentationOptions, update docs #43780

Merged
merged 6 commits into from
Jan 22, 2025
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;
lmolkova marked this conversation as resolved.
Show resolved Hide resolved
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;
lmolkova marked this conversation as resolved.
Show resolved Hide resolved

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