Skip to content

Commit c6b15e7

Browse files
committed
merge
2 parents 370a516 + 8cde80a commit c6b15e7

File tree

49 files changed

+2152
-157
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2152
-157
lines changed

README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@ This package includes the instrumentation agent as well as
3939
instrumentations for all supported libraries and all available data exporters.
4040
The package provides a completely automatic, out-of-the-box experience.
4141

42-
*Note: There are 2.x releases and 1.x releases. The 2.0 release included significant breaking
43-
changes, the details of which can be found in the [release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/tag/v2.0.0).
44-
It is recommended to use the latest 2.x release which will have the latest features and improvements.
45-
1.x will receive security patches for a limited time and will not include other bug fixes and
46-
enhancements.*
47-
48-
4942
Enable the instrumentation agent using the `-javaagent` flag to the JVM.
5043

5144
```

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import io.opentelemetry.context.Context;
1313
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
1414
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
15+
import io.opentelemetry.semconv.AttributeKeyTemplate;
1516
import java.util.Collection;
17+
import java.util.Map;
1618

1719
/**
1820
* Extractor of <a
@@ -38,6 +40,8 @@ public final class SqlClientAttributesExtractor<REQUEST, RESPONSE>
3840
AttributeKey.stringKey("db.collection.name");
3941
private static final AttributeKey<Long> DB_OPERATION_BATCH_SIZE =
4042
AttributeKey.longKey("db.operation.batch.size");
43+
private static final AttributeKeyTemplate<String> DB_QUERY_PARAMETER =
44+
AttributeKeyTemplate.stringKeyTemplate("db.query.parameter");
4145

4246
/** Creates the SQL client attributes extractor with default configuration. */
4347
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
@@ -58,14 +62,18 @@ public static <REQUEST, RESPONSE> SqlClientAttributesExtractorBuilder<REQUEST, R
5862

5963
private final AttributeKey<String> oldSemconvTableAttribute;
6064
private final boolean statementSanitizationEnabled;
65+
private final boolean captureQueryParameters;
6166

6267
SqlClientAttributesExtractor(
6368
SqlClientAttributesGetter<REQUEST, RESPONSE> getter,
6469
AttributeKey<String> oldSemconvTableAttribute,
65-
boolean statementSanitizationEnabled) {
70+
boolean statementSanitizationEnabled,
71+
boolean captureQueryParameters) {
6672
super(getter);
6773
this.oldSemconvTableAttribute = oldSemconvTableAttribute;
68-
this.statementSanitizationEnabled = statementSanitizationEnabled;
74+
// capturing query parameters disables statement sanitization
75+
this.statementSanitizationEnabled = !captureQueryParameters && statementSanitizationEnabled;
76+
this.captureQueryParameters = captureQueryParameters;
6977
}
7078

7179
@Override
@@ -78,6 +86,9 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
7886
return;
7987
}
8088

89+
Long batchSize = getter.getBatchSize(request);
90+
boolean isBatch = batchSize != null && batchSize > 1;
91+
8192
if (SemconvStability.emitOldDatabaseSemconv()) {
8293
if (rawQueryTexts.size() == 1) { // for backcompat(?)
8394
String rawQueryText = rawQueryTexts.iterator().next();
@@ -95,8 +106,6 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
95106
}
96107

97108
if (SemconvStability.emitStableDatabaseSemconv()) {
98-
Long batchSize = getter.getBatchSize(request);
99-
boolean isBatch = batchSize != null && batchSize > 1;
100109
if (isBatch) {
101110
internalSet(attributes, DB_OPERATION_BATCH_SIZE, batchSize);
102111
}
@@ -127,6 +136,20 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
127136
}
128137
}
129138
}
139+
140+
Map<String, String> queryParameters = getter.getQueryParameters(request);
141+
setQueryParameters(attributes, isBatch, queryParameters);
142+
}
143+
144+
private void setQueryParameters(
145+
AttributesBuilder attributes, boolean isBatch, Map<String, String> queryParameters) {
146+
if (captureQueryParameters && !isBatch && queryParameters != null) {
147+
for (Map.Entry<String, String> entry : queryParameters.entrySet()) {
148+
String key = entry.getKey();
149+
String value = entry.getValue();
150+
internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value);
151+
}
152+
}
130153
}
131154

132155
// String.join is not available on android

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public final class SqlClientAttributesExtractorBuilder<REQUEST, RESPONSE> {
2020
final SqlClientAttributesGetter<REQUEST, RESPONSE> getter;
2121
AttributeKey<String> oldSemconvTableAttribute = DB_SQL_TABLE;
2222
boolean statementSanitizationEnabled = true;
23+
boolean captureQueryParameters = false;
2324

2425
SqlClientAttributesExtractorBuilder(SqlClientAttributesGetter<REQUEST, RESPONSE> getter) {
2526
this.getter = getter;
@@ -48,12 +49,27 @@ public SqlClientAttributesExtractorBuilder<REQUEST, RESPONSE> setStatementSaniti
4849
return this;
4950
}
5051

52+
/**
53+
* Sets whether the query parameters should be captured as span attributes named {@code
54+
* db.query.parameter.<key>}. Enabling this option disables the statement sanitization. Disabled
55+
* by default.
56+
*
57+
* <p>WARNING: captured query parameters may contain sensitive information such as passwords,
58+
* personally identifiable information or protected health info.
59+
*/
60+
@CanIgnoreReturnValue
61+
public SqlClientAttributesExtractorBuilder<REQUEST, RESPONSE> setCaptureQueryParameters(
62+
boolean captureQueryParameters) {
63+
this.captureQueryParameters = captureQueryParameters;
64+
return this;
65+
}
66+
5167
/**
5268
* Returns a new {@link SqlClientAttributesExtractor} with the settings of this {@link
5369
* SqlClientAttributesExtractorBuilder}.
5470
*/
5571
public AttributesExtractor<REQUEST, RESPONSE> build() {
5672
return new SqlClientAttributesExtractor<>(
57-
getter, oldSemconvTableAttribute, statementSanitizationEnabled);
73+
getter, oldSemconvTableAttribute, statementSanitizationEnabled, captureQueryParameters);
5874
}
5975
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import static java.util.Collections.singleton;
1010

1111
import java.util.Collection;
12+
import java.util.Collections;
13+
import java.util.Map;
1214
import javax.annotation.Nullable;
1315

1416
/**
@@ -66,4 +68,9 @@ default Collection<String> getRawQueryTexts(REQUEST request) {
6668
default Long getBatchSize(REQUEST request) {
6769
return null;
6870
}
71+
72+
// TODO: make this required to implement
73+
default Map<String, String> getQueryParameters(REQUEST request) {
74+
return Collections.emptyMap();
75+
}
6976
}

instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.api.incubator.semconv.db;
77

88
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
9+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER;
910
import static java.util.Collections.emptySet;
1011
import static java.util.Collections.singleton;
1112
import static org.assertj.core.api.Assertions.entry;
@@ -62,6 +63,14 @@ public Long getBatchSize(Map<String, Object> map) {
6263
return read(map, "db.operation.batch.size", Long.class);
6364
}
6465

66+
@SuppressWarnings("unchecked")
67+
@Override
68+
public Map<String, String> getQueryParameters(Map<String, Object> map) {
69+
Map<String, String> parameters =
70+
(Map<String, String>) read(map, "db.query.parameter", Map.class);
71+
return parameters != null ? parameters : Collections.emptyMap();
72+
}
73+
6574
protected String read(Map<String, Object> map, String key) {
6675
return read(map, key, String.class);
6776
}
@@ -387,4 +396,74 @@ void shouldIgnoreBatchSizeOne() {
387396

388397
assertThat(endAttributes.build().isEmpty()).isTrue();
389398
}
399+
400+
@Test
401+
void shouldExtractQueryParameters() {
402+
// given
403+
Map<String, Object> request = new HashMap<>();
404+
request.put("db.name", "potatoes");
405+
// a query with prepared parameters and parameters to sanitize
406+
request.put(
407+
"db.statement",
408+
"SELECT col FROM table WHERE field1=? AND field2='A' AND field3=? AND field4=2");
409+
// a prepared parameters map
410+
Map<String, String> parameterMap = new HashMap<>();
411+
parameterMap.put("0", "'a'");
412+
parameterMap.put("1", "1");
413+
request.put("db.query.parameter", parameterMap);
414+
415+
Context context = Context.root();
416+
417+
AttributesExtractor<Map<String, Object>, Void> underTest =
418+
SqlClientAttributesExtractor.builder(new TestAttributesGetter())
419+
.setCaptureQueryParameters(true)
420+
.build();
421+
422+
// when
423+
AttributesBuilder startAttributes = Attributes.builder();
424+
underTest.onStart(startAttributes, context, request);
425+
426+
AttributesBuilder endAttributes = Attributes.builder();
427+
underTest.onEnd(endAttributes, context, request, null, null);
428+
429+
String prefix = DB_QUERY_PARAMETER.getAttributeKey("").getKey();
430+
Attributes queryParameterAttributes =
431+
startAttributes.removeIf(attribute -> !attribute.getKey().startsWith(prefix)).build();
432+
433+
// then
434+
assertThat(queryParameterAttributes)
435+
.containsOnly(
436+
entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"),
437+
entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1"));
438+
439+
assertThat(endAttributes.build().isEmpty()).isTrue();
440+
}
441+
442+
@Test
443+
void shouldNotExtractQueryParametersForBatch() {
444+
// given
445+
Map<String, Object> request = new HashMap<>();
446+
request.put("db.name", "potatoes");
447+
request.put("db.statements", singleton("INSERT INTO potato VALUES(?)"));
448+
request.put("db.operation.batch.size", 2L);
449+
request.put("db.query.parameter", Collections.singletonMap("0", "1"));
450+
451+
Context context = Context.root();
452+
453+
AttributesExtractor<Map<String, Object>, Void> underTest =
454+
SqlClientAttributesExtractor.builder(new TestMultiAttributesGetter())
455+
.setCaptureQueryParameters(true)
456+
.build();
457+
458+
// when
459+
AttributesBuilder startAttributes = Attributes.builder();
460+
underTest.onStart(startAttributes, context, request);
461+
462+
AttributesBuilder endAttributes = Attributes.builder();
463+
underTest.onEnd(endAttributes, context, request, null, null);
464+
465+
// then
466+
assertThat(startAttributes.build()).doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0"));
467+
assertThat(endAttributes.build().isEmpty()).isTrue();
468+
}
390469
}

instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,20 @@ testing {
141141
}
142142
}
143143

144+
val s3CrtTest by registering(JvmTestSuite::class) {
145+
dependencies {
146+
if (latestDepTest) {
147+
implementation("software.amazon.awssdk:s3:latest.release")
148+
implementation("software.amazon.awssdk.crt:aws-crt:latest.release")
149+
} else {
150+
implementation("software.amazon.awssdk:s3:2.27.21")
151+
implementation("software.amazon.awssdk.crt:aws-crt:0.30.11")
152+
}
153+
implementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:library"))
154+
implementation("org.testcontainers:localstack")
155+
}
156+
}
157+
144158
val testBedrockRuntime by registering(JvmTestSuite::class) {
145159
dependencies {
146160
implementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.returns;
10+
11+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
13+
import java.util.concurrent.CompletableFuture;
14+
import net.bytebuddy.asm.Advice;
15+
import net.bytebuddy.description.type.TypeDescription;
16+
import net.bytebuddy.matcher.ElementMatcher;
17+
18+
public class AwsAsyncClientHandlerInstrumentation implements TypeInstrumentation {
19+
20+
@Override
21+
public ElementMatcher<TypeDescription> typeMatcher() {
22+
// This class is used internally by the aws async clients to execute requests. Alternatively
23+
// we could instrument all methods that return a CompletableFuture in classes whose name ends
24+
// with "AsyncClient" that extend software.amazon.awssdk.core.SdkClient
25+
return named("software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler");
26+
}
27+
28+
@Override
29+
public void transform(TypeTransformer transformer) {
30+
transformer.applyAdviceToMethod(
31+
named("execute").and(returns(CompletableFuture.class)),
32+
this.getClass().getName() + "$WrapFutureAdvice");
33+
}
34+
35+
@SuppressWarnings("unused")
36+
public static class WrapFutureAdvice {
37+
38+
@Advice.OnMethodExit(suppress = Throwable.class)
39+
public static void methodExit(@Advice.Return(readOnly = false) CompletableFuture<?> future) {
40+
// propagate context into CompletableFuture returned from aws async client methods
41+
future = CompletableFutureWrapper.wrap(future);
42+
}
43+
}
44+
}

instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55

66
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
77

8+
import static java.util.Arrays.asList;
9+
810
import com.google.auto.service.AutoService;
911
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
1012
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1114
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
1215
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
1316
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
17+
import java.util.List;
1418

1519
@AutoService(InstrumentationModule.class)
1620
public class AwsSdkInstrumentationModule extends AbstractAwsSdkInstrumentationModule {
@@ -35,6 +39,12 @@ public void injectClasses(ClassInjector injector) {
3539
.inject(InjectionMode.CLASS_ONLY);
3640
}
3741

42+
@Override
43+
public List<TypeInstrumentation> typeInstrumentations() {
44+
return asList(
45+
new ResourceInjectingTypeInstrumentation(), new AwsAsyncClientHandlerInstrumentation());
46+
}
47+
3848
@Override
3949
void doTransform(TypeTransformer transformer) {
4050
// Nothing to transform, this type instrumentation is only used for injecting resources.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
7+
8+
import io.opentelemetry.context.Context;
9+
import io.opentelemetry.context.Scope;
10+
import java.util.concurrent.CompletableFuture;
11+
12+
public final class CompletableFutureWrapper {
13+
14+
private CompletableFutureWrapper() {}
15+
16+
public static <T> CompletableFuture<T> wrap(CompletableFuture<T> future) {
17+
return wrap(future, Context.current());
18+
}
19+
20+
public static <T> CompletableFuture<T> wrap(CompletableFuture<T> future, Context context) {
21+
CompletableFuture<T> result = new CompletableFuture<>();
22+
future.whenComplete(
23+
(T value, Throwable throwable) -> {
24+
try (Scope ignored = context.makeCurrent()) {
25+
if (throwable != null) {
26+
result.completeExceptionally(throwable);
27+
} else {
28+
result.complete(value);
29+
}
30+
}
31+
});
32+
33+
return result;
34+
}
35+
}

0 commit comments

Comments
 (0)