Skip to content

Commit f902b9d

Browse files
author
Liudmila Molkova
authored
Clientcore: instrumentation convenience API (Azure#44006)
* Operation instrumentation conventience
1 parent faba4ba commit f902b9d

33 files changed

+1566
-286
lines changed

sdk/clientcore/core/src/main/java/io/clientcore/core/http/pipeline/HttpInstrumentationPolicy.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929

3030
import java.io.IOException;
3131
import java.io.InputStream;
32+
import java.util.Arrays;
3233
import java.util.Collections;
3334
import java.util.HashMap;
35+
import java.util.List;
3436
import java.util.Locale;
3537
import java.util.Map;
3638
import java.util.Properties;
@@ -164,11 +166,16 @@ public final class HttpInstrumentationPolicy implements HttpPipelinePolicy {
164166

165167
private static final int MAX_BODY_LOG_SIZE = 1024 * 16;
166168
private static final String REDACTED_PLACEHOLDER = "REDACTED";
169+
167170
// HTTP request duration metric is formally defined in the OpenTelemetry Semantic Conventions:
168171
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md#metric-httpclientrequestduration
169172
private static final String REQUEST_DURATION_METRIC_NAME = "http.client.request.duration";
170173
private static final String REQUEST_DURATION_METRIC_DESCRIPTION = "Duration of HTTP client requests";
171174
private static final String REQUEST_DURATION_METRIC_UNIT = "s";
175+
// the histogram boundaries are optimized for typical HTTP request durations and could be customized by users on
176+
// the OTel side. These are the defaults documented in the OpenTelemetry Semantic Conventions (link above).
177+
private static final List<Double> REQUEST_DURATION_BOUNDARIES_ADVICE = Collections.unmodifiableList(
178+
Arrays.asList(0.005d, 0.01d, 0.025d, 0.05d, 0.075d, 0.1d, 0.25d, 0.5d, 0.75d, 1d, 2.5d, 5d, 7.5d, 10d));
172179

173180
// request log level is low (verbose) since almost all request details are also
174181
// captured on the response log.
@@ -197,7 +204,7 @@ public HttpInstrumentationPolicy(HttpInstrumentationOptions instrumentationOptio
197204
this.tracer = instrumentation.createTracer();
198205
this.meter = instrumentation.createMeter();
199206
this.httpRequestDuration = meter.createDoubleHistogram(REQUEST_DURATION_METRIC_NAME,
200-
REQUEST_DURATION_METRIC_DESCRIPTION, REQUEST_DURATION_METRIC_UNIT);
207+
REQUEST_DURATION_METRIC_DESCRIPTION, REQUEST_DURATION_METRIC_UNIT, REQUEST_DURATION_BOUNDARIES_ADVICE);
201208
this.traceContextPropagator = instrumentation.getW3CTraceContextPropagator();
202209

203210
HttpInstrumentationOptions optionsToUse

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/instrumentation/AttributeKeys.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ public final class AttributeKeys {
148148
*/
149149
public static final String HTTP_RESPONSE_BODY_CONTENT_KEY = "http.request.body.content";
150150

151+
/**
152+
* Key representing operation name. The value should be a string.
153+
* When instrumenting client libraries, it should be language-agnostic operation name provided in typespec
154+
* or swagger.
155+
*/
156+
public static final String OPERATION_NAME_KEY = "operation.name";
157+
151158
/**
152159
* Key representing maximum number of redirects or retries. It's reported when the number of redirects or retries
153160
* was exhausted.

sdk/clientcore/core/src/main/java/io/clientcore/core/instrumentation/tracing/NoopInstrumentationContext.java renamed to sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/instrumentation/NoopInstrumentationContext.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
package io.clientcore.core.instrumentation.tracing;
4+
package io.clientcore.core.implementation.instrumentation;
55

66
import io.clientcore.core.instrumentation.InstrumentationContext;
7+
import io.clientcore.core.instrumentation.tracing.Span;
78

8-
final class NoopInstrumentationContext implements InstrumentationContext {
9+
/**
10+
* No-op implementation of {@link InstrumentationContext}.
11+
*/
12+
public final class NoopInstrumentationContext implements InstrumentationContext {
913
public static final NoopInstrumentationContext INSTANCE = new NoopInstrumentationContext();
1014

1115
private NoopInstrumentationContext() {

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/instrumentation/NoopMeter.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.clientcore.core.instrumentation.metrics.LongCounter;
1010
import io.clientcore.core.instrumentation.metrics.Meter;
1111

12+
import java.util.List;
1213
import java.util.Objects;
1314

1415
/**
@@ -47,7 +48,8 @@ private NoopMeter() {
4748
* {@inheritDoc}
4849
*/
4950
@Override
50-
public DoubleHistogram createDoubleHistogram(String name, String description, String unit) {
51+
public DoubleHistogram createDoubleHistogram(String name, String description, String unit,
52+
List<Double> bucketBoundaries) {
5153
Objects.requireNonNull(name, "'name' cannot be null.");
5254
Objects.requireNonNull(description, "'description' cannot be null.");
5355
Objects.requireNonNull(unit, "'unit' cannot be null.");
@@ -83,8 +85,4 @@ public LongCounter createLongUpDownCounter(String name, String description, Stri
8385
public boolean isEnabled() {
8486
return false;
8587
}
86-
87-
@Override
88-
public void close() {
89-
}
9088
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package io.clientcore.core.implementation.instrumentation.fallback;
5+
6+
import io.clientcore.core.instrumentation.InstrumentationAttributes;
7+
8+
import java.util.Collections;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.Objects;
12+
13+
class FallbackAttributes implements InstrumentationAttributes {
14+
private final Map<String, Object> attributes;
15+
16+
FallbackAttributes(Map<String, Object> attributes) {
17+
if (attributes == null) {
18+
this.attributes = Collections.emptyMap();
19+
return;
20+
}
21+
22+
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
23+
Objects.requireNonNull(entry.getKey(), "attribute key cannot be null.");
24+
Objects.requireNonNull(entry.getValue(), "attribute value cannot be null.");
25+
}
26+
this.attributes = Collections.unmodifiableMap(attributes);
27+
}
28+
29+
@Override
30+
public InstrumentationAttributes put(String key, Object value) {
31+
Objects.requireNonNull(key, "'key' cannot be null.");
32+
Objects.requireNonNull(value, "'value' cannot be null.");
33+
34+
Map<String, Object> newAttributes = new HashMap<>((int) ((attributes.size() + 1) * 1.5));
35+
newAttributes.putAll(attributes);
36+
newAttributes.put(key, value);
37+
return new FallbackAttributes(newAttributes);
38+
}
39+
40+
Map<String, Object> getAttributes() {
41+
return attributes;
42+
}
43+
}

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/instrumentation/fallback/FallbackInstrumentation.java

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33

44
package io.clientcore.core.implementation.instrumentation.fallback;
55

6-
import io.clientcore.core.implementation.instrumentation.NoopAttributes;
6+
import io.clientcore.core.implementation.instrumentation.LibraryInstrumentationOptionsAccessHelper;
77
import io.clientcore.core.implementation.instrumentation.NoopMeter;
88
import io.clientcore.core.instrumentation.Instrumentation;
99
import io.clientcore.core.instrumentation.InstrumentationAttributes;
1010
import io.clientcore.core.instrumentation.InstrumentationContext;
1111
import io.clientcore.core.instrumentation.InstrumentationOptions;
1212
import io.clientcore.core.instrumentation.LibraryInstrumentationOptions;
1313
import io.clientcore.core.instrumentation.metrics.Meter;
14+
import io.clientcore.core.instrumentation.tracing.Span;
15+
import io.clientcore.core.instrumentation.tracing.SpanKind;
1416
import io.clientcore.core.instrumentation.tracing.TraceContextPropagator;
1517
import io.clientcore.core.instrumentation.tracing.Tracer;
1618

1719
import java.util.Map;
18-
import java.util.Objects;
1920

2021
/**
2122
* Fallback implementation of {@link Instrumentation} which implements basic correlation and context propagation
@@ -26,6 +27,8 @@ public class FallbackInstrumentation implements Instrumentation {
2627

2728
private final InstrumentationOptions instrumentationOptions;
2829
private final LibraryInstrumentationOptions libraryOptions;
30+
private final boolean allowNestedSpans;
31+
private final boolean isTracingEnabled;
2932

3033
/**
3134
* Creates a new instance of {@link FallbackInstrumentation}.
@@ -36,6 +39,9 @@ public FallbackInstrumentation(InstrumentationOptions instrumentationOptions,
3639
LibraryInstrumentationOptions libraryOptions) {
3740
this.instrumentationOptions = instrumentationOptions;
3841
this.libraryOptions = libraryOptions;
42+
this.allowNestedSpans = libraryOptions != null
43+
&& LibraryInstrumentationOptionsAccessHelper.isSpanSuppressionDisabled(libraryOptions);
44+
this.isTracingEnabled = instrumentationOptions == null || instrumentationOptions.isTracingEnabled();
3945
}
4046

4147
/**
@@ -58,15 +64,12 @@ public Meter createMeter() {
5864
return NoopMeter.INSTANCE;
5965
}
6066

67+
/**
68+
* {@inheritDoc}
69+
*/
6170
@Override
6271
public InstrumentationAttributes createAttributes(Map<String, Object> attributes) {
63-
if (attributes != null) {
64-
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
65-
Objects.requireNonNull(entry.getKey(), "attribute key cannot be null.");
66-
Objects.requireNonNull(entry.getValue(), "attribute value cannot be null.");
67-
}
68-
}
69-
return NoopAttributes.INSTANCE;
72+
return new FallbackAttributes(attributes);
7073
}
7174

7275
/**
@@ -94,4 +97,36 @@ public <T> InstrumentationContext createInstrumentationContext(T context) {
9497
return FallbackSpanContext.INVALID;
9598
}
9699
}
100+
101+
/**
102+
* {@inheritDoc}
103+
*/
104+
@Override
105+
public boolean shouldInstrument(SpanKind spanKind, InstrumentationContext context) {
106+
if (!isTracingEnabled) {
107+
return false;
108+
}
109+
110+
if (allowNestedSpans) {
111+
return true;
112+
}
113+
114+
return spanKind != tryGetSpanKind(context);
115+
}
116+
117+
/**
118+
* Retrieves the span kind from the given context if and only if the context is a {@link FallbackSpanContext}
119+
* i.e. was created by this instrumentation.
120+
* @param context the context to get the span kind from
121+
* @return the span kind or {@code null} if the context is not recognized
122+
*/
123+
private SpanKind tryGetSpanKind(InstrumentationContext context) {
124+
if (context instanceof FallbackSpanContext) {
125+
Span span = context.getSpan();
126+
if (span instanceof FallbackSpan) {
127+
return ((FallbackSpan) span).getSpanKind();
128+
}
129+
}
130+
return null;
131+
}
97132
}

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/instrumentation/fallback/FallbackSpan.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.clientcore.core.instrumentation.InstrumentationContext;
77
import io.clientcore.core.instrumentation.logging.ClientLogger;
88
import io.clientcore.core.instrumentation.tracing.Span;
9+
import io.clientcore.core.instrumentation.tracing.SpanKind;
910
import io.clientcore.core.instrumentation.tracing.TracingScope;
1011

1112
import static io.clientcore.core.implementation.instrumentation.AttributeKeys.ERROR_TYPE_KEY;
@@ -18,11 +19,14 @@ final class FallbackSpan implements Span {
1819
private final ClientLogger.LoggingEvent log;
1920
private final long startTime;
2021
private final FallbackSpanContext spanContext;
22+
private final SpanKind kind;
2123
private String errorType;
2224

23-
FallbackSpan(ClientLogger.LoggingEvent log, FallbackSpanContext parentSpanContext, boolean isRecording) {
25+
FallbackSpan(ClientLogger.LoggingEvent log, SpanKind spanKind, FallbackSpanContext parentSpanContext,
26+
boolean isRecording) {
2427
this.log = log;
2528
this.startTime = isRecording ? System.nanoTime() : 0;
29+
this.kind = spanKind;
2630
this.spanContext = FallbackSpanContext.fromParent(parentSpanContext, isRecording, this);
2731
if (log != null && log.isEnabled()) {
2832
this.log.addKeyValue(TRACE_ID_KEY, spanContext.getTraceId())
@@ -77,6 +81,10 @@ public void end(Throwable error) {
7781
log.log(null);
7882
}
7983

84+
public SpanKind getSpanKind() {
85+
return kind;
86+
}
87+
8088
/**
8189
* {@inheritDoc}
8290
*/

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/instrumentation/fallback/FallbackSpanBuilder.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
package io.clientcore.core.implementation.instrumentation.fallback;
55

6+
import io.clientcore.core.instrumentation.InstrumentationAttributes;
67
import io.clientcore.core.instrumentation.InstrumentationContext;
78
import io.clientcore.core.instrumentation.logging.ClientLogger;
89
import io.clientcore.core.instrumentation.tracing.Span;
910
import io.clientcore.core.instrumentation.tracing.SpanBuilder;
1011
import io.clientcore.core.instrumentation.tracing.SpanKind;
1112

13+
import java.util.Map;
14+
1215
import static io.clientcore.core.implementation.instrumentation.AttributeKeys.SPAN_KIND_KEY;
1316
import static io.clientcore.core.implementation.instrumentation.AttributeKeys.SPAN_NAME_KEY;
1417
import static io.clientcore.core.implementation.instrumentation.AttributeKeys.SPAN_PARENT_ID_KEY;
@@ -17,15 +20,18 @@ final class FallbackSpanBuilder implements SpanBuilder {
1720
static final FallbackSpanBuilder NOOP = new FallbackSpanBuilder();
1821
private final ClientLogger.LoggingEvent log;
1922
private final FallbackSpanContext parentSpanContext;
23+
private final SpanKind spanKind;
2024

2125
private FallbackSpanBuilder() {
2226
this.log = null;
2327
this.parentSpanContext = FallbackSpanContext.INVALID;
28+
this.spanKind = null;
2429
}
2530

2631
FallbackSpanBuilder(ClientLogger logger, String spanName, SpanKind spanKind,
2732
InstrumentationContext instrumentationContext) {
2833
this.parentSpanContext = FallbackSpanContext.fromInstrumentationContext(instrumentationContext);
34+
this.spanKind = spanKind;
2935
this.log = logger.atVerbose();
3036
if (log.isEnabled()) {
3137
log.addKeyValue(SPAN_NAME_KEY, spanName).addKeyValue(SPAN_KIND_KEY, spanKind.name());
@@ -46,13 +52,26 @@ public SpanBuilder setAttribute(String key, Object value) {
4652
return this;
4753
}
4854

55+
/**
56+
* {@inheritDoc}
57+
*/
58+
@Override
59+
public SpanBuilder setAllAttributes(InstrumentationAttributes attributes) {
60+
if (log != null && attributes instanceof FallbackAttributes) {
61+
for (Map.Entry<String, Object> entry : ((FallbackAttributes) attributes).getAttributes().entrySet()) {
62+
log.addKeyValue(entry.getKey(), entry.getValue());
63+
}
64+
}
65+
return this;
66+
}
67+
4968
/**
5069
* {@inheritDoc}
5170
*/
5271
@Override
5372
public Span startSpan() {
5473
if (log != null) {
55-
return new FallbackSpan(log, parentSpanContext, log.isEnabled());
74+
return new FallbackSpan(log, spanKind, parentSpanContext, log.isEnabled());
5675
}
5776

5877
return Span.noop();

0 commit comments

Comments
 (0)