diff --git a/CHANGELOG.md b/CHANGELOG.md index 41447b7328..4e74ed4b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Add db.system and db.name attributes to db spans data ([#1629](https://github.com/getsentry/sentry-dart/pull/1629)) + ### Features - Tracing without performance ([#1621](https://github.com/getsentry/sentry-dart/pull/1621)) diff --git a/sqflite/lib/src/sentry_batch.dart b/sqflite/lib/src/sentry_batch.dart index 95949125bd..150b1c51da 100644 --- a/sqflite/lib/src/sentry_batch.dart +++ b/sqflite/lib/src/sentry_batch.dart @@ -6,6 +6,7 @@ import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common/src/sql_builder.dart'; import 'sentry_database.dart'; +import 'utils/sentry_database_span_attributes.dart'; /// A [Batch] wrapper that adds Sentry support. /// @@ -21,6 +22,7 @@ import 'sentry_database.dart'; class SentryBatch implements Batch { final Batch _batch; final Hub _hub; + final String? _dbName; // we don't clear the buffer because SqfliteBatch don't either final _buffer = StringBuffer(); @@ -36,7 +38,9 @@ class SentryBatch implements Batch { SentryBatch( this._batch, { @internal Hub? hub, - }) : _hub = hub ?? HubAdapter(); + @internal String? dbName, + }) : _hub = hub ?? HubAdapter(), + _dbName = dbName; @override Future> apply({bool? noResult, bool? continueOnError}) { @@ -47,8 +51,10 @@ class SentryBatch implements Batch { SentryDatabase.dbOp, description: _buffer.toString().trim(), ); + // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteBatch; + setDatabaseAttributeData(span, _dbName); try { final result = await _batch.apply( @@ -86,6 +92,7 @@ class SentryBatch implements Batch { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteBatch; + setDatabaseAttributeData(span, _dbName); try { final result = await _batch.commit( diff --git a/sqflite/lib/src/sentry_database.dart b/sqflite/lib/src/sentry_database.dart index f47551cff8..71147c3464 100644 --- a/sqflite/lib/src/sentry_database.dart +++ b/sqflite/lib/src/sentry_database.dart @@ -5,6 +5,8 @@ import 'package:sqflite/sqflite.dart'; import 'sentry_database_executor.dart'; import 'sentry_sqflite_transaction.dart'; import 'version.dart'; +import 'utils/sentry_database_span_attributes.dart'; +import 'package:path/path.dart' as p; /// A [Database] wrapper that adds Sentry support. /// @@ -31,6 +33,18 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database { static const dbSqlQueryOp = 'db.sql.query'; static const _dbSqlOp = 'db.sql.transaction'; + @internal + // ignore: public_member_api_docs + static const dbSystemKey = 'db.system'; + @internal + // ignore: public_member_api_docs + static const dbSystem = 'sqlite'; + @internal + // ignore: public_member_api_docs + static const dbNameKey = 'db.name'; + @internal + // ignore: public_member_api_docs + String dbName; /// ```dart /// import 'package:sqflite/sqflite.dart'; @@ -43,7 +57,9 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database { this._database, { @internal Hub? hub, }) : _hub = hub ?? HubAdapter(), - super(_database, hub: hub) { + dbName = p.basenameWithoutExtension(_database.path), + super(_database, + hub: hub, dbName: p.basenameWithoutExtension(_database.path)) { // ignore: invalid_use_of_internal_member final options = _hub.options; options.sdk.addIntegration('SentrySqfliteTracing'); @@ -113,12 +129,13 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabase; + setDatabaseAttributeData(span, dbName); Future newAction(Transaction txn) async { - final executor = - SentryDatabaseExecutor(txn, parentSpan: span, hub: _hub); + final executor = SentryDatabaseExecutor(txn, + parentSpan: span, hub: _hub, dbName: dbName); final sentrySqfliteTransaction = - SentrySqfliteTransaction(executor, hub: _hub); + SentrySqfliteTransaction(executor, hub: _hub, dbName: dbName); return await action(sentrySqfliteTransaction); } diff --git a/sqflite/lib/src/sentry_database_executor.dart b/sqflite/lib/src/sentry_database_executor.dart index b43d13c8d6..c232ca9e24 100644 --- a/sqflite/lib/src/sentry_database_executor.dart +++ b/sqflite/lib/src/sentry_database_executor.dart @@ -7,24 +7,28 @@ import 'package:sqflite_common/src/sql_builder.dart'; import 'sentry_batch.dart'; import 'sentry_database.dart'; +import 'utils/sentry_database_span_attributes.dart'; @internal // ignore: public_member_api_docs class SentryDatabaseExecutor implements DatabaseExecutor { final DatabaseExecutor _executor; final ISentrySpan? _parentSpan; + final String? _dbName; // ignore: public_member_api_docs SentryDatabaseExecutor( this._executor, { ISentrySpan? parentSpan, @internal Hub? hub, + @internal String? dbName, }) : _parentSpan = parentSpan, - _hub = hub ?? HubAdapter(); + _hub = hub ?? HubAdapter(), + _dbName = dbName; final Hub _hub; @override - Batch batch() => SentryBatch(_executor.batch(), hub: _hub); + Batch batch() => SentryBatch(_executor.batch(), hub: _hub, dbName: _dbName); @override Database get database => _executor.database; @@ -41,6 +45,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = @@ -68,8 +73,10 @@ class SentryDatabaseExecutor implements DatabaseExecutor { SentryDatabase.dbSqlExecuteOp, description: sql, ); + span?.setData(SentryDatabase.dbSystemKey, SentryDatabase.dbSystem); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { await _executor.execute(sql, arguments); @@ -107,6 +114,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.insert( @@ -163,6 +171,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.query( @@ -226,6 +235,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.queryCursor( @@ -266,6 +276,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.rawDelete(sql, arguments); @@ -294,6 +305,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.rawInsert(sql, arguments); @@ -325,6 +337,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.rawQuery(sql, arguments); @@ -357,6 +370,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.rawQueryCursor( @@ -389,6 +403,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.rawUpdate(sql, arguments); @@ -430,6 +445,7 @@ class SentryDatabaseExecutor implements DatabaseExecutor { ); // ignore: invalid_use_of_internal_member span?.origin = SentryTraceOrigins.autoDbSqfliteDatabaseExecutor; + setDatabaseAttributeData(span, _dbName); try { final result = await _executor.update( diff --git a/sqflite/lib/src/sentry_sqflite_transaction.dart b/sqflite/lib/src/sentry_sqflite_transaction.dart index c207023e9c..78c41b487d 100644 --- a/sqflite/lib/src/sentry_sqflite_transaction.dart +++ b/sqflite/lib/src/sentry_sqflite_transaction.dart @@ -21,16 +21,19 @@ import 'sentry_batch.dart'; class SentrySqfliteTransaction extends Transaction implements DatabaseExecutor { final DatabaseExecutor _executor; final Hub _hub; + final String? _dbName; // ignore: public_member_api_docs @internal SentrySqfliteTransaction( this._executor, { @internal Hub? hub, - }) : _hub = hub ?? HubAdapter(); + @internal String? dbName, + }) : _hub = hub ?? HubAdapter(), + _dbName = dbName; @override - Batch batch() => SentryBatch(_executor.batch(), hub: _hub); + Batch batch() => SentryBatch(_executor.batch(), hub: _hub, dbName: _dbName); @override Database get database => _executor.database; diff --git a/sqflite/lib/src/utils/sentry_database_span_attributes.dart b/sqflite/lib/src/utils/sentry_database_span_attributes.dart new file mode 100644 index 0000000000..119f2dd1b8 --- /dev/null +++ b/sqflite/lib/src/utils/sentry_database_span_attributes.dart @@ -0,0 +1,12 @@ +import 'package:sentry/sentry.dart'; + +import '../../sentry_sqflite.dart'; + +/// Sets the database attributes on the [span]. +/// It contains the database system and the database name. +void setDatabaseAttributeData(ISentrySpan? span, String? dbName) { + span?.setData(SentryDatabase.dbSystemKey, SentryDatabase.dbSystem); + if (dbName != null) { + span?.setData(SentryDatabase.dbNameKey, dbName); + } +} diff --git a/sqflite/pubspec.yaml b/sqflite/pubspec.yaml index db60eec2a5..55fa554d05 100644 --- a/sqflite/pubspec.yaml +++ b/sqflite/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: sqflite: ^2.0.0 sqflite_common: ^2.0.0 meta: ^1.3.0 + path: ^1.8.3 dev_dependencies: lints: ^2.0.0 diff --git a/sqflite/test/mocks/mocks.dart b/sqflite/test/mocks/mocks.dart index 0a3115df74..3f0af8274c 100644 --- a/sqflite/test/mocks/mocks.dart +++ b/sqflite/test/mocks/mocks.dart @@ -33,7 +33,8 @@ ISentrySpan startTransactionShim( DatabaseExecutor, ], customMocks: [ - MockSpec(fallbackGenerators: {#startTransaction: startTransactionShim}) + MockSpec( + fallbackGenerators: {#startTransaction: startTransactionShim}), ], ) void main() {} diff --git a/sqflite/test/sentry_batch_test.dart b/sqflite/test/sentry_batch_test.dart index e01aa4d5c7..ce6f59e0d5 100644 --- a/sqflite/test/sentry_batch_test.dart +++ b/sqflite/test/sentry_batch_test.dart @@ -285,6 +285,39 @@ SELECT * FROM Product'''; await db.close(); }); + test('apply creates db span with dbSystem and dbName attributes', () async { + final db = await fixture.getDatabase(); + final batch = db.batch(); + + batch.insert('Product', {'title': 'Product 1'}); + + await batch.apply(); + + final span = fixture.tracer.children.last; + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect( + span.data[SentryDatabase.dbNameKey], (db as SentryDatabase).dbName); + + await db.close(); + }); + + test('commit creates db span with dbSystem and dbName attributes', + () async { + final db = await fixture.getDatabase(); + final batch = db.batch(); + + batch.insert('Product', {'title': 'Product 1'}); + + await batch.commit(); + + final span = fixture.tracer.children.last; + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect( + span.data[SentryDatabase.dbNameKey], (db as SentryDatabase).dbName); + + await db.close(); + }); + tearDown(() { databaseFactory = sqfliteDatabaseFactoryDefault; }); diff --git a/sqflite/test/sentry_database_test.dart b/sqflite/test/sentry_database_test.dart index 3a76d8066e..e05bd2367c 100644 --- a/sqflite/test/sentry_database_test.dart +++ b/sqflite/test/sentry_database_test.dart @@ -84,6 +84,8 @@ void main() { expect(span.context.operation, 'db.sql.transaction'); expect(span.context.description, 'Transaction DB: $inMemoryDatabasePath'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, // ignore: invalid_use_of_internal_member @@ -109,6 +111,9 @@ void main() { ); expect(insertSpan.context.parentSpanId, trSpan.context.spanId); expect(insertSpan.status, SpanStatus.ok()); + expect( + insertSpan.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(insertSpan.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( insertSpan.origin, @@ -130,6 +135,34 @@ void main() { await db.close(); }); + test('opening db sets currentDbName with :memory:', () async { + final db = await fixture.getSut(); + + expect(db.dbName, ':memory:'); + + await db.close(); + }); + + test('opening db sets currentDbName with db file without extension', + () async { + final db = await fixture.getSut( + database: await openDatabase('path/database/mydatabase.db'), + execute: false, + ); + + expect(db.dbName, 'mydatabase'); + + await db.close(); + }); + + test('closing db sets currentDbName to null', () async { + final db = await fixture.getSut(); + + expect(db.dbName, inMemoryDatabasePath); + + await db.close(); + }); + tearDown(() { databaseFactory = sqfliteDatabaseFactoryDefault; }); @@ -212,6 +245,8 @@ void main() { expect(span.context.operation, 'db.sql.execute'); expect(span.context.description, 'DELETE FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -231,6 +266,8 @@ void main() { expect(span.context.operation, 'db.sql.execute'); expect(span.context.description, 'DELETE FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -253,6 +290,8 @@ void main() { 'INSERT INTO Product (title) VALUES (?)', ); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -272,6 +311,8 @@ void main() { expect(span.context.operation, 'db.sql.query'); expect(span.context.description, 'SELECT * FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -291,6 +332,8 @@ void main() { expect(span.context.operation, 'db.sql.query'); expect(span.context.description, 'SELECT * FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -310,6 +353,8 @@ void main() { expect(span.context.operation, 'db.sql.execute'); expect(span.context.description, 'DELETE FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -333,6 +378,8 @@ void main() { 'INSERT INTO Product (title) VALUES (?)', ); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -352,6 +399,8 @@ void main() { expect(span.context.operation, 'db.sql.query'); expect(span.context.description, 'SELECT * FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -371,6 +420,8 @@ void main() { expect(span.context.operation, 'db.sql.query'); expect(span.context.description, 'SELECT * FROM Product'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -390,6 +441,8 @@ void main() { expect(span.context.operation, 'db.sql.execute'); expect(span.context.description, 'UPDATE Product SET title = ?'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, @@ -409,6 +462,8 @@ void main() { expect(span.context.operation, 'db.sql.execute'); expect(span.context.description, 'UPDATE Product SET title = ?'); expect(span.status, SpanStatus.ok()); + expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem); + expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath); expect( span.origin, diff --git a/sqflite/test/sentry_sqflite_database_factory_dart_test.dart b/sqflite/test/sentry_sqflite_database_factory_dart_test.dart index 61f8aaa9ca..d2ed2e9fb5 100644 --- a/sqflite/test/sentry_sqflite_database_factory_dart_test.dart +++ b/sqflite/test/sentry_sqflite_database_factory_dart_test.dart @@ -38,6 +38,7 @@ void main() { final db = await openDatabase(inMemoryDatabasePath); expect(db is SentryDatabase, true); + expect((db as SentryDatabase).dbName, inMemoryDatabasePath); await db.close(); }); @@ -56,10 +57,11 @@ void main() { () async { final db = await openDatabase(inMemoryDatabasePath); + expect((db as SentryDatabase).dbName, inMemoryDatabasePath); + final span = fixture.tracer.children.last; expect(span.context.operation, 'db'); expect(span.context.description, 'Open DB: $inMemoryDatabasePath'); - expect(span.context.description, 'Open DB: $inMemoryDatabasePath'); expect( span.origin, diff --git a/sqflite/test/sentry_sqflite_test.dart b/sqflite/test/sentry_sqflite_test.dart index 54ff24232d..ff75d67e6d 100644 --- a/sqflite/test/sentry_sqflite_test.dart +++ b/sqflite/test/sentry_sqflite_test.dart @@ -97,6 +97,7 @@ void main() { expect(span.status, SpanStatus.ok()); // ignore: invalid_use_of_internal_member expect(span.origin, SentryTraceOrigins.autoDbSqfliteOpenDatabase); + expect((db as SentryDatabase).dbName, inMemoryDatabasePath); await db.close(); });