SQLite
Instrument SQLite databases – including Room and SQLDelight – via SentrySQLiteDriver and SentrySupportSQLiteOpenHelper.
The sentry-android-sqlite library generates spans for your SQLite queries, whether you use Room, SQLDelight, or another persistence library. It does so by wrapping your existing SQLiteDriver or SupportSQLiteOpenHelper.
If you're using the Sentry Android Gradle Plugin, wrapping is performed automatically. (See Auto-Instrumentation for more details.)
Room users can also take advantage of our auto-instrumentation of DAO methods.
Check out our migration advice if you're migrating from SupportSQLiteOpenHelper to SQLiteDriver.
Spans require tracing to be enabled.
The Sentry Android Gradle Plugin (SAGP) uses bytecode manipulation to automatically wrap your driver and/or open helper.†
To use the SAGP, follow the plugin installation instructions. The SAGP auto-installs the Sentry Android SDK by default.
† For now, the SAGP only auto-wraps uses of SQLiteDriver with Room. Any non-Room uses of the driver must be wrapped manually. SAGP auto-wraps open helpers in all contexts.
The type or call site wrapped depends on the SAGP version:
| API | Minimum SAGP version | Coverage |
|---|---|---|
SQLiteDriver | ≥6.13.0 | Room.databaseBuilder().setDriver(...) call sites |
SupportSQLiteOpenHelper | ≥3.0.0 | FrameworkSQLiteOpenHelperFactory |
SupportSQLiteOpenHelper | ≥3.11.0 | Any SupportSQLiteOpenHelper.Factory (SQLDelight, custom factories, etc.) |
No additional configuration is required, as database auto-instrumentation is enabled by default. To disable it, use the DATABASE instrumentation feature:
build.gradleimport io.sentry.android.gradle.extensions.InstrumentationFeature
sentry {
tracingInstrumentation {
enabled = true
features = EnumSet.allOf(InstrumentationFeature) - InstrumentationFeature.DATABASE
}
}
import io.sentry.android.gradle.extensions.InstrumentationFeature
sentry {
tracingInstrumentation {
enabled = true
features = EnumSet.allOf(InstrumentationFeature) - InstrumentationFeature.DATABASE
}
}
import java.util.EnumSet
import io.sentry.android.gradle.extensions.InstrumentationFeature
sentry {
tracingInstrumentation {
enabled.set(true)
features.set(EnumSet.allOf(InstrumentationFeature::class.java) - InstrumentationFeature.DATABASE)
}
}
Disabling DATABASE instrumentation disables SQLiteDriver wrapping, SupportSQLiteOpenHelper wrapping, and Room DAO spans.
See our Gradle page for other SAGP configuration options.
If you don't use the SAGP, you can always wrap your driver or open helper explicitly. Add the Sentry Android SDK and the sentry-android-sqlite artifact:
build.gradledependencies {
implementation 'io.sentry:sentry-android:8.50.1'
implementation 'io.sentry:sentry-android-sqlite:8.50.1'
}
dependencies {
implementation 'io.sentry:sentry-android:8.50.1'
implementation 'io.sentry:sentry-android-sqlite:8.50.1'
}
dependencies {
implementation("io.sentry:sentry-android:8.50.1")
implementation("io.sentry:sentry-android-sqlite:8.50.1")
}
The sentry-android-sqlite artifact ships both wrappers; the minimum version depends on which API you need to wrap:
| API | Minimum sentry-android-sqlite version |
|---|---|
SQLiteDriver | ≥8.45.0 |
SupportSQLiteOpenHelper | ≥6.21.0 |
Wrap your driver or open helper directly with SentrySQLiteDriver.create(...) or SentrySupportSQLiteOpenHelper.create(...). Use the wrapped instance wherever you would have used the unwrapped one. For most teams, that means wiring it into Room or SQLDelight:
import androidx.room.Room
import androidx.sqlite.driver.AndroidSQLiteDriver
import io.sentry.sqlite.SentrySQLiteDriver
val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
.setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver()))
.build()
import androidx.room.Room
import androidx.sqlite.driver.AndroidSQLiteDriver
import io.sentry.sqlite.SentrySQLiteDriver
val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
.setDriver(SentrySQLiteDriver.create(AndroidSQLiteDriver()))
.build()
import androidx.room.Room
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import io.sentry.android.sqlite.SentrySupportSQLiteOpenHelper
val database = Room.databaseBuilder(context, MyDatabase::class.java, "dbName")
.openHelperFactory { configuration ->
SentrySupportSQLiteOpenHelper.create(FrameworkSQLiteOpenHelperFactory().create(configuration))
}
.build()
Note: If you're using the AndroidX SupportSQLiteDriver, you'll want to make sure you're wrapping the open helper but not the support driver itself. See the alert under the migration section for more details.
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import io.sentry.android.sqlite.SentrySupportSQLiteOpenHelper
val driver = AndroidSqliteDriver(
schema = MyDatabase.Schema,
context = context,
name = "myapp.db",
factory = SupportSQLiteOpenHelper.Factory { configuration ->
SentrySupportSQLiteOpenHelper.create(FrameworkSQLiteOpenHelperFactory().create(configuration))
},
)
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import io.sentry.android.sqlite.SentrySupportSQLiteOpenHelper
val driver = AndroidSqliteDriver(
schema = MyDatabase.Schema,
context = context,
name = "myapp.db",
factory = SupportSQLiteOpenHelper.Factory { configuration ->
SentrySupportSQLiteOpenHelper.create(FrameworkSQLiteOpenHelperFactory().create(configuration))
},
)
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
import app.cash.sqldelight.driver.android.AndroidSqliteDriver;
import io.sentry.android.sqlite.SentrySupportSQLiteOpenHelper;
SupportSQLiteOpenHelper.Factory factory = configuration ->
SentrySupportSQLiteOpenHelper.create(new FrameworkSQLiteOpenHelperFactory().create(configuration));
AndroidSqliteDriver driver = new AndroidSqliteDriver(
MyDatabase.Companion.getSchema(),
context,
"myapp.db",
factory
);
Note: SQLDelight doesn't currently support SQLiteDriver.
This section doesn't apply to migrations to AndroidX's SupportSQLiteDriver (the bridge adapter for Room [2.7, 3.0)).
The support driver consumes your existing SupportSQLiteOpenHelper, so continue to use SentrySupportSQLiteOpenHelper to wrap your helper as before.
If you're switching your app's SQLite API from SupportSQLiteOpenHelper to SQLiteDriver, you may need to update your Sentry dependencies:
| Path | Previous minimum | New minimum |
|---|---|---|
| Manual instrumentation | sentry-android-sqlite 6.21.0 | sentry-android-sqlite 8.45.0 |
| Auto-instrumentation† | Sentry Android Gradle Plugin 3.11.0 | Sentry Android Gradle Plugin 6.13.0 |
† SAGP installs sentry-android-sqlite transitively when your project depends on androidx.sqlite:sqlite, so you typically don't need to add it. But if your project explicitly pins sentry-android-sqlite, bump it to ≥8.45.0.
Replace SentrySupportSQLiteOpenHelper.create(openHelper) with SentrySQLiteDriver.create(driver), and you're all set! (See Manual Instrumentation → Room for the full call-site context.)
Note on span attributes: SentrySQLiteDriver derives the db.name span attribute from the base name of the file path passed to open() (for example, /data/.../my.db → my.db), while SentrySupportSQLiteOpenHelper uses AndroidX's databaseName verbatim.
Note on span durations: Due to underlying API differences, SQLiteDriver and SupportSQLiteOpenHelper span durations may differ. Driver spans are limited to work performed by the database during statement execution, while open helpers may also track time spent on statement preparation or work the owning app performs consuming native SQLite output.
To confirm that your wrapped driver or open helper is producing spans, execute a query inside a Sentry transaction and check for the SQL span in sentry.io.
import io.sentry.Sentry
import io.sentry.SpanStatus
import io.sentry.TransactionOptions
// `driver` is your SentrySQLiteDriver-wrapped instance (see Configure section above).
val transaction = Sentry.startTransaction(
"DB Smoke Test",
"db.query",
TransactionOptions().apply { isBindToScope = true },
)
driver.open(":memory:").use { connection ->
connection.prepare("SELECT 1").use { it.step() }
}
transaction.finish(SpanStatus.OK)
import io.sentry.Sentry
import io.sentry.SpanStatus
import io.sentry.TransactionOptions
// `driver` is your SentrySQLiteDriver-wrapped instance (see Configure section above).
val transaction = Sentry.startTransaction(
"DB Smoke Test",
"db.query",
TransactionOptions().apply { isBindToScope = true },
)
driver.open(":memory:").use { connection ->
connection.prepare("SELECT 1").use { it.step() }
}
transaction.finish(SpanStatus.OK)
import io.sentry.Sentry
import io.sentry.SpanStatus
import io.sentry.TransactionOptions
// `openHelper` is your SentrySupportSQLiteOpenHelper-wrapped instance (see Configure section above).
val transaction = Sentry.startTransaction(
"DB Smoke Test",
"db.query",
TransactionOptions().apply { isBindToScope = true },
)
openHelper.writableDatabase.query("SELECT 1").use { it.moveToFirst() }
transaction.finish(SpanStatus.OK)
To view the recorded transaction, log into sentry.io and open the Traces page. Filter by your transaction name (for example, transaction:"DB Smoke Test"), then open the trace. You should see a SQL-level span emitted by the Sentry wrapper around your driver or open helper, with the executed query as its description:
Sentry captures SQL strings as span descriptions. If you execute SQL with values interpolated directly into the query string (for example, db.query("SELECT * FROM users WHERE id = $id")), those values will be sent to Sentry. Prefer parameterized queries – query(sql, bindArgs), execSQL(sql, bindArgs), or Room @Query placeholders – so only the SQL skeleton is captured. See Scrubbing Sensitive Data for additional controls.
For users of Room, the SAGP will also auto-instrument your DAO classes, adding a span around each DAO method invocation. This gives you a higher-level span for the DAO operation on top of the SQL-level spans produced by the underlying driver or open helper wrapper, making it easier to attribute queries back to the code that issued them.
DAO methods are instrumented via bytecode manipulation. We don't currently support manual instrumentation.
Known limitation: DAO auto-instrumentation doesn't work on Room ≥2.7.0 (including Room ≥3.0.0) due to internal API changes, although you'll still see SQL-level spans from the underlying SQLiteDriver or SupportSQLiteOpenHelper wrapper. (See #1304.)
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").