Skip to content

Commit 3fe387a

Browse files
authored
Send db.system and db.name in span data (#2894)
1 parent d55d97a commit 3fe387a

File tree

11 files changed

+562
-0
lines changed

11 files changed

+562
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Send `db.system` and `db.name` in span data ([#2894](https://github.com/getsentry/sentry-java/pull/2894))
8+
39
## 6.28.0
410

511
### Features

sentry-jdbc/api/sentry-jdbc.api

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ public final class io/sentry/jdbc/BuildConfig {
33
public static final field VERSION_NAME Ljava/lang/String;
44
}
55

6+
public final class io/sentry/jdbc/DatabaseUtils {
7+
public fun <init> ()V
8+
public static fun parse (Ljava/lang/String;)Lio/sentry/jdbc/DatabaseUtils$DatabaseDetails;
9+
public static fun readFrom (Lcom/p6spy/engine/common/StatementInformation;)Lio/sentry/jdbc/DatabaseUtils$DatabaseDetails;
10+
}
11+
12+
public final class io/sentry/jdbc/DatabaseUtils$DatabaseDetails {
13+
public fun getDbName ()Ljava/lang/String;
14+
public fun getDbSystem ()Ljava/lang/String;
15+
}
16+
617
public class io/sentry/jdbc/SentryJdbcEventListener : com/p6spy/engine/event/SimpleJdbcEventListener {
718
public fun <init> ()V
819
public fun <init> (Lio/sentry/IHub;)V

sentry-jdbc/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies {
3434
testImplementation(kotlin(Config.kotlinStdLib))
3535
testImplementation(Config.TestLibs.kotlinTestJunit)
3636
testImplementation(Config.TestLibs.mockitoKotlin)
37+
testImplementation(Config.TestLibs.mockitoInline)
3738
testImplementation(Config.TestLibs.awaitility)
3839
testImplementation(Config.TestLibs.hsqldb)
3940
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package io.sentry.jdbc;
2+
3+
import com.p6spy.engine.common.ConnectionInformation;
4+
import com.p6spy.engine.common.StatementInformation;
5+
import io.sentry.util.StringUtils;
6+
import java.net.URI;
7+
import java.util.Locale;
8+
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
public final class DatabaseUtils {
12+
13+
private static final @NotNull DatabaseDetails EMPTY = new DatabaseDetails(null, null);
14+
15+
public static DatabaseDetails readFrom(
16+
final @Nullable StatementInformation statementInformation) {
17+
if (statementInformation == null) {
18+
return EMPTY;
19+
}
20+
21+
final @Nullable ConnectionInformation connectionInformation =
22+
statementInformation.getConnectionInformation();
23+
if (connectionInformation == null) {
24+
return EMPTY;
25+
}
26+
27+
return parse(connectionInformation.getUrl());
28+
}
29+
30+
public static DatabaseDetails parse(final @Nullable String databaseConnectionUrl) {
31+
if (databaseConnectionUrl == null) {
32+
return EMPTY;
33+
}
34+
try {
35+
final @NotNull String rawUrl =
36+
removeP6SpyPrefix(databaseConnectionUrl.toLowerCase(Locale.ROOT));
37+
final @NotNull String[] urlParts = rawUrl.split(":", -1);
38+
if (urlParts.length > 1) {
39+
final @NotNull String dbSystem = urlParts[0];
40+
return parseDbSystemSpecific(dbSystem, urlParts, rawUrl);
41+
}
42+
} catch (Throwable t) {
43+
// ignore
44+
}
45+
46+
return EMPTY;
47+
}
48+
49+
private static @NotNull DatabaseDetails parseDbSystemSpecific(
50+
final @NotNull String dbSystem,
51+
final @NotNull String[] urlParts,
52+
final @NotNull String urlString) {
53+
if ("hsqldb".equalsIgnoreCase(dbSystem)
54+
|| "h2".equalsIgnoreCase(dbSystem)
55+
|| "derby".equalsIgnoreCase(dbSystem)
56+
|| "sqlite".equalsIgnoreCase(dbSystem)) {
57+
if (urlString.contains("//")) {
58+
return parseAsUri(dbSystem, StringUtils.removePrefix(urlString, dbSystem + ":"));
59+
}
60+
if (urlParts.length > 2) {
61+
String dbNameAndMaybeMore = urlParts[2];
62+
return new DatabaseDetails(dbSystem, StringUtils.substringBefore(dbNameAndMaybeMore, ";"));
63+
}
64+
if (urlParts.length > 1) {
65+
String dbNameAndMaybeMore = urlParts[1];
66+
return new DatabaseDetails(dbSystem, StringUtils.substringBefore(dbNameAndMaybeMore, ";"));
67+
}
68+
}
69+
if ("mariadb".equalsIgnoreCase(dbSystem)
70+
|| "mysql".equalsIgnoreCase(dbSystem)
71+
|| "postgresql".equalsIgnoreCase(dbSystem)
72+
|| "mongo".equalsIgnoreCase(dbSystem)) {
73+
return parseAsUri(dbSystem, urlString);
74+
}
75+
if ("sqlserver".equalsIgnoreCase(dbSystem)) {
76+
try {
77+
String dbProperty = ";databasename=";
78+
final int index = urlString.indexOf(dbProperty);
79+
if (index >= 0) {
80+
final @NotNull String dbNameAndMaybeMore =
81+
urlString.substring(index + dbProperty.length());
82+
return new DatabaseDetails(
83+
dbSystem, StringUtils.substringBefore(dbNameAndMaybeMore, ";"));
84+
}
85+
} catch (Throwable t) {
86+
// ignore
87+
}
88+
}
89+
if ("oracle".equalsIgnoreCase(dbSystem)) {
90+
String uriPrefix = "oracle:thin:@//";
91+
final int indexOfUri = urlString.indexOf(uriPrefix);
92+
if (indexOfUri >= 0) {
93+
final @NotNull String uri =
94+
"makethisaprotocol://" + urlString.substring(indexOfUri + uriPrefix.length());
95+
return parseAsUri(dbSystem, uri);
96+
}
97+
98+
final int indexOfTnsNames = urlString.indexOf("oracle:thin:@(");
99+
if (indexOfTnsNames >= 0) {
100+
String serviceNamePrefix = "(service_name=";
101+
final int indexOfServiceName = urlString.indexOf(serviceNamePrefix);
102+
if (indexOfServiceName >= 0) {
103+
final int indexOfClosingBrace = urlString.indexOf(")", indexOfServiceName);
104+
final @NotNull String serviceName =
105+
urlString.substring(
106+
indexOfServiceName + serviceNamePrefix.length(), indexOfClosingBrace);
107+
return new DatabaseDetails(dbSystem, serviceName);
108+
}
109+
}
110+
}
111+
if ("datadirect".equalsIgnoreCase(dbSystem)
112+
|| "tibcosoftware".equalsIgnoreCase(dbSystem)
113+
|| "jtds".equalsIgnoreCase(dbSystem)
114+
|| "microsoft".equalsIgnoreCase(dbSystem)) {
115+
return parse(StringUtils.removePrefix(urlString, dbSystem + ":"));
116+
}
117+
118+
return new DatabaseDetails(dbSystem, null);
119+
}
120+
121+
private static @NotNull DatabaseDetails parseAsUri(
122+
final @NotNull String dbSystem, final @NotNull String urlString) {
123+
try {
124+
final @NotNull URI url = new URI(urlString);
125+
String path = StringUtils.removePrefix(url.getPath(), "/");
126+
String pathWithoutProperties = StringUtils.substringBefore(path, ";");
127+
return new DatabaseDetails(dbSystem, pathWithoutProperties);
128+
} catch (Throwable t) {
129+
System.out.println(t.getMessage());
130+
// ignore
131+
}
132+
return new DatabaseDetails(dbSystem, null);
133+
}
134+
135+
private static @NotNull String removeP6SpyPrefix(final @NotNull String url) {
136+
return StringUtils.removePrefix(StringUtils.removePrefix(url, "jdbc:"), "p6spy:");
137+
}
138+
139+
public static final class DatabaseDetails {
140+
private final @Nullable String dbSystem;
141+
private final @Nullable String dbName;
142+
143+
DatabaseDetails(final @Nullable String dbSystem, final @Nullable String dbName) {
144+
this.dbSystem = dbSystem;
145+
this.dbName = dbName;
146+
}
147+
148+
public @Nullable String getDbSystem() {
149+
return dbSystem;
150+
}
151+
152+
public @Nullable String getDbName() {
153+
return dbName;
154+
}
155+
}
156+
}

sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.sentry.jdbc;
22

3+
import static io.sentry.SpanDataConvention.DB_NAME_KEY;
4+
import static io.sentry.SpanDataConvention.DB_SYSTEM_KEY;
5+
36
import com.jakewharton.nopen.annotation.Open;
47
import com.p6spy.engine.common.StatementInformation;
58
import com.p6spy.engine.event.SimpleJdbcEventListener;
@@ -21,6 +24,9 @@ public class SentryJdbcEventListener extends SimpleJdbcEventListener {
2124
private final @NotNull IHub hub;
2225
private static final @NotNull ThreadLocal<ISpan> CURRENT_SPAN = new ThreadLocal<>();
2326

27+
private volatile @Nullable DatabaseUtils.DatabaseDetails cachedDatabaseDetails = null;
28+
private final @NotNull Object databaseDetailsLock = new Object();
29+
2430
public SentryJdbcEventListener(final @NotNull IHub hub) {
2531
this.hub = Objects.requireNonNull(hub, "hub is required");
2632
addPackageAndIntegrationInfo();
@@ -46,7 +52,10 @@ public void onAfterAnyExecute(
4652
long timeElapsedNanos,
4753
final @Nullable SQLException e) {
4854
final ISpan span = CURRENT_SPAN.get();
55+
4956
if (span != null) {
57+
applyDatabaseDetailsToSpan(statementInformation, span);
58+
5059
if (e != null) {
5160
span.setThrowable(e);
5261
span.setStatus(SpanStatus.INTERNAL_ERROR);
@@ -63,4 +72,31 @@ private void addPackageAndIntegrationInfo() {
6372
SentryIntegrationPackageStorage.getInstance()
6473
.addPackage("maven:io.sentry:sentry-jdbc", BuildConfig.VERSION_NAME);
6574
}
75+
76+
private void applyDatabaseDetailsToSpan(
77+
final @NotNull StatementInformation statementInformation, final @NotNull ISpan span) {
78+
final @NotNull DatabaseUtils.DatabaseDetails databaseDetails =
79+
getOrComputeDatabaseDetails(statementInformation);
80+
81+
if (databaseDetails.getDbSystem() != null) {
82+
span.setData(DB_SYSTEM_KEY, databaseDetails.getDbSystem());
83+
}
84+
85+
if (databaseDetails.getDbName() != null) {
86+
span.setData(DB_NAME_KEY, databaseDetails.getDbName());
87+
}
88+
}
89+
90+
private @NotNull DatabaseUtils.DatabaseDetails getOrComputeDatabaseDetails(
91+
final @NotNull StatementInformation statementInformation) {
92+
if (cachedDatabaseDetails == null) {
93+
synchronized (databaseDetailsLock) {
94+
if (cachedDatabaseDetails == null) {
95+
cachedDatabaseDetails = DatabaseUtils.readFrom(statementInformation);
96+
}
97+
}
98+
}
99+
100+
return cachedDatabaseDetails;
101+
}
66102
}

0 commit comments

Comments
 (0)