Skip to content

Commit

Permalink
Add Hive breadcrumbs (#1773)
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase authored Dec 5, 2023
1 parent 2d74010 commit d3801f8
Show file tree
Hide file tree
Showing 6 changed files with 866 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Features

- Add option to opt out of fatal level for automatically collected errors ([#1738](https://github.com/getsentry/sentry-dart/pull/1738))
- Add `Hive` breadcrumbs ([#1773](https://github.com/getsentry/sentry-dart/pull/1773))

### Dependencies

Expand Down
22 changes: 21 additions & 1 deletion hive/lib/src/sentry_span_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,44 @@ class SentrySpanHelper {
// ignore: invalid_use_of_internal_member
span?.origin = _origin;

span?.setData(SentryHiveImpl.dbSystemKey, SentryHiveImpl.dbSystem);
var breadcrumb = Breadcrumb(
message: description,
data: {},
type: 'query',
);

span?.setData(SentryHiveImpl.dbSystemKey, SentryHiveImpl.dbSystem);
if (dbName != null) {
span?.setData(SentryHiveImpl.dbNameKey, dbName);
}

breadcrumb.data?[SentryHiveImpl.dbSystemKey] = SentryHiveImpl.dbSystem;
if (dbName != null) {
breadcrumb.data?[SentryHiveImpl.dbNameKey] = dbName;
}

try {
final result = await execute();

span?.status = SpanStatus.ok();
breadcrumb.data?['status'] = 'ok';

return result;
} catch (exception) {
span?.throwable = exception;
span?.status = SpanStatus.internalError();

breadcrumb.data?['status'] = 'internal_error';
breadcrumb = breadcrumb.copyWith(
level: SentryLevel.warning,
);

rethrow;
} finally {
await span?.finish();

// ignore: invalid_use_of_internal_member
await _hub.scope.addBreadcrumb(breadcrumb);
}
}
}
272 changes: 272 additions & 0 deletions hive/test/sentry_box_base_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ void main() {
expect(span?.throwable, exception);
}

void verifyBreadcrumb(
String message,
Breadcrumb? crumb, {
bool checkName = false,
String status = 'ok',
}) {
expect(
crumb?.message,
message,
);
expect(crumb?.type, 'query');
if (checkName) {
expect(crumb?.data?[SentryHiveImpl.dbNameKey], Fixture.dbName);
}
expect(crumb?.data?['status'], status);
}

group('adds span', () {
late Fixture fixture;

Expand All @@ -49,6 +66,7 @@ void main() {

when(fixture.hub.options).thenReturn(fixture.options);
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
when(fixture.hub.scope).thenReturn(fixture.scope);
});

tearDown(() async {
Expand Down Expand Up @@ -131,6 +149,7 @@ void main() {
when(fixture.hub.options).thenReturn(fixture.options);
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
when(fixture.mockBox.name).thenReturn(Fixture.dbName);
when(fixture.hub.scope).thenReturn(fixture.scope);
});

tearDown(() async {
Expand Down Expand Up @@ -253,6 +272,254 @@ void main() {
verifyErrorSpan('deleteAt', fixture.exception, fixture.getCreatedSpan());
});
});

group('adds breadcrumb', () {
late Fixture fixture;

setUp(() async {
fixture = Fixture();
await fixture.setUp();

when(fixture.hub.options).thenReturn(fixture.options);
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
when(fixture.hub.scope).thenReturn(fixture.scope);
});

tearDown(() async {
await fixture.tearDown();
});

test('add adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.add(Person('Joe Dirt'));

verifyBreadcrumb('add', fixture.getCreatedBreadcrumb());
});

test('addAll adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.addAll([Person('Joe Dirt')]);

verifyBreadcrumb('addAll', fixture.getCreatedBreadcrumb());
});

test('clear adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.clear();

verifyBreadcrumb('clear', fixture.getCreatedBreadcrumb());
});

test('close adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.close();

verifyBreadcrumb('close', fixture.getCreatedBreadcrumb());
});

test('compact adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.compact();

verifyBreadcrumb('compact', fixture.getCreatedBreadcrumb());
});

test('delete adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.delete('fixture-key');

verifyBreadcrumb('delete', fixture.getCreatedBreadcrumb());
});

test('deleteAll adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.deleteAll(['fixture-key']);

verifyBreadcrumb('deleteAll', fixture.getCreatedBreadcrumb());
});

test('deleteAt adds breadcrumb', () async {
final sut = fixture.getSut();

await sut.add(Person('Joe Dirt'));
await sut.deleteAt(0);

verifyBreadcrumb('deleteAt', fixture.getCreatedBreadcrumb());
});
});

group('adds error breadcrumb', () {
late Fixture fixture;

setUp(() async {
fixture = Fixture();
await fixture.setUp();

when(fixture.hub.options).thenReturn(fixture.options);
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
when(fixture.mockBox.name).thenReturn(Fixture.dbName);
when(fixture.hub.scope).thenReturn(fixture.scope);
});

tearDown(() async {
await fixture.tearDown();
});

test('throwing add adds error breadcrumb', () async {
when(fixture.mockBox.add(any)).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.add(Person('Joe Dirt'));
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'add',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing addAll adds error breadcrumb', () async {
when(fixture.mockBox.addAll(any)).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.addAll([Person('Joe Dirt')]);
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'addAll',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing clear adds error breadcrumb', () async {
when(fixture.mockBox.clear()).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.clear();
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'clear',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing close adds error breadcrumb', () async {
when(fixture.mockBox.close()).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.close();
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'close',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing compact adds error breadcrumb', () async {
when(fixture.mockBox.compact()).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.compact();
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'compact',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing delete adds error breadcrumb', () async {
when(fixture.mockBox.delete(any)).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.delete('fixture-key');
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'delete',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing deleteAll adds error breadcrumb', () async {
when(fixture.mockBox.deleteAll(any)).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

try {
await sut.deleteAll(['fixture-key']);
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'deleteAll',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});

test('throwing deleteAt adds error breadcrumb', () async {
when(fixture.mockBox.add(any)).thenAnswer((_) async {
return 1;
});
when(fixture.mockBox.deleteAt(any)).thenThrow(fixture.exception);

final sut = fixture.getSut(injectMockBox: true);

await sut.add(Person('Joe Dirt'));
try {
await sut.deleteAt(0);
} catch (error) {
expect(error, fixture.exception);
}

verifyBreadcrumb(
'deleteAt',
fixture.getCreatedBreadcrumb(),
status: 'internal_error',
);
});
});
}

class Fixture {
Expand All @@ -266,6 +533,7 @@ class Fixture {

final _context = SentryTransactionContext('name', 'operation');
late final tracer = SentryTracer(_context, hub);
late final scope = Scope(options);

Future<void> setUp() async {
Hive.init(Directory.systemTemp.path);
Expand Down Expand Up @@ -294,4 +562,8 @@ class Fixture {
SentrySpan? getCreatedSpan() {
return tracer.children.last;
}

Breadcrumb? getCreatedBreadcrumb() {
return hub.scope.breadcrumbs.last;
}
}
Loading

0 comments on commit d3801f8

Please sign in to comment.