Skip to content

Commit 3d7022d

Browse files
committed
Add unit and integration tests for DB exception event extractor
1 parent fcbda86 commit 3d7022d

13 files changed

Lines changed: 256 additions & 92 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.db.internal;
7+
8+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsLogs;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
10+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
11+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
12+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
13+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
14+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
15+
16+
import io.opentelemetry.api.logs.Severity;
17+
import io.opentelemetry.context.Context;
18+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
19+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
20+
import io.opentelemetry.sdk.logs.data.LogRecordData;
21+
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
22+
import java.util.List;
23+
import org.assertj.core.api.AbstractAssert;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.extension.RegisterExtension;
26+
27+
class DbExceptionEventExtractorsTest {
28+
29+
@RegisterExtension
30+
static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create();
31+
32+
@Test
33+
void dbClientExceptionLog() {
34+
InstrumenterBuilder<String, String> builder =
35+
Instrumenter.builder(otelTesting.getOpenTelemetry(), "test", unused -> "span");
36+
DbExceptionEventExtractors.setDbClientExceptionEventExtractor(builder);
37+
Instrumenter<String, String> instrumenter = builder.buildInstrumenter();
38+
39+
Context context = instrumenter.start(Context.root(), "request");
40+
IllegalStateException error = new IllegalStateException("test");
41+
instrumenter.end(context, "request", "response", error);
42+
43+
List<LogRecordData> logs = otelTesting.getLogRecords();
44+
if (emitExceptionAsLogs()) {
45+
assertThat(logs).hasSize(1);
46+
assertThat(logs.get(0))
47+
.hasSeverity(Severity.WARN)
48+
.hasEventName("db.client.operation.exception")
49+
.hasAttributesSatisfyingExactly(
50+
equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"),
51+
equalTo(EXCEPTION_MESSAGE, "test"),
52+
satisfies(EXCEPTION_STACKTRACE, AbstractAssert::isNotNull));
53+
} else {
54+
assertThat(logs).isEmpty();
55+
}
56+
}
57+
}

instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraTelemetryBuilder.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor;
1616
import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor;
1717
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
18+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
1819
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
1920

2021
/** A builder of {@link CassandraTelemetry}. */
@@ -58,18 +59,19 @@ private static Instrumenter<CassandraRequest, ExecutionInfo> createInstrumenter(
5859
OpenTelemetry openTelemetry, boolean querySanitizationEnabled) {
5960
CassandraSqlAttributesGetter attributesGetter = new CassandraSqlAttributesGetter();
6061

61-
return setDbClientExceptionEventExtractor(
62-
Instrumenter.<CassandraRequest, ExecutionInfo>builder(
63-
openTelemetry,
64-
INSTRUMENTATION_NAME,
65-
DbClientSpanNameExtractor.create(attributesGetter))
66-
.addAttributesExtractor(
67-
SqlClientAttributesExtractor.builder(attributesGetter)
68-
.setTableAttribute(DB_CASSANDRA_TABLE)
69-
.setQuerySanitizationEnabled(querySanitizationEnabled)
70-
.build())
71-
.addAttributesExtractor(new CassandraAttributesExtractor())
72-
.addOperationMetrics(DbClientMetrics.get()))
73-
.buildInstrumenter(SpanKindExtractor.alwaysClient());
62+
InstrumenterBuilder<CassandraRequest, ExecutionInfo> builder =
63+
Instrumenter.<CassandraRequest, ExecutionInfo>builder(
64+
openTelemetry,
65+
INSTRUMENTATION_NAME,
66+
DbClientSpanNameExtractor.create(attributesGetter))
67+
.addAttributesExtractor(
68+
SqlClientAttributesExtractor.builder(attributesGetter)
69+
.setTableAttribute(DB_CASSANDRA_TABLE)
70+
.setQuerySanitizationEnabled(querySanitizationEnabled)
71+
.build())
72+
.addAttributesExtractor(new CassandraAttributesExtractor())
73+
.addOperationMetrics(DbClientMetrics.get());
74+
setDbClientExceptionEventExtractor(builder);
75+
return builder.buildInstrumenter(SpanKindExtractor.alwaysClient());
7476
}
7577
}

instrumentation/clickhouse/clickhouse-client-common-0.5/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/clickhouse/client/common/v0_5/ClickHouseInstrumenterFactory.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor;
1414
import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor;
1515
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
16+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
1617
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
1718
import java.util.function.Function;
1819

@@ -24,20 +25,21 @@ public static Instrumenter<ClickHouseDbRequest, Void> createInstrumenter(
2425
ClickHouseAttributesGetter dbAttributesGetter =
2526
new ClickHouseAttributesGetter(errorCodeExtractor);
2627

27-
return setDbClientExceptionEventExtractor(
28-
Instrumenter.<ClickHouseDbRequest, Void>builder(
29-
GlobalOpenTelemetry.get(),
30-
instrumenterName,
31-
DbClientSpanNameExtractor.createWithGenericOldSpanName(dbAttributesGetter))
32-
.addAttributesExtractor(
33-
SqlClientAttributesExtractor.builder(dbAttributesGetter)
34-
.setTableAttribute(null)
35-
.setQuerySanitizationEnabled(
36-
DbConfig.isQuerySanitizationEnabled(
37-
GlobalOpenTelemetry.get(), "clickhouse"))
38-
.build())
39-
.addOperationMetrics(DbClientMetrics.get()))
40-
.buildInstrumenter(SpanKindExtractor.alwaysClient());
28+
InstrumenterBuilder<ClickHouseDbRequest, Void> builder =
29+
Instrumenter.<ClickHouseDbRequest, Void>builder(
30+
GlobalOpenTelemetry.get(),
31+
instrumenterName,
32+
DbClientSpanNameExtractor.createWithGenericOldSpanName(dbAttributesGetter))
33+
.addAttributesExtractor(
34+
SqlClientAttributesExtractor.builder(dbAttributesGetter)
35+
.setTableAttribute(null)
36+
.setQuerySanitizationEnabled(
37+
DbConfig.isQuerySanitizationEnabled(
38+
GlobalOpenTelemetry.get(), "clickhouse"))
39+
.build())
40+
.addOperationMetrics(DbClientMetrics.get());
41+
setDbClientExceptionEventExtractor(builder);
42+
return builder.buildInstrumenter(SpanKindExtractor.alwaysClient());
4143
}
4244

4345
private ClickHouseInstrumenterFactory() {}

instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchRestInstrumenterFactory.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics;
1313
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
1414
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
15+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
1516
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
1617
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
1718
import java.util.List;
@@ -43,15 +44,15 @@ public static Instrumenter<ElasticsearchRestRequest, Response> create(
4344
spanNameExtractorTransformer.apply(
4445
new ElasticsearchSpanNameExtractor(dbClientAttributesGetter));
4546

46-
return setDbClientExceptionEventExtractor(
47-
Instrumenter.<ElasticsearchRestRequest, Response>builder(
48-
openTelemetry, instrumentationName, spanNameExtractor)
49-
.addAttributesExtractor(
50-
DbClientAttributesExtractor.create(dbClientAttributesGetter))
51-
.addAttributesExtractor(esClientAttributesExtractor)
52-
.addAttributesExtractors(attributesExtractors)
53-
.addOperationMetrics(DbClientMetrics.get()))
54-
.buildInstrumenter(SpanKindExtractor.alwaysClient());
47+
InstrumenterBuilder<ElasticsearchRestRequest, Response> builder =
48+
Instrumenter.<ElasticsearchRestRequest, Response>builder(
49+
openTelemetry, instrumentationName, spanNameExtractor)
50+
.addAttributesExtractor(DbClientAttributesExtractor.create(dbClientAttributesGetter))
51+
.addAttributesExtractor(esClientAttributesExtractor)
52+
.addAttributesExtractors(attributesExtractors)
53+
.addOperationMetrics(DbClientMetrics.get());
54+
setDbClientExceptionEventExtractor(builder);
55+
return builder.buildInstrumenter(SpanKindExtractor.alwaysClient());
5556
}
5657

5758
private ElasticsearchRestInstrumenterFactory() {}

instrumentation/jdbc/javaagent/build.gradle.kts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@ tasks {
111111
jvmArgs("-Dotel.instrumentation.jdbc.experimental.capture-query-parameters=true")
112112
}
113113

114+
val testExceptionSignalLogs by registering(Test::class) {
115+
testClassesDirs = sourceSets.test.get().output.classesDirs
116+
classpath = sourceSets.test.get().runtimeClasspath
117+
118+
filter {
119+
excludeTestsMatching("SlickTest")
120+
excludeTestsMatching("SqlCommenterTest")
121+
excludeTestsMatching("PreparedStatementParametersTest")
122+
}
123+
jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true")
124+
jvmArgs("-Dotel.semconv.exception.signal.preview=logs")
125+
systemProperty("metadataConfig", "otel.semconv.exception.signal.preview=logs")
126+
}
127+
114128
test {
115129
filter {
116130
excludeTestsMatching("SlickTest")
@@ -126,6 +140,7 @@ tasks {
126140
dependsOn(testStableSemconv)
127141
dependsOn(testSlickStableSemconv)
128142
dependsOn(testCaptureParameters)
143+
dependsOn(testExceptionSignalLogs)
129144
}
130145
}
131146

instrumentation/jdbc/library/build.gradle.kts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,15 @@ tasks {
7070
jvmArgs("-Dotel.semconv-stability.opt-in=database")
7171
}
7272

73+
val testExceptionSignalLogs by registering(Test::class) {
74+
testClassesDirs = sourceSets.test.get().output.classesDirs
75+
classpath = sourceSets.test.get().runtimeClasspath
76+
77+
jvmArgs("-Dotel.semconv.exception.signal.preview=logs")
78+
}
79+
7380
check {
74-
dependsOn(testStableSemconv)
81+
dependsOn(testStableSemconv, testExceptionSignalLogs)
7582
}
7683
}
7784

instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor;
1919
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
2020
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
21+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
2122
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
2223
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
2324
import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo;
@@ -75,18 +76,19 @@ public static Instrumenter<DbRequest, Void> createStatementInstrumenter(
7576
boolean querySanitizationEnabled,
7677
boolean captureQueryParameters) {
7778
JdbcAttributesGetter getter = new JdbcAttributesGetter();
78-
return setDbClientExceptionEventExtractor(
79-
Instrumenter.<DbRequest, Void>builder(
80-
openTelemetry, INSTRUMENTATION_NAME, DbClientSpanNameExtractor.create(getter))
81-
.addAttributesExtractor(
82-
SqlClientAttributesExtractor.builder(getter)
83-
.setQuerySanitizationEnabled(querySanitizationEnabled)
84-
.setCaptureQueryParameters(captureQueryParameters)
85-
.build())
86-
.addAttributesExtractors(extractors)
87-
.addOperationMetrics(DbClientMetrics.get())
88-
.setEnabled(enabled))
89-
.buildInstrumenter(SpanKindExtractor.alwaysClient());
79+
InstrumenterBuilder<DbRequest, Void> builder =
80+
Instrumenter.<DbRequest, Void>builder(
81+
openTelemetry, INSTRUMENTATION_NAME, DbClientSpanNameExtractor.create(getter))
82+
.addAttributesExtractor(
83+
SqlClientAttributesExtractor.builder(getter)
84+
.setQuerySanitizationEnabled(querySanitizationEnabled)
85+
.setCaptureQueryParameters(captureQueryParameters)
86+
.build())
87+
.addAttributesExtractors(extractors)
88+
.addOperationMetrics(DbClientMetrics.get())
89+
.setEnabled(enabled);
90+
setDbClientExceptionEventExtractor(builder);
91+
return builder.buildInstrumenter(SpanKindExtractor.alwaysClient());
9092
}
9193

9294
public static Instrumenter<DataSource, DbInfo> createDataSourceInstrumenter(

instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package io.opentelemetry.instrumentation.jdbc.testing;
77

8+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsLogs;
9+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsSpanEvents;
810
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv;
911
import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions;
1012
import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric;
@@ -14,11 +16,15 @@
1416
import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps;
1517
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
1618
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
19+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
1720
import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE;
1821
import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE;
1922
import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_SUMMARY;
2023
import static io.opentelemetry.semconv.DbAttributes.DB_STORED_PROCEDURE_NAME;
2124
import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME;
25+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
26+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
27+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
2228
import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
2329
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING;
2430
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME;
@@ -30,21 +36,25 @@
3036
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues.HSQLDB;
3137
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues.OTHER_SQL;
3238
import static java.util.Arrays.asList;
39+
import static java.util.stream.Collectors.toList;
3340
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3441

3542
import com.google.common.collect.ImmutableMap;
3643
import com.google.common.collect.Maps;
3744
import com.mchange.v2.c3p0.ComboPooledDataSource;
3845
import com.zaxxer.hikari.HikariConfig;
3946
import com.zaxxer.hikari.HikariDataSource;
47+
import io.opentelemetry.api.logs.Severity;
4048
import io.opentelemetry.api.trace.SpanKind;
4149
import io.opentelemetry.instrumentation.jdbc.TestConnection;
4250
import io.opentelemetry.instrumentation.jdbc.TestDriver;
4351
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
4452
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
53+
import io.opentelemetry.sdk.logs.data.LogRecordData;
4554
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
4655
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
4756
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
57+
import io.opentelemetry.sdk.trace.data.StatusData;
4858
import java.beans.PropertyVetoException;
4959
import java.io.Closeable;
5060
import java.sql.CallableStatement;
@@ -66,6 +76,7 @@
6676
import org.apache.derby.jdbc.EmbeddedDataSource;
6777
import org.apache.derby.jdbc.EmbeddedDriver;
6878
import org.assertj.core.api.ThrowingConsumer;
79+
import org.awaitility.Awaitility;
6980
import org.h2.jdbcx.JdbcDataSource;
7081
import org.hsqldb.jdbc.JDBCDriver;
7182
import org.junit.jupiter.api.BeforeAll;
@@ -418,6 +429,65 @@ isCallStatement && emitStableDatabaseSemconv()
418429
testing(), "io.opentelemetry.jdbc", DB_SYSTEM_NAME, DB_NAMESPACE, DB_QUERY_SUMMARY);
419430
}
420431

432+
@Test
433+
void testFailedStatement() throws SQLException {
434+
Connection connection = wrap(new org.h2.Driver().connect(JDBC_URLS.get("h2"), null));
435+
Statement statement = connection.createStatement();
436+
cleanup.deferCleanup(statement);
437+
438+
assertThatThrownBy(
439+
() ->
440+
testing()
441+
.runWithSpan(
442+
"parent",
443+
() -> statement.executeQuery("SELECT * FROM table_does_not_exist")))
444+
.isInstanceOf(SQLException.class);
445+
446+
testing()
447+
.waitAndAssertTraces(
448+
trace ->
449+
trace.hasSpansSatisfyingExactly(
450+
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
451+
span ->
452+
span.hasKind(SpanKind.CLIENT)
453+
.hasParent(trace.getSpan(0))
454+
.hasStatus(StatusData.error())
455+
.satisfies(
456+
spanData -> {
457+
if (emitExceptionAsSpanEvents()) {
458+
assertThat(spanData.getEvents()).hasSize(1);
459+
assertThat(spanData.getEvents().get(0).getName())
460+
.isEqualTo("exception");
461+
} else {
462+
assertThat(spanData.getEvents()).isEmpty();
463+
}
464+
})));
465+
466+
if (emitExceptionAsLogs()) {
467+
assertExceptionLog();
468+
}
469+
}
470+
471+
private void assertExceptionLog() {
472+
Awaitility.await()
473+
.untilAsserted(
474+
() -> {
475+
List<LogRecordData> logs =
476+
testing().logRecords().stream()
477+
.filter(log -> "db.client.operation.exception".equals(log.getEventName()))
478+
.collect(toList());
479+
480+
assertThat(logs).hasSize(1);
481+
assertThat(logs.get(0))
482+
.hasSeverity(Severity.WARN)
483+
.hasEventName("db.client.operation.exception")
484+
.hasAttributesSatisfyingExactly(
485+
satisfies(EXCEPTION_TYPE, val -> val.isNotNull()),
486+
satisfies(EXCEPTION_MESSAGE, val -> val.isNotNull()),
487+
satisfies(EXCEPTION_STACKTRACE, val -> val.isNotNull()));
488+
});
489+
}
490+
421491
static Stream<Arguments> preparedStatementStream() throws SQLException {
422492
return Stream.of(
423493
Arguments.of(

0 commit comments

Comments
 (0)