Skip to content

Commit dc4cc7a

Browse files
authored
Collect database transaction spans (BEGIN, COMMIT, ROLLBACK) (#5072)
* Collect db transaction spans * make span creation for db transactions opt-in * remove code duplication * changelog
1 parent 8687935 commit dc4cc7a

File tree

13 files changed

+396
-24
lines changed

13 files changed

+396
-24
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
- Add `installGroupsOverride` parameter and `installGroups` property to Build Distribution SDK ([#5062](https://github.com/getsentry/sentry-java/pull/5062))
88
- Update Android targetSdk to API 36 (Android 16) ([#5016](https://github.com/getsentry/sentry-java/pull/5016))
99
- Add AndroidManifest support for Spotlight configuration via `io.sentry.spotlight.enable` and `io.sentry.spotlight.url` ([#5064](https://github.com/getsentry/sentry-java/pull/5064))
10+
- Collect database transaction spans (`BEGIN`, `COMMIT`, `ROLLBACK`) ([#5072](https://github.com/getsentry/sentry-java/pull/5072))
11+
- To enable creation of these spans, set `options.enableDatabaseTransactionTracing` to `true`
12+
- `enable-database-transaction-tracing=true` when using `sentry.properties`
13+
- For Spring Boot, use `sentry.enable-database-transaction-tracing=true` in `application.properties` or in `application.yml`:
14+
```yaml
15+
sentry:
16+
enable-database-transaction-tracing: true
17+
```
1018
1119
### Fixes
1220

sentry-jdbc/api/sentry-jdbc.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public final class io/sentry/jdbc/BuildConfig {
66
public final class io/sentry/jdbc/DatabaseUtils {
77
public fun <init> ()V
88
public static fun parse (Ljava/lang/String;)Lio/sentry/jdbc/DatabaseUtils$DatabaseDetails;
9+
public static fun readFrom (Lcom/p6spy/engine/common/ConnectionInformation;)Lio/sentry/jdbc/DatabaseUtils$DatabaseDetails;
910
public static fun readFrom (Lcom/p6spy/engine/common/StatementInformation;)Lio/sentry/jdbc/DatabaseUtils$DatabaseDetails;
1011
}
1112

@@ -19,6 +20,12 @@ public class io/sentry/jdbc/SentryJdbcEventListener : com/p6spy/engine/event/Sim
1920
public fun <init> ()V
2021
public fun <init> (Lio/sentry/IScopes;)V
2122
public fun onAfterAnyExecute (Lcom/p6spy/engine/common/StatementInformation;JLjava/sql/SQLException;)V
23+
public fun onAfterCommit (Lcom/p6spy/engine/common/ConnectionInformation;JLjava/sql/SQLException;)V
24+
public fun onAfterRollback (Lcom/p6spy/engine/common/ConnectionInformation;JLjava/sql/SQLException;)V
25+
public fun onAfterSetAutoCommit (Lcom/p6spy/engine/common/ConnectionInformation;ZZLjava/sql/SQLException;)V
2226
public fun onBeforeAnyExecute (Lcom/p6spy/engine/common/StatementInformation;)V
27+
public fun onBeforeCommit (Lcom/p6spy/engine/common/ConnectionInformation;)V
28+
public fun onBeforeRollback (Lcom/p6spy/engine/common/ConnectionInformation;)V
29+
public fun onBeforeSetAutoCommit (Lcom/p6spy/engine/common/ConnectionInformation;ZZ)V
2330
}
2431

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public static DatabaseDetails readFrom(
2020

2121
final @Nullable ConnectionInformation connectionInformation =
2222
statementInformation.getConnectionInformation();
23+
return readFrom(connectionInformation);
24+
}
25+
26+
public static DatabaseDetails readFrom(
27+
final @Nullable ConnectionInformation connectionInformation) {
2328
if (connectionInformation == null) {
2429
return EMPTY;
2530
}

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

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import static io.sentry.SpanDataConvention.DB_SYSTEM_KEY;
55

66
import com.jakewharton.nopen.annotation.Open;
7+
import com.p6spy.engine.common.ConnectionInformation;
78
import com.p6spy.engine.common.StatementInformation;
89
import com.p6spy.engine.event.SimpleJdbcEventListener;
910
import io.sentry.IScopes;
1011
import io.sentry.ISentryLifecycleToken;
1112
import io.sentry.ISpan;
1213
import io.sentry.ScopesAdapter;
1314
import io.sentry.SentryIntegrationPackageStorage;
14-
import io.sentry.Span;
1515
import io.sentry.SpanOptions;
1616
import io.sentry.SpanStatus;
1717
import io.sentry.util.AutoClosableReentrantLock;
@@ -20,12 +20,12 @@
2020
import org.jetbrains.annotations.NotNull;
2121
import org.jetbrains.annotations.Nullable;
2222

23-
/** P6Spy JDBC event listener that creates {@link Span}s around database queries. */
2423
@Open
2524
public class SentryJdbcEventListener extends SimpleJdbcEventListener {
2625
private static final String TRACE_ORIGIN = "auto.db.jdbc";
2726
private final @NotNull IScopes scopes;
28-
private static final @NotNull ThreadLocal<ISpan> CURRENT_SPAN = new ThreadLocal<>();
27+
private static final @NotNull ThreadLocal<ISpan> CURRENT_QUERY_SPAN = new ThreadLocal<>();
28+
private static final @NotNull ThreadLocal<ISpan> CURRENT_TRANSACTION_SPAN = new ThreadLocal<>();
2929

3030
private volatile @Nullable DatabaseUtils.DatabaseDetails cachedDatabaseDetails = null;
3131
protected final @NotNull AutoClosableReentrantLock databaseDetailsLock =
@@ -47,24 +47,109 @@ public SentryJdbcEventListener() {
4747

4848
@Override
4949
public void onBeforeAnyExecute(final @NotNull StatementInformation statementInformation) {
50-
final ISpan parent = scopes.getSpan();
51-
if (parent != null && !parent.isNoOp()) {
52-
final @NotNull SpanOptions spanOptions = new SpanOptions();
53-
spanOptions.setOrigin(TRACE_ORIGIN);
54-
final ISpan span = parent.startChild("db.query", statementInformation.getSql(), spanOptions);
55-
CURRENT_SPAN.set(span);
56-
}
50+
startSpan(CURRENT_QUERY_SPAN, "db.query", statementInformation.getSql());
5751
}
5852

5953
@Override
6054
public void onAfterAnyExecute(
6155
final @NotNull StatementInformation statementInformation,
6256
long timeElapsedNanos,
6357
final @Nullable SQLException e) {
64-
final ISpan span = CURRENT_SPAN.get();
58+
finishSpan(CURRENT_QUERY_SPAN, statementInformation.getConnectionInformation(), e);
59+
}
60+
61+
@Override
62+
public void onBeforeSetAutoCommit(
63+
final @NotNull ConnectionInformation connectionInformation,
64+
boolean newAutoCommit,
65+
boolean currentAutoCommit) {
66+
if (!isDatabaseTransactionTracingEnabled()) {
67+
return;
68+
}
69+
final boolean isSwitchingToManualCommit = !newAutoCommit && currentAutoCommit;
70+
if (isSwitchingToManualCommit) {
71+
startSpan(CURRENT_TRANSACTION_SPAN, "db.sql.transaction.begin", "BEGIN");
72+
}
73+
}
74+
75+
@Override
76+
public void onAfterSetAutoCommit(
77+
final @NotNull ConnectionInformation connectionInformation,
78+
final boolean newAutoCommit,
79+
final boolean oldAutoCommit,
80+
final @Nullable SQLException e) {
81+
if (!isDatabaseTransactionTracingEnabled()) {
82+
return;
83+
}
84+
final boolean isSwitchingToManualCommit = !newAutoCommit && oldAutoCommit;
85+
if (isSwitchingToManualCommit) {
86+
finishSpan(CURRENT_TRANSACTION_SPAN, connectionInformation, e);
87+
}
88+
}
89+
90+
@Override
91+
public void onBeforeCommit(final @NotNull ConnectionInformation connectionInformation) {
92+
if (!isDatabaseTransactionTracingEnabled()) {
93+
return;
94+
}
95+
startSpan(CURRENT_TRANSACTION_SPAN, "db.sql.transaction.commit", "COMMIT");
96+
}
97+
98+
@Override
99+
public void onAfterCommit(
100+
final @NotNull ConnectionInformation connectionInformation,
101+
final long timeElapsedNanos,
102+
final @Nullable SQLException e) {
103+
if (!isDatabaseTransactionTracingEnabled()) {
104+
return;
105+
}
106+
finishSpan(CURRENT_TRANSACTION_SPAN, connectionInformation, e);
107+
}
108+
109+
@Override
110+
public void onBeforeRollback(final @NotNull ConnectionInformation connectionInformation) {
111+
if (!isDatabaseTransactionTracingEnabled()) {
112+
return;
113+
}
114+
startSpan(CURRENT_TRANSACTION_SPAN, "db.sql.transaction.rollback", "ROLLBACK");
115+
}
116+
117+
@Override
118+
public void onAfterRollback(
119+
final @NotNull ConnectionInformation connectionInformation,
120+
final long timeElapsedNanos,
121+
final @Nullable SQLException e) {
122+
if (!isDatabaseTransactionTracingEnabled()) {
123+
return;
124+
}
125+
finishSpan(CURRENT_TRANSACTION_SPAN, connectionInformation, e);
126+
}
127+
128+
private boolean isDatabaseTransactionTracingEnabled() {
129+
return scopes.getOptions().isEnableDatabaseTransactionTracing();
130+
}
131+
132+
private void startSpan(
133+
final @NotNull ThreadLocal<ISpan> spanHolder,
134+
final @NotNull String operation,
135+
final @Nullable String description) {
136+
final @Nullable ISpan parent = scopes.getSpan();
137+
if (parent != null && !parent.isNoOp()) {
138+
final @NotNull SpanOptions spanOptions = new SpanOptions();
139+
spanOptions.setOrigin(TRACE_ORIGIN);
140+
final @NotNull ISpan span = parent.startChild(operation, description, spanOptions);
141+
spanHolder.set(span);
142+
}
143+
}
144+
145+
private void finishSpan(
146+
final @NotNull ThreadLocal<ISpan> spanHolder,
147+
final @Nullable ConnectionInformation connectionInformation,
148+
final @Nullable SQLException e) {
149+
final @Nullable ISpan span = spanHolder.get();
65150

66151
if (span != null) {
67-
applyDatabaseDetailsToSpan(statementInformation, span);
152+
applyDatabaseDetailsToSpan(connectionInformation, span);
68153

69154
if (e != null) {
70155
span.setThrowable(e);
@@ -73,7 +158,7 @@ public void onAfterAnyExecute(
73158
span.setStatus(SpanStatus.OK);
74159
}
75160
span.finish();
76-
CURRENT_SPAN.set(null);
161+
spanHolder.remove();
77162
}
78163
}
79164

@@ -82,9 +167,9 @@ private void addPackageAndIntegrationInfo() {
82167
}
83168

84169
private void applyDatabaseDetailsToSpan(
85-
final @NotNull StatementInformation statementInformation, final @NotNull ISpan span) {
170+
final @Nullable ConnectionInformation connectionInformation, final @NotNull ISpan span) {
86171
final @NotNull DatabaseUtils.DatabaseDetails databaseDetails =
87-
getOrComputeDatabaseDetails(statementInformation);
172+
getOrComputeDatabaseDetails(connectionInformation);
88173

89174
if (databaseDetails.getDbSystem() != null) {
90175
span.setData(DB_SYSTEM_KEY, databaseDetails.getDbSystem());
@@ -96,11 +181,11 @@ private void applyDatabaseDetailsToSpan(
96181
}
97182

98183
private @NotNull DatabaseUtils.DatabaseDetails getOrComputeDatabaseDetails(
99-
final @NotNull StatementInformation statementInformation) {
184+
final @Nullable ConnectionInformation connectionInformation) {
100185
if (cachedDatabaseDetails == null) {
101186
try (final @NotNull ISentryLifecycleToken ignored = databaseDetailsLock.acquire()) {
102187
if (cachedDatabaseDetails == null) {
103-
cachedDatabaseDetails = DatabaseUtils.readFrom(statementInformation);
188+
cachedDatabaseDetails = DatabaseUtils.readFrom(connectionInformation);
104189
}
105190
}
106191
}

0 commit comments

Comments
 (0)