Skip to content

Commit 37531a0

Browse files
author
Mateusz Rzeszutek
authored
Fill additional db.* attributes on DataSource#getConnection() (open-telemetry#8966)
1 parent da3c5be commit 37531a0

File tree

9 files changed

+186
-65
lines changed

9 files changed

+186
-65
lines changed

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919
import io.opentelemetry.instrumentation.jdbc.internal.JdbcNetAttributesGetter;
2020
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
2121
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
22+
import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo;
2223
import javax.sql.DataSource;
2324

2425
public final class JdbcSingletons {
2526
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc";
2627

2728
private static final Instrumenter<DbRequest, Void> STATEMENT_INSTRUMENTER;
28-
public static final Instrumenter<DataSource, Void> DATASOURCE_INSTRUMENTER =
29+
public static final Instrumenter<DataSource, DbInfo> DATASOURCE_INSTRUMENTER =
2930
createDataSourceInstrumenter(GlobalOpenTelemetry.get());
3031

3132
static {
@@ -56,7 +57,7 @@ public static Instrumenter<DbRequest, Void> statementInstrumenter() {
5657
return STATEMENT_INSTRUMENTER;
5758
}
5859

59-
public static Instrumenter<DataSource, Void> dataSourceInstrumenter() {
60+
public static Instrumenter<DataSource, DbInfo> dataSourceInstrumenter() {
6061
return DATASOURCE_INSTRUMENTER;
6162
}
6263

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/datasource/DataSourceInstrumentation.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
99
import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.dataSourceInstrumenter;
1010
import static net.bytebuddy.matcher.ElementMatchers.named;
11+
import static net.bytebuddy.matcher.ElementMatchers.returns;
1112

1213
import io.opentelemetry.context.Context;
1314
import io.opentelemetry.context.Scope;
15+
import io.opentelemetry.instrumentation.jdbc.internal.JdbcUtils;
1416
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
17+
import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo;
1518
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1619
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
20+
import java.sql.Connection;
1721
import javax.sql.DataSource;
1822
import net.bytebuddy.asm.Advice;
1923
import net.bytebuddy.description.type.TypeDescription;
@@ -29,7 +33,8 @@ public ElementMatcher<TypeDescription> typeMatcher() {
2933
@Override
3034
public void transform(TypeTransformer transformer) {
3135
transformer.applyAdviceToMethod(
32-
named("getConnection"), DataSourceInstrumentation.class.getName() + "$GetConnectionAdvice");
36+
named("getConnection").and(returns(named("java.sql.Connection"))),
37+
DataSourceInstrumentation.class.getName() + "$GetConnectionAdvice");
3338
}
3439

3540
@SuppressWarnings("unused")
@@ -47,21 +52,29 @@ public static void start(
4752
return;
4853
}
4954

50-
context = dataSourceInstrumenter().start(parentContext, ds);
51-
scope = context.makeCurrent();
55+
if (dataSourceInstrumenter().shouldStart(parentContext, ds)) {
56+
context = dataSourceInstrumenter().start(parentContext, ds);
57+
scope = context.makeCurrent();
58+
}
5259
}
5360

5461
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
5562
public static void stopSpan(
5663
@Advice.This DataSource ds,
64+
@Advice.Return Connection connection,
65+
@Advice.Thrown Throwable throwable,
5766
@Advice.Local("otelContext") Context context,
58-
@Advice.Local("otelScope") Scope scope,
59-
@Advice.Thrown Throwable throwable) {
67+
@Advice.Local("otelScope") Scope scope) {
6068
if (scope == null) {
6169
return;
6270
}
6371
scope.close();
64-
dataSourceInstrumenter().end(context, ds, null, throwable);
72+
DbInfo dbInfo = null;
73+
Connection realConnection = JdbcUtils.unwrapConnection(connection);
74+
if (realConnection != null) {
75+
dbInfo = JdbcUtils.extractDbInfo(realConnection);
76+
}
77+
dataSourceInstrumenter().end(context, ds, dbInfo, throwable);
6578
}
6679
}
6780
}

instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,10 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification {
580580
attributes {
581581
"$SemanticAttributes.CODE_NAMESPACE" datasource.class.name
582582
"$SemanticAttributes.CODE_FUNCTION" "getConnection"
583+
"$SemanticAttributes.DB_SYSTEM" system
584+
"$SemanticAttributes.DB_USER" { user == null | user == it }
585+
"$SemanticAttributes.DB_NAME" "jdbcunittest"
586+
"$SemanticAttributes.DB_CONNECTION_STRING" connectionString
583587
}
584588
}
585589
if (recursive) {
@@ -590,20 +594,24 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification {
590594
attributes {
591595
"$SemanticAttributes.CODE_NAMESPACE" datasource.class.name
592596
"$SemanticAttributes.CODE_FUNCTION" "getConnection"
597+
"$SemanticAttributes.DB_SYSTEM" system
598+
"$SemanticAttributes.DB_USER" { user == null | user == it }
599+
"$SemanticAttributes.DB_NAME" "jdbcunittest"
600+
"$SemanticAttributes.DB_CONNECTION_STRING" connectionString
593601
}
594602
}
595603
}
596604
}
597605
}
598606

599607
where:
600-
datasource | init
601-
new JdbcDataSource() | { ds -> ds.setURL(jdbcUrls.get("h2")) }
602-
new EmbeddedDataSource() | { ds -> ds.jdbcurl = jdbcUrls.get("derby") }
603-
cpDatasources.get("hikari").get("h2") | null
604-
cpDatasources.get("hikari").get("derby") | null
605-
cpDatasources.get("c3p0").get("h2") | null
606-
cpDatasources.get("c3p0").get("derby") | null
608+
datasource | init | system | user | connectionString
609+
new JdbcDataSource() | { ds -> ds.setURL(jdbcUrls.get("h2")) } | "h2" | null | "h2:mem:"
610+
new EmbeddedDataSource() | { ds -> ds.jdbcurl = jdbcUrls.get("derby") } | "derby" | "APP" | "derby:memory:"
611+
cpDatasources.get("hikari").get("h2") | null | "h2" | null | "h2:mem:"
612+
cpDatasources.get("hikari").get("derby") | null | "derby" | "APP" | "derby:memory:"
613+
cpDatasources.get("c3p0").get("h2") | null | "h2" | null | "h2:mem:"
614+
cpDatasources.get("c3p0").get("derby") | null | "derby" | "APP" | "derby:memory:"
607615

608616
// Tomcat's pool doesn't work because the getConnection method is
609617
// implemented in a parent class that doesn't implement DataSource

instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
public class OpenTelemetryDataSource implements DataSource, AutoCloseable {
4646

4747
private final DataSource delegate;
48-
private final Instrumenter<DataSource, Void> dataSourceInstrumenter;
48+
private final Instrumenter<DataSource, DbInfo> dataSourceInstrumenter;
4949
private final Instrumenter<DbRequest, Void> statementInstrumenter;
5050
private volatile DbInfo cachedDbInfo;
5151

@@ -128,25 +128,29 @@ public void close() throws Exception {
128128
}
129129
}
130130

131-
private <T, E extends SQLException> T wrapCall(ThrowingSupplier<T, E> callable) throws E {
131+
private Connection wrapCall(ThrowingSupplier<Connection, SQLException> getConnection)
132+
throws SQLException {
132133
Context parentContext = Context.current();
133134

134-
if (!Span.fromContext(parentContext).getSpanContext().isValid()) {
135+
if (!Span.fromContext(parentContext).getSpanContext().isValid()
136+
|| !dataSourceInstrumenter.shouldStart(parentContext, delegate)) {
135137
// this instrumentation is already very noisy, and calls to getConnection outside of an
136138
// existing trace do not tend to be very interesting
137-
return callable.call();
139+
return getConnection.call();
138140
}
139141

140-
Context context = this.dataSourceInstrumenter.start(parentContext, delegate);
141-
T result;
142+
Context context = dataSourceInstrumenter.start(parentContext, delegate);
143+
Connection connection = null;
144+
Throwable error = null;
142145
try (Scope ignored = context.makeCurrent()) {
143-
result = callable.call();
146+
connection = getConnection.call();
147+
return connection;
144148
} catch (Throwable t) {
145-
this.dataSourceInstrumenter.end(context, delegate, null, t);
149+
error = t;
146150
throw t;
151+
} finally {
152+
dataSourceInstrumenter.end(context, delegate, getDbInfo(connection), error);
147153
}
148-
this.dataSourceInstrumenter.end(context, delegate, null, null);
149-
return result;
150154
}
151155

152156
private DbInfo getDbInfo(Connection connection) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesGetter;
99
import javax.sql.DataSource;
1010

11-
final class DataSourceCodeAttributesGetter implements CodeAttributesGetter<DataSource> {
11+
enum DataSourceCodeAttributesGetter implements CodeAttributesGetter<DataSource> {
12+
INSTANCE;
1213

1314
@Override
1415
public Class<?> getCodeClass(DataSource dataSource) {
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.instrumentation.jdbc.internal;
7+
8+
import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
9+
10+
import io.opentelemetry.api.common.AttributesBuilder;
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
13+
import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo;
14+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
15+
import javax.annotation.Nullable;
16+
import javax.sql.DataSource;
17+
18+
enum DataSourceDbAttributesExtractor implements AttributesExtractor<DataSource, DbInfo> {
19+
INSTANCE;
20+
21+
@Override
22+
public void onStart(AttributesBuilder attributes, Context parentContext, DataSource dataSource) {}
23+
24+
@Override
25+
public void onEnd(
26+
AttributesBuilder attributes,
27+
Context context,
28+
DataSource dataSource,
29+
@Nullable DbInfo dbInfo,
30+
@Nullable Throwable error) {
31+
if (dbInfo == null) {
32+
return;
33+
}
34+
internalSet(attributes, SemanticAttributes.DB_SYSTEM, dbInfo.getSystem());
35+
internalSet(attributes, SemanticAttributes.DB_USER, dbInfo.getUser());
36+
internalSet(attributes, SemanticAttributes.DB_NAME, getName(dbInfo));
37+
internalSet(attributes, SemanticAttributes.DB_CONNECTION_STRING, dbInfo.getShortUrl());
38+
}
39+
40+
private static String getName(DbInfo dbInfo) {
41+
String name = dbInfo.getName();
42+
return name == null ? dbInfo.getDb() : name;
43+
}
44+
}

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@
99
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1010
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
1111
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor;
12+
import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo;
1213
import javax.sql.DataSource;
1314

1415
/**
1516
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
1617
* any time.
1718
*/
1819
public final class DataSourceInstrumenterFactory {
20+
1921
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc";
20-
private static final DataSourceCodeAttributesGetter codeAttributesGetter =
21-
new DataSourceCodeAttributesGetter();
2222

23-
public static Instrumenter<DataSource, Void> createDataSourceInstrumenter(
23+
public static Instrumenter<DataSource, DbInfo> createDataSourceInstrumenter(
2424
OpenTelemetry openTelemetry) {
25-
return Instrumenter.<DataSource, Void>builder(
26-
openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(codeAttributesGetter))
27-
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
25+
DataSourceCodeAttributesGetter getter = DataSourceCodeAttributesGetter.INSTANCE;
26+
return Instrumenter.<DataSource, DbInfo>builder(
27+
openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(getter))
28+
.addAttributesExtractor(CodeAttributesExtractor.create(getter))
29+
.addAttributesExtractor(DataSourceDbAttributesExtractor.INSTANCE)
2830
.buildInstrumenter();
2931
}
3032

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,19 @@ public final class JdbcUtils {
2626

2727
@Nullable private static Field c3poField = null;
2828

29-
/** Returns the unwrapped connection or null if exception was thrown. */
3029
public static Connection connectionFromStatement(Statement statement) {
31-
Connection connection;
3230
try {
33-
connection = statement.getConnection();
31+
return unwrapConnection(statement.getConnection());
32+
} catch (Throwable e) {
33+
// Had some problem getting the connection.
34+
logger.log(FINE, "Could not get connection from a statement", e);
35+
return null;
36+
}
37+
}
3438

39+
/** Returns the unwrapped connection or null if exception was thrown. */
40+
public static Connection unwrapConnection(Connection connection) {
41+
try {
3542
if (c3poField != null) {
3643
if (connection.getClass().getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) {
3744
return (Connection) c3poField.get(connection);
@@ -64,7 +71,7 @@ public static Connection connectionFromStatement(Statement statement) {
6471
}
6572
} catch (Throwable e) {
6673
// Had some problem getting the connection.
67-
logger.log(FINE, "Could not get connection for StatementAdvice", e);
74+
logger.log(FINE, "Could not unwrap connection", e);
6875
return null;
6976
}
7077
return connection;

0 commit comments

Comments
 (0)