diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f259f8b0..6442a03abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fixes + +- Fix transaction end timestamp trimming ([#1916](https://github.com/getsentry/sentry-dart/pull/1916)) + - Transaction end timestamps are now correctly trimmed to the latest child span end timestamp + ### Features - Use `recordHttpBreadcrumbs` to set iOS `enableNetworkBreadcrumbs` ([#1884](https://github.com/getsentry/sentry-dart/pull/1884)) diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index c57912fe46..c7f6993ad3 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -109,18 +109,24 @@ class SentryTracer extends ISentrySpan { } var _rootEndTimestamp = commonEndTimestamp; + + // Trim the end timestamp of the transaction to the very last timestamp of child spans if (_trimEnd && children.isNotEmpty) { - final childEndTimestamps = children - .where((child) => child.endTimestamp != null) - .map((child) => child.endTimestamp!); - - if (childEndTimestamps.isNotEmpty) { - final oldestChildEndTimestamp = - childEndTimestamps.reduce((a, b) => a.isAfter(b) ? a : b); - if (_rootEndTimestamp.isAfter(oldestChildEndTimestamp)) { - _rootEndTimestamp = oldestChildEndTimestamp; + DateTime? latestEndTime; + + for (final child in children) { + final childEndTimestamp = child.endTimestamp; + if (childEndTimestamp != null) { + if (latestEndTime == null || + childEndTimestamp.isAfter(latestEndTime)) { + latestEndTime = child.endTimestamp; + } } } + + if (latestEndTime != null) { + _rootEndTimestamp = latestEndTime; + } } // the callback should run before because if the span is finished, diff --git a/dart/test/sentry_tracer_test.dart b/dart/test/sentry_tracer_test.dart index 48ff011e82..1a328702d1 100644 --- a/dart/test/sentry_tracer_test.dart +++ b/dart/test/sentry_tracer_test.dart @@ -386,6 +386,28 @@ void main() { expect(sut.endTimestamp, endTimestamp); }); + test('end trimmed to latest child end timestamp', () async { + final sut = fixture.getSut(trimEnd: true); + final rootEndInitial = getUtcDateTime(); + final childAEnd = rootEndInitial; + final childBEnd = rootEndInitial.add(Duration(seconds: 1)); + final childCEnd = rootEndInitial; + + final childA = sut.startChild('operation-a', description: 'description'); + final childB = sut.startChild('operation-b', description: 'description'); + final childC = sut.startChild('operation-c', description: 'description'); + + await childA.finish(endTimestamp: childAEnd); + await childB.finish(endTimestamp: childBEnd); + await childC.finish(endTimestamp: childCEnd); + + await sut.finish(endTimestamp: rootEndInitial); + + expect(sut.endTimestamp, equals(childB.endTimestamp), + reason: + 'The root end timestamp should be updated to match the latest child end timestamp.'); + }); + test('does not add more spans than configured in options', () async { fixture.hub.options.maxSpans = 2; final sut = fixture.getSut();