diff --git a/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt b/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt index 059eb1bb1b5..4fe75ef4d28 100644 --- a/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt +++ b/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt @@ -6,6 +6,7 @@ import io.sentry.IScopes import io.sentry.ISpan import io.sentry.Instrumenter import io.sentry.ScopesAdapter +import io.sentry.SentryDate import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryStackTraceFactory import io.sentry.SpanDataConvention @@ -46,12 +47,12 @@ internal class OpenHelperSpans( if (result is CrossProcessCursor) { return SentryCrossProcessCursor(result, this, sql) as T } - span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) + span = startSpan(sql, startTimestamp) span?.spanContext?.origin = TRACE_ORIGIN span?.status = SpanStatus.OK result } catch (e: Throwable) { - span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) + span = startSpan(sql, startTimestamp) span?.spanContext?.origin = TRACE_ORIGIN span?.status = SpanStatus.INTERNAL_ERROR span?.throwable = e @@ -76,4 +77,12 @@ internal class OpenHelperSpans( } } } + + private fun startSpan(sql: String, startTimestamp: SentryDate): ISpan? = + scopes.span?.startChild( + "db.sql.query", + sql.takeIf { scopes.options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways }, + startTimestamp, + Instrumenter.SENTRY, + ) } diff --git a/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt b/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt index b3c0eb7c713..fe2b15a33bb 100644 --- a/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt +++ b/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt @@ -50,7 +50,10 @@ internal class DriverSpans(private val scopes: IScopes, private val dbMetadata: val startTimestamp = SentryLongDate(startTimestampNanos) val endTimestamp = SentryLongDate(startTimestampNanos + durationNanos) - parent.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY).apply { + val description = sql.takeIf { + scopes.options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways + } + parent.startChild("db.sql.query", description, startTimestamp, Instrumenter.SENTRY).apply { spanContext.origin = SQLITE_TRACE_ORIGIN throwable?.let { this.throwable = it } diff --git a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt index 0552094838e..8b442c59ee5 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt @@ -66,6 +66,28 @@ class OpenHelperSpansTest { assertTrue(span.isFinished) } + @Test + fun `performSql omits description when database query data is disabled`() { + val sut = fixture.getSut() + fixture.options.dataCollection.setDatabaseQueryData(false) + + sut.performSql("SELECT secret FROM users") {} + + val span = fixture.sentryTracer.children.first() + assertNull(span.description) + assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY]) + } + + @Test + fun `performSql keeps description in legacy mode`() { + val sut = fixture.getSut() + fixture.options.isSendDefaultPii = false + + sut.performSql("SELECT secret FROM users") {} + + assertEquals("SELECT secret FROM users", fixture.sentryTracer.children.first().description) + } + @Test fun `performSql does not create a span if no span is running`() { val sut = fixture.getSut(isSpanActive = false) diff --git a/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt index 319fc20d7ce..2265d10aa75 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt @@ -129,6 +129,28 @@ class DriverSpansTest { assertTrue(span.isFinished) } + @Test + fun `record method omits description when database query data is disabled`() { + val sut = fixture.getSut() + fixture.options.dataCollection.setDatabaseQueryData(false) + + sut.record("SELECT secret FROM users", sut.startTimestamp(), 1_000_000, SpanStatus.OK) + + val span = fixture.sentryTracer.children.first() + assertNull(span.description) + assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY]) + } + + @Test + fun `record method keeps description in legacy mode`() { + val sut = fixture.getSut() + fixture.options.isSendDefaultPii = false + + sut.record("SELECT secret FROM users", sut.startTimestamp(), 1_000_000, SpanStatus.OK) + + assertEquals("SELECT secret FROM users", fixture.sentryTracer.children.first().description) + } + @Test fun `record method sets finishDate equal to startDate + durationNanos`() { val sut = fixture.getSut() diff --git a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java index 4206de18002..59e50efae26 100644 --- a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java +++ b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java @@ -47,7 +47,11 @@ public SentryJdbcEventListener() { @Override public void onBeforeAnyExecute(final @NotNull StatementInformation statementInformation) { - startSpan(CURRENT_QUERY_SPAN, "db.query", statementInformation.getSql()); + final @Nullable String description = + scopes.getOptions().getDataCollectionResolver().isDatabaseQueryDataWithLegacyAlways() + ? statementInformation.getSql() + : null; + startSpan(CURRENT_QUERY_SPAN, "db.query", description); } @Override diff --git a/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt b/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt index 22ee97e5d47..436bc4abf62 100644 --- a/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt +++ b/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt @@ -90,6 +90,29 @@ class SentryJdbcEventListenerTest { assertEquals("INSERT INTO foo VALUES (2)", fixture.tx.children[1].description) } + @Test + fun `omits query description when database query data is disabled`() { + val sut = fixture.getSut() + fixture.options.dataCollection.setDatabaseQueryData(false) + + sut.connection.use { it.prepareStatement("INSERT INTO foo VALUES (1)").executeUpdate() } + + assertEquals(1, fixture.tx.children.size) + assertEquals(null, fixture.tx.children.first().description) + assertEquals("hsqldb", fixture.tx.children.first().data[DB_SYSTEM_KEY]) + assertEquals("testdb", fixture.tx.children.first().data[DB_NAME_KEY]) + } + + @Test + fun `legacy mode keeps query description when sendDefaultPii is false`() { + val sut = fixture.getSut() + fixture.options.isSendDefaultPii = false + + sut.connection.use { it.prepareStatement("INSERT INTO foo VALUES (1)").executeUpdate() } + + assertEquals("INSERT INTO foo VALUES (1)", fixture.tx.children.first().description) + } + @Test fun `creates spans for calls resulting in error`() { val sut = fixture.getSut(existingRow = 1) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 9ae6e60181f..b999cc0d902 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -422,6 +422,7 @@ public final class io/sentry/DataCollectionResolver { public fun getQueryParams ()Lio/sentry/KeyValueCollectionBehavior; public fun isDataCollectionConfigured ()Z public fun isDatabaseQueryData ()Z + public fun isDatabaseQueryDataWithLegacyAlways ()Z public fun isGraphqlDocument ()Z public fun isGraphqlDocumentWithLegacyAlways ()Z public fun isGraphqlDocumentWithLegacyBodyGate ()Z diff --git a/sentry/src/main/java/io/sentry/DataCollectionResolver.java b/sentry/src/main/java/io/sentry/DataCollectionResolver.java index cdfb0649188..a293614eb2b 100644 --- a/sentry/src/main/java/io/sentry/DataCollectionResolver.java +++ b/sentry/src/main/java/io/sentry/DataCollectionResolver.java @@ -31,6 +31,10 @@ public boolean isDatabaseQueryData() { return explicitOrSendDefaultPii(options.getDataCollection().getDatabaseQueryData(), true); } + public boolean isDatabaseQueryDataWithLegacyAlways() { + return explicitOrDefault(options.getDataCollection().getDatabaseQueryData(), true, true); + } + public boolean isGraphqlDocument() { return explicitOrSendDefaultPii(options.getDataCollection().getGraphql().getDocument(), true); } diff --git a/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt b/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt index 47f637f0a08..fb525ce43d2 100644 --- a/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt +++ b/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt @@ -78,6 +78,17 @@ class DataCollectionResolverTest { assertThat(options.dataCollectionResolver.isDatabaseQueryData).isFalse() } + @Test + fun `database query data legacy always variant preserves collection when namespace is absent`() { + val options = SentryOptions().apply { isSendDefaultPii = false } + + assertThat(options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways).isTrue() + + options.dataCollection.setDatabaseQueryData(false) + + assertThat(options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways).isFalse() + } + @Test fun `GraphQL document falls back to sendDefaultPii and override takes precedence`() { val options = SentryOptions().apply { isSendDefaultPii = true }