Skip to content

Commit d3801f8

Browse files
authored
Add Hive breadcrumbs (#1773)
1 parent 2d74010 commit d3801f8

6 files changed

+866
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
### Features
1111

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

1415
### Dependencies
1516

hive/lib/src/sentry_span_helper.dart

+21-1
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,44 @@ class SentrySpanHelper {
3535
// ignore: invalid_use_of_internal_member
3636
span?.origin = _origin;
3737

38-
span?.setData(SentryHiveImpl.dbSystemKey, SentryHiveImpl.dbSystem);
38+
var breadcrumb = Breadcrumb(
39+
message: description,
40+
data: {},
41+
type: 'query',
42+
);
3943

44+
span?.setData(SentryHiveImpl.dbSystemKey, SentryHiveImpl.dbSystem);
4045
if (dbName != null) {
4146
span?.setData(SentryHiveImpl.dbNameKey, dbName);
4247
}
4348

49+
breadcrumb.data?[SentryHiveImpl.dbSystemKey] = SentryHiveImpl.dbSystem;
50+
if (dbName != null) {
51+
breadcrumb.data?[SentryHiveImpl.dbNameKey] = dbName;
52+
}
53+
4454
try {
4555
final result = await execute();
56+
4657
span?.status = SpanStatus.ok();
58+
breadcrumb.data?['status'] = 'ok';
4759

4860
return result;
4961
} catch (exception) {
5062
span?.throwable = exception;
5163
span?.status = SpanStatus.internalError();
5264

65+
breadcrumb.data?['status'] = 'internal_error';
66+
breadcrumb = breadcrumb.copyWith(
67+
level: SentryLevel.warning,
68+
);
69+
5370
rethrow;
5471
} finally {
5572
await span?.finish();
73+
74+
// ignore: invalid_use_of_internal_member
75+
await _hub.scope.addBreadcrumb(breadcrumb);
5676
}
5777
}
5878
}

hive/test/sentry_box_base_test.dart

+272
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ void main() {
4040
expect(span?.throwable, exception);
4141
}
4242

43+
void verifyBreadcrumb(
44+
String message,
45+
Breadcrumb? crumb, {
46+
bool checkName = false,
47+
String status = 'ok',
48+
}) {
49+
expect(
50+
crumb?.message,
51+
message,
52+
);
53+
expect(crumb?.type, 'query');
54+
if (checkName) {
55+
expect(crumb?.data?[SentryHiveImpl.dbNameKey], Fixture.dbName);
56+
}
57+
expect(crumb?.data?['status'], status);
58+
}
59+
4360
group('adds span', () {
4461
late Fixture fixture;
4562

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

5067
when(fixture.hub.options).thenReturn(fixture.options);
5168
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
69+
when(fixture.hub.scope).thenReturn(fixture.scope);
5270
});
5371

5472
tearDown(() async {
@@ -131,6 +149,7 @@ void main() {
131149
when(fixture.hub.options).thenReturn(fixture.options);
132150
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
133151
when(fixture.mockBox.name).thenReturn(Fixture.dbName);
152+
when(fixture.hub.scope).thenReturn(fixture.scope);
134153
});
135154

136155
tearDown(() async {
@@ -253,6 +272,254 @@ void main() {
253272
verifyErrorSpan('deleteAt', fixture.exception, fixture.getCreatedSpan());
254273
});
255274
});
275+
276+
group('adds breadcrumb', () {
277+
late Fixture fixture;
278+
279+
setUp(() async {
280+
fixture = Fixture();
281+
await fixture.setUp();
282+
283+
when(fixture.hub.options).thenReturn(fixture.options);
284+
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
285+
when(fixture.hub.scope).thenReturn(fixture.scope);
286+
});
287+
288+
tearDown(() async {
289+
await fixture.tearDown();
290+
});
291+
292+
test('add adds breadcrumb', () async {
293+
final sut = fixture.getSut();
294+
295+
await sut.add(Person('Joe Dirt'));
296+
297+
verifyBreadcrumb('add', fixture.getCreatedBreadcrumb());
298+
});
299+
300+
test('addAll adds breadcrumb', () async {
301+
final sut = fixture.getSut();
302+
303+
await sut.addAll([Person('Joe Dirt')]);
304+
305+
verifyBreadcrumb('addAll', fixture.getCreatedBreadcrumb());
306+
});
307+
308+
test('clear adds breadcrumb', () async {
309+
final sut = fixture.getSut();
310+
311+
await sut.clear();
312+
313+
verifyBreadcrumb('clear', fixture.getCreatedBreadcrumb());
314+
});
315+
316+
test('close adds breadcrumb', () async {
317+
final sut = fixture.getSut();
318+
319+
await sut.close();
320+
321+
verifyBreadcrumb('close', fixture.getCreatedBreadcrumb());
322+
});
323+
324+
test('compact adds breadcrumb', () async {
325+
final sut = fixture.getSut();
326+
327+
await sut.compact();
328+
329+
verifyBreadcrumb('compact', fixture.getCreatedBreadcrumb());
330+
});
331+
332+
test('delete adds breadcrumb', () async {
333+
final sut = fixture.getSut();
334+
335+
await sut.delete('fixture-key');
336+
337+
verifyBreadcrumb('delete', fixture.getCreatedBreadcrumb());
338+
});
339+
340+
test('deleteAll adds breadcrumb', () async {
341+
final sut = fixture.getSut();
342+
343+
await sut.deleteAll(['fixture-key']);
344+
345+
verifyBreadcrumb('deleteAll', fixture.getCreatedBreadcrumb());
346+
});
347+
348+
test('deleteAt adds breadcrumb', () async {
349+
final sut = fixture.getSut();
350+
351+
await sut.add(Person('Joe Dirt'));
352+
await sut.deleteAt(0);
353+
354+
verifyBreadcrumb('deleteAt', fixture.getCreatedBreadcrumb());
355+
});
356+
});
357+
358+
group('adds error breadcrumb', () {
359+
late Fixture fixture;
360+
361+
setUp(() async {
362+
fixture = Fixture();
363+
await fixture.setUp();
364+
365+
when(fixture.hub.options).thenReturn(fixture.options);
366+
when(fixture.hub.getSpan()).thenReturn(fixture.tracer);
367+
when(fixture.mockBox.name).thenReturn(Fixture.dbName);
368+
when(fixture.hub.scope).thenReturn(fixture.scope);
369+
});
370+
371+
tearDown(() async {
372+
await fixture.tearDown();
373+
});
374+
375+
test('throwing add adds error breadcrumb', () async {
376+
when(fixture.mockBox.add(any)).thenThrow(fixture.exception);
377+
378+
final sut = fixture.getSut(injectMockBox: true);
379+
380+
try {
381+
await sut.add(Person('Joe Dirt'));
382+
} catch (error) {
383+
expect(error, fixture.exception);
384+
}
385+
386+
verifyBreadcrumb(
387+
'add',
388+
fixture.getCreatedBreadcrumb(),
389+
status: 'internal_error',
390+
);
391+
});
392+
393+
test('throwing addAll adds error breadcrumb', () async {
394+
when(fixture.mockBox.addAll(any)).thenThrow(fixture.exception);
395+
396+
final sut = fixture.getSut(injectMockBox: true);
397+
398+
try {
399+
await sut.addAll([Person('Joe Dirt')]);
400+
} catch (error) {
401+
expect(error, fixture.exception);
402+
}
403+
404+
verifyBreadcrumb(
405+
'addAll',
406+
fixture.getCreatedBreadcrumb(),
407+
status: 'internal_error',
408+
);
409+
});
410+
411+
test('throwing clear adds error breadcrumb', () async {
412+
when(fixture.mockBox.clear()).thenThrow(fixture.exception);
413+
414+
final sut = fixture.getSut(injectMockBox: true);
415+
416+
try {
417+
await sut.clear();
418+
} catch (error) {
419+
expect(error, fixture.exception);
420+
}
421+
422+
verifyBreadcrumb(
423+
'clear',
424+
fixture.getCreatedBreadcrumb(),
425+
status: 'internal_error',
426+
);
427+
});
428+
429+
test('throwing close adds error breadcrumb', () async {
430+
when(fixture.mockBox.close()).thenThrow(fixture.exception);
431+
432+
final sut = fixture.getSut(injectMockBox: true);
433+
434+
try {
435+
await sut.close();
436+
} catch (error) {
437+
expect(error, fixture.exception);
438+
}
439+
440+
verifyBreadcrumb(
441+
'close',
442+
fixture.getCreatedBreadcrumb(),
443+
status: 'internal_error',
444+
);
445+
});
446+
447+
test('throwing compact adds error breadcrumb', () async {
448+
when(fixture.mockBox.compact()).thenThrow(fixture.exception);
449+
450+
final sut = fixture.getSut(injectMockBox: true);
451+
452+
try {
453+
await sut.compact();
454+
} catch (error) {
455+
expect(error, fixture.exception);
456+
}
457+
458+
verifyBreadcrumb(
459+
'compact',
460+
fixture.getCreatedBreadcrumb(),
461+
status: 'internal_error',
462+
);
463+
});
464+
465+
test('throwing delete adds error breadcrumb', () async {
466+
when(fixture.mockBox.delete(any)).thenThrow(fixture.exception);
467+
468+
final sut = fixture.getSut(injectMockBox: true);
469+
470+
try {
471+
await sut.delete('fixture-key');
472+
} catch (error) {
473+
expect(error, fixture.exception);
474+
}
475+
476+
verifyBreadcrumb(
477+
'delete',
478+
fixture.getCreatedBreadcrumb(),
479+
status: 'internal_error',
480+
);
481+
});
482+
483+
test('throwing deleteAll adds error breadcrumb', () async {
484+
when(fixture.mockBox.deleteAll(any)).thenThrow(fixture.exception);
485+
486+
final sut = fixture.getSut(injectMockBox: true);
487+
488+
try {
489+
await sut.deleteAll(['fixture-key']);
490+
} catch (error) {
491+
expect(error, fixture.exception);
492+
}
493+
494+
verifyBreadcrumb(
495+
'deleteAll',
496+
fixture.getCreatedBreadcrumb(),
497+
status: 'internal_error',
498+
);
499+
});
500+
501+
test('throwing deleteAt adds error breadcrumb', () async {
502+
when(fixture.mockBox.add(any)).thenAnswer((_) async {
503+
return 1;
504+
});
505+
when(fixture.mockBox.deleteAt(any)).thenThrow(fixture.exception);
506+
507+
final sut = fixture.getSut(injectMockBox: true);
508+
509+
await sut.add(Person('Joe Dirt'));
510+
try {
511+
await sut.deleteAt(0);
512+
} catch (error) {
513+
expect(error, fixture.exception);
514+
}
515+
516+
verifyBreadcrumb(
517+
'deleteAt',
518+
fixture.getCreatedBreadcrumb(),
519+
status: 'internal_error',
520+
);
521+
});
522+
});
256523
}
257524

258525
class Fixture {
@@ -266,6 +533,7 @@ class Fixture {
266533

267534
final _context = SentryTransactionContext('name', 'operation');
268535
late final tracer = SentryTracer(_context, hub);
536+
late final scope = Scope(options);
269537

270538
Future<void> setUp() async {
271539
Hive.init(Directory.systemTemp.path);
@@ -294,4 +562,8 @@ class Fixture {
294562
SentrySpan? getCreatedSpan() {
295563
return tracer.children.last;
296564
}
565+
566+
Breadcrumb? getCreatedBreadcrumb() {
567+
return hub.scope.breadcrumbs.last;
568+
}
297569
}

0 commit comments

Comments
 (0)