Skip to content

Commit 5bc93af

Browse files
author
Liudmila Molkova
authored
Change HttpLogOptions to be HttpInstrumentationOptions, update docs (Azure#43780)
* simplified httplogopts * HttpLogOptions -> HttpInstrumentationOptions, docs and logging cleanup * rename get|setProvider to get|setTelemetryProvider and make it object
1 parent c939638 commit 5bc93af

File tree

32 files changed

+717
-552
lines changed

32 files changed

+717
-552
lines changed

sdk/clientcore/core/checkstyle-suppressions.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
<suppress files="io.clientcore.core.http.client.DefaultHttpClientBuilder.java" checks="com.azure.tools.checkstyle.checks.ServiceClientBuilderCheck" />
2525
<suppress files="io.clientcore.core.http.client.implementation.InputStreamTimeoutResponseSubscriber.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
2626
<suppress files="io.clientcore.core.http.pipeline.HttpInstrumentationPolicy.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
27-
<suppress files="io.clientcore.core.http.pipeline.HttpLoggingPolicy.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
2827
<suppress files="io.clientcore.core.implementation.MethodHandleReflectiveInvoker.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
2928
<suppress files="io.clientcore.core.implementation.http.rest.LengthValidatingInputStream.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
3029
<suppress files="io.clientcore.core.serialization.json.JsonReader.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />

sdk/clientcore/core/spotbugs-exclude.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,6 @@
7979
<Bug pattern="DB_DUPLICATE_SWITCH_CLAUSES" />
8080
<Class name="io.clientcore.core.serialization.xml.implementation.aalto.out.CharXmlWriter" />
8181
</Match>
82-
<Match>
83-
<Bug pattern="DCN_NULLPOINTER_EXCEPTION" />
84-
<Class name="io.clientcore.core.http.pipeline.HttpLoggingPolicy" />
85-
</Match>
8682
<Match>
8783
<Bug pattern="DLS_DEAD_LOCAL_STORE" />
8884
<Or>
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package io.clientcore.core.http.models;
5+
6+
import io.clientcore.core.instrumentation.InstrumentationOptions;
7+
import io.clientcore.core.util.configuration.Configuration;
8+
import io.clientcore.core.util.configuration.ConfigurationProperty;
9+
import io.clientcore.core.util.configuration.ConfigurationPropertyBuilder;
10+
11+
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.HashSet;
14+
import java.util.List;
15+
import java.util.Objects;
16+
import java.util.Set;
17+
18+
/**
19+
* Configuration options for HTTP instrumentation.
20+
* <p>
21+
* 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>
22+
* and, when enabled, detailed HTTP logs.
23+
* <p>
24+
* The following information is recorded on distributed traces:
25+
* <ul>
26+
* <li>Request method, URI. The URI is sanitized based on allowed query parameters configurable with {@link #setAllowedQueryParamNames(Set)} and {@link #addAllowedQueryParamName(String)}</li>
27+
* <li>Response status code</li>
28+
* <li>Error details if the request fails</li>
29+
* <li>Time it takes to receive response</li>
30+
* <li>Correlation identifiers</li>
31+
* </ul>
32+
*
33+
The following information is recorded on detailed HTTP logs:
34+
* <ul>
35+
* <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>
36+
* <li>Response status code and body size</li>
37+
* <li>Request and response headers from allow-list configured via {@link #setAllowedHeaderNames(Set)} and {@link #addAllowedHeaderName(HttpHeaderName)}.</li>
38+
* <li>Error details if the request fails</li>
39+
* <li>Time it takes to receive response</li>
40+
* <li>Correlation identifiers</li>
41+
* <li>When content logging is enabled via {@link HttpLogDetailLevel#BODY_AND_HEADERS}: request and response body, and time-to-last-byte</li>
42+
* </ul>
43+
*
44+
* Client libraries auto-discover global OpenTelemetry SDK instance configured by the java agent or
45+
* in the application code. Just create a client instance as usual as shown in the following code snippet:
46+
*
47+
* <p><strong>Clients auto-discover global OpenTelemetry</strong></p>
48+
*
49+
* <!-- src_embed io.clientcore.core.telemetry.useglobalopentelemetry -->
50+
* <pre>
51+
*
52+
* AutoConfiguredOpenTelemetrySdk.initialize&#40;&#41;;
53+
*
54+
* SampleClient client = new SampleClientBuilder&#40;&#41;.build&#40;&#41;;
55+
*
56+
* &#47;&#47; this call will be traced using OpenTelemetry SDK initialized globally
57+
* client.clientCall&#40;&#41;;
58+
*
59+
* </pre>
60+
* <!-- end io.clientcore.core.telemetry.useglobalopentelemetry -->
61+
* <p>
62+
*
63+
* Alternatively, application developers can pass OpenTelemetry SDK instance explicitly to the client libraries.
64+
*
65+
* <p><strong>Pass configured OpenTelemetry instance explicitly</strong></p>
66+
*
67+
* <!-- src_embed io.clientcore.core.telemetry.useexplicitopentelemetry -->
68+
* <pre>
69+
*
70+
* OpenTelemetry openTelemetry = AutoConfiguredOpenTelemetrySdk.initialize&#40;&#41;.getOpenTelemetrySdk&#40;&#41;;
71+
* HttpInstrumentationOptions instrumentationOptions = new HttpInstrumentationOptions&#40;&#41;
72+
* .setTelemetryProvider&#40;openTelemetry&#41;;
73+
*
74+
* SampleClient client = new SampleClientBuilder&#40;&#41;.instrumentationOptions&#40;instrumentationOptions&#41;.build&#40;&#41;;
75+
*
76+
* &#47;&#47; this call will be traced using OpenTelemetry SDK provided explicitly
77+
* client.clientCall&#40;&#41;;
78+
*
79+
* </pre>
80+
* <!-- end io.clientcore.core.telemetry.useexplicitopentelemetry -->
81+
*/
82+
public final class HttpInstrumentationOptions extends InstrumentationOptions {
83+
private HttpLogDetailLevel logDetailLevel;
84+
private boolean isRedactedHeaderNamesLoggingEnabled;
85+
private Set<HttpHeaderName> allowedHeaderNames;
86+
private Set<String> allowedQueryParamNames;
87+
private static final List<HttpHeaderName> DEFAULT_HEADERS_ALLOWLIST
88+
= Arrays.asList(HttpHeaderName.TRACEPARENT, HttpHeaderName.ACCEPT, HttpHeaderName.CACHE_CONTROL,
89+
HttpHeaderName.CONNECTION, HttpHeaderName.CONTENT_LENGTH, HttpHeaderName.CONTENT_TYPE, HttpHeaderName.DATE,
90+
HttpHeaderName.ETAG, HttpHeaderName.EXPIRES, HttpHeaderName.IF_MATCH, HttpHeaderName.IF_MODIFIED_SINCE,
91+
HttpHeaderName.IF_NONE_MATCH, HttpHeaderName.IF_UNMODIFIED_SINCE, HttpHeaderName.LAST_MODIFIED,
92+
HttpHeaderName.PRAGMA, HttpHeaderName.RETRY_AFTER, HttpHeaderName.SERVER, HttpHeaderName.TRANSFER_ENCODING,
93+
HttpHeaderName.USER_AGENT, HttpHeaderName.WWW_AUTHENTICATE);
94+
95+
static final HttpLogDetailLevel ENVIRONMENT_HTTP_LOG_DETAIL_LEVEL
96+
= HttpLogDetailLevel.fromConfiguration(Configuration.getGlobalConfiguration());
97+
private static final List<String> DEFAULT_QUERY_PARAMS_ALLOWLIST = Collections.singletonList("api-version");
98+
99+
/**
100+
* Creates a new instance using default options:
101+
* <ul>
102+
* <li>Detailed HTTP logging is disabled.</li>
103+
* <li>Distributed tracing is enabled.</li>
104+
* </ul>
105+
*/
106+
public HttpInstrumentationOptions() {
107+
super();
108+
logDetailLevel = ENVIRONMENT_HTTP_LOG_DETAIL_LEVEL;
109+
isRedactedHeaderNamesLoggingEnabled = true;
110+
allowedHeaderNames = new HashSet<>(DEFAULT_HEADERS_ALLOWLIST);
111+
allowedQueryParamNames = new HashSet<>(DEFAULT_QUERY_PARAMS_ALLOWLIST);
112+
}
113+
114+
/**
115+
* Gets the level of detail for HTTP request logs. Default is {@link HttpLogDetailLevel#NONE}.
116+
* <p>
117+
* When HTTP logging is disabled, basic information about the request and response is still recorded
118+
* on distributed tracing spans.
119+
*
120+
* @return The {@link HttpLogDetailLevel}.
121+
*/
122+
public HttpLogDetailLevel getHttpLogLevel() {
123+
return logDetailLevel;
124+
}
125+
126+
/**
127+
* Flag indicating whether HTTP request and response header values are added to the logs
128+
* when their name is not explicitly allowed via {@link HttpInstrumentationOptions#setAllowedHeaderNames(Set)} or
129+
* {@link HttpInstrumentationOptions#addAllowedHeaderName(HttpHeaderName)}.
130+
* True by default.
131+
*
132+
* @return True if redacted header names logging is enabled, false otherwise.
133+
*/
134+
public boolean isRedactedHeaderNamesLoggingEnabled() {
135+
return isRedactedHeaderNamesLoggingEnabled;
136+
}
137+
138+
/**
139+
* Enables or disables logging of redacted header names.
140+
* @param redactedHeaderNamesLoggingEnabled True to enable logging of redacted header names, false otherwise.
141+
* Default is true.
142+
* @return The updated {@link HttpInstrumentationOptions} object.
143+
*/
144+
public HttpInstrumentationOptions setRedactedHeaderNamesLoggingEnabled(boolean redactedHeaderNamesLoggingEnabled) {
145+
isRedactedHeaderNamesLoggingEnabled = redactedHeaderNamesLoggingEnabled;
146+
return this;
147+
}
148+
149+
/**
150+
* Sets the level of detail for HTTP request logs.
151+
* Default is {@link HttpLogDetailLevel#NONE}.
152+
*
153+
* @param logDetailLevel The {@link HttpLogDetailLevel}.
154+
*
155+
* @return The updated {@link HttpInstrumentationOptions} object.
156+
*/
157+
public HttpInstrumentationOptions setHttpLogLevel(HttpLogDetailLevel logDetailLevel) {
158+
this.logDetailLevel = logDetailLevel;
159+
return this;
160+
}
161+
162+
/**
163+
* Gets the allowed headers that should be logged when they appear on the request or response.
164+
*
165+
* @return The list of allowed headers.
166+
*/
167+
public Set<HttpHeaderName> getAllowedHeaderNames() {
168+
return Collections.unmodifiableSet(allowedHeaderNames);
169+
}
170+
171+
/**
172+
* Sets the given allowed headers that should be logged.
173+
* Note: headers are not recorded on traces.
174+
*
175+
* <p>
176+
* This method sets the provided header names to be the allowed header names which will be logged for all HTTP
177+
* requests and responses, overwriting any previously configured headers. Additionally, users can use
178+
* {@link HttpInstrumentationOptions#addAllowedHeaderName(HttpHeaderName)} or {@link HttpInstrumentationOptions#getAllowedHeaderNames()} to add or
179+
* remove more headers names to the existing set of allowed header names.
180+
* </p>
181+
*
182+
* @param allowedHeaderNames The list of allowed header names from the user.
183+
*
184+
* @return The updated HttpLogOptions object.
185+
*/
186+
public HttpInstrumentationOptions setAllowedHeaderNames(final Set<HttpHeaderName> allowedHeaderNames) {
187+
this.allowedHeaderNames = allowedHeaderNames == null ? new HashSet<>() : new HashSet<>(allowedHeaderNames);
188+
189+
return this;
190+
}
191+
192+
/**
193+
* Sets the given allowed header to the default header set that should be logged when they appear on the request or response.
194+
* <p>
195+
* Note: headers are not recorded on traces.
196+
*
197+
* @param allowedHeaderName The allowed header name from the user.
198+
*
199+
* @return The updated HttpLogOptions object.
200+
*
201+
* @throws NullPointerException If {@code allowedHeaderName} is {@code null}.
202+
*/
203+
public HttpInstrumentationOptions addAllowedHeaderName(final HttpHeaderName allowedHeaderName) {
204+
Objects.requireNonNull(allowedHeaderName);
205+
this.allowedHeaderNames.add(allowedHeaderName);
206+
207+
return this;
208+
}
209+
210+
/**
211+
* Gets the allowed query parameters.
212+
*
213+
* @return The list of allowed query parameters.
214+
*/
215+
public Set<String> getAllowedQueryParamNames() {
216+
return Collections.unmodifiableSet(allowedQueryParamNames);
217+
}
218+
219+
/**
220+
* Sets the given allowed query params to be recorded on logs and traces.
221+
*
222+
* @param allowedQueryParamNames The list of allowed query params from the user.
223+
*
224+
* @return The updated {@code allowedQueryParamName} object.
225+
*/
226+
public HttpInstrumentationOptions setAllowedQueryParamNames(final Set<String> allowedQueryParamNames) {
227+
this.allowedQueryParamNames
228+
= allowedQueryParamNames == null ? new HashSet<>() : new HashSet<>(allowedQueryParamNames);
229+
230+
return this;
231+
}
232+
233+
/**
234+
* Sets the given allowed query param that can be recorded on logs and traces.
235+
*
236+
* @param allowedQueryParamName The allowed query param name from the user.
237+
*
238+
* @return The updated {@link HttpInstrumentationOptions} object.
239+
*
240+
* @throws NullPointerException If {@code allowedQueryParamName} is {@code null}.
241+
*/
242+
public HttpInstrumentationOptions addAllowedQueryParamName(final String allowedQueryParamName) {
243+
this.allowedQueryParamNames.add(allowedQueryParamName);
244+
return this;
245+
}
246+
247+
@Override
248+
public HttpInstrumentationOptions setTracingEnabled(boolean isTracingEnabled) {
249+
super.setTracingEnabled(isTracingEnabled);
250+
return this;
251+
}
252+
253+
@Override
254+
public HttpInstrumentationOptions setTelemetryProvider(Object telemetryProvider) {
255+
super.setTelemetryProvider(telemetryProvider);
256+
return this;
257+
}
258+
259+
/**
260+
* The level of detail for HTTP request logs.
261+
*/
262+
public enum HttpLogDetailLevel {
263+
/**
264+
* HTTP logging is turned off.
265+
*/
266+
NONE,
267+
268+
/**
269+
* Enables logging the following information on detailed HTTP logs
270+
* <ul>
271+
* <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>
272+
* <li>Response status code and body size</li>
273+
* <li>Request and response headers from allow-list configured via {@link #setAllowedHeaderNames(Set)} and {@link #addAllowedHeaderName(HttpHeaderName)}.</li>
274+
* <li>Error details if the request fails</li>
275+
* <li>Time it takes to receive response</li>
276+
* <li>Correlation identifiers</li>
277+
* </ul>
278+
*/
279+
HEADERS,
280+
281+
/**
282+
* Enables logging the following information on detailed HTTP logs
283+
* <ul>
284+
* <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>
285+
* <li>Response status code and body size</li>
286+
* <li>Error details if the request fails</li>
287+
* <li>Time it takes to receive response</li>
288+
* <li>Correlation identifiers</li>
289+
* <li>Request and response bodies</li>
290+
* <li>Time-to-last-byte</li>
291+
* </ul>
292+
*
293+
* <p>
294+
* The request and response body will be buffered into memory even if it is never consumed by an application, possibly impacting
295+
* performance.
296+
* <p>
297+
* Body is not logged (and not buffered) for requests and responses where the content length is not known or greater than 16KB.
298+
*/
299+
BODY,
300+
301+
/**
302+
* Enables logging everything in {@link #HEADERS} and {@link #BODY}.
303+
*
304+
* <p>
305+
* The request and response body will be buffered into memory even if it is never consumed by an application, possibly impacting
306+
* performance.
307+
* <p>
308+
* Body is not logged (and not buffered) for requests and responses where the content length is not known or greater than 16KB.
309+
*/
310+
BODY_AND_HEADERS;
311+
312+
private static final String HEADERS_VALUE = "headers";
313+
private static final String BODY_VALUE = "body";
314+
private static final String BODY_AND_HEADERS_VALUE = "body_and_headers";
315+
316+
private static final ConfigurationProperty<String> HTTP_LOG_DETAIL_LEVEL
317+
= ConfigurationPropertyBuilder.ofString("http.log.detail.level")
318+
.shared(true)
319+
.environmentVariableName(Configuration.PROPERTY_HTTP_LOG_DETAIL_LEVEL)
320+
.defaultValue("none")
321+
.build();
322+
323+
static HttpLogDetailLevel fromConfiguration(Configuration configuration) {
324+
String detailLevel = configuration.get(HTTP_LOG_DETAIL_LEVEL);
325+
326+
HttpLogDetailLevel logDetailLevel;
327+
328+
if (HEADERS_VALUE.equalsIgnoreCase(detailLevel)) {
329+
logDetailLevel = HEADERS;
330+
} else if (BODY_VALUE.equalsIgnoreCase(detailLevel)) {
331+
logDetailLevel = BODY;
332+
} else if (BODY_AND_HEADERS_VALUE.equalsIgnoreCase(detailLevel)) {
333+
logDetailLevel = BODY_AND_HEADERS;
334+
} else {
335+
logDetailLevel = NONE;
336+
}
337+
338+
return logDetailLevel;
339+
}
340+
}
341+
}

0 commit comments

Comments
 (0)