Skip to content

Commit 2d65d04

Browse files
authored
fix(logs): Use seconds since the Unix epoch for log.timestamp (#3558)
We have incorrectly used `timestamp.toIso8601String()` and this was only accepted because the backend does lenient date/timestamp parsing. On the native Android error it does lead to parsing issues though, as reported by a user.
1 parent 1343a83 commit 2d65d04

File tree

7 files changed

+24
-12
lines changed

7 files changed

+24
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Stop re-triggering hitTest in SentryUserInteractionWidget on pointerUp ([#3540](https://github.com/getsentry/sentry-dart/pull/3540))
8+
- Use seconds since the Unix epoch for `log.timestamp` ([#3558](https://github.com/getsentry/sentry-dart/pull/3558))
89
- Implement `SqfliteDatabaseExecutor` to prevent `TypeError` on `getVersion/setVersion` ([3539](https://github.com/getsentry/sentry-dart/pull/3539))
910

1011
### Dependencies

packages/dart/lib/src/telemetry/log/log.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import '../../protocol/sentry_attribute.dart';
22
import '../../protocol/sentry_id.dart';
33
import '../../protocol/span_id.dart';
4+
import '../../utils/date_time_extension.dart';
45
import 'log_level.dart';
56

67
class SentryLog {
@@ -27,7 +28,7 @@ class SentryLog {
2728

2829
Map<String, dynamic> toJson() {
2930
return {
30-
'timestamp': timestamp.toIso8601String(),
31+
'timestamp': timestamp.secondsSinceEpoch,
3132
'trace_id': traceId.toString(),
3233
if (spanId != null) 'span_id': spanId.toString(),
3334
'level': level.value,

packages/dart/lib/src/telemetry/metric/metric.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:meta/meta.dart';
22

33
import '../../../sentry.dart';
4+
import '../../utils/date_time_extension.dart';
45

56
/// Base class for metric data points sent to Sentry.
67
///
@@ -47,7 +48,7 @@ abstract class SentryMetric {
4748
@internal
4849
Map<String, dynamic> toJson() {
4950
return {
50-
'timestamp': timestamp.millisecondsSinceEpoch / 1000.0,
51+
'timestamp': timestamp.secondsSinceEpoch,
5152
'type': type,
5253
'name': name,
5354
'value': value,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
extension DateTimeExtension on DateTime {
2+
double get secondsSinceEpoch => microsecondsSinceEpoch / 1000000.0;
3+
}

packages/dart/test/telemetry/log/log_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:sentry/sentry.dart';
33

44
void main() {
55
test('$SentryLog to json', () {
6-
final timestamp = DateTime.now();
6+
final timestamp = DateTime.utc(2024, 1, 15, 10, 30, 0, 123, 456);
77
final traceId = SentryId.newId();
88
final spanId = SpanId.newId();
99

@@ -25,7 +25,7 @@ void main() {
2525
final json = logItem.toJson();
2626

2727
expect(json, {
28-
'timestamp': timestamp.toIso8601String(),
28+
'timestamp': 1705314600.123456,
2929
'trace_id': traceId.toString(),
3030
'span_id': spanId.toString(),
3131
'level': 'info',

packages/dart/test/telemetry/metric/metric_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ void main() {
66
test('serializes all fields correctly', () {
77
final traceId = SentryId.newId();
88
final spanId = SpanId.newId();
9-
final timestamp = DateTime.utc(2024, 1, 15, 10, 30, 0);
9+
final timestamp = DateTime.utc(2024, 1, 15, 10, 30, 0, 123, 456);
1010

1111
final metric = SentryCounterMetric(
1212
timestamp: timestamp,
@@ -20,7 +20,7 @@ void main() {
2020

2121
final json = metric.toJson();
2222

23-
expect(json['timestamp'], 1705314600.0);
23+
expect(json['timestamp'], 1705314600.123456);
2424
expect(json['type'], 'counter');
2525
expect(json['name'], 'button_clicks');
2626
expect(json['value'], 5);

packages/flutter/test/user_interaction/sentry_user_interaction_widget_test.dart

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import 'package:sentry/src/sentry_tracer.dart';
1313
import '../mocks.dart';
1414
import '../mocks.mocks.dart';
1515

16+
// The Scaffold widget tree uses AnimatedBuilder on stable but Builder on beta.
17+
// Use anyOf to accept either name since this is a framework-internal detail.
18+
final _animatedBuilderElement = {
19+
'element': anyOf('AnimatedBuilder', 'Builder')
20+
};
21+
1622
void main() {
1723
late Fixture fixture;
1824
setUp(() async {
@@ -121,7 +127,7 @@ void main() {
121127
{'name': '_ScaffoldSlot.body', 'element': 'LayoutId'},
122128
{'element': 'CustomMultiChildLayout'},
123129
{'element': 'Actions'},
124-
{'element': 'AnimatedBuilder'},
130+
_animatedBuilderElement,
125131
{'element': 'DefaultTextStyle'}
126132
],
127133
'view.id': 'btn_1',
@@ -148,7 +154,7 @@ void main() {
148154
{'name': '_ScaffoldSlot.body', 'element': 'LayoutId'},
149155
{'element': 'CustomMultiChildLayout'},
150156
{'element': 'Actions'},
151-
{'element': 'AnimatedBuilder'},
157+
_animatedBuilderElement,
152158
{'element': 'DefaultTextStyle'}
153159
],
154160
'label': 'Button 1',
@@ -176,7 +182,7 @@ void main() {
176182
{'name': '_ScaffoldSlot.body', 'element': 'LayoutId'},
177183
{'element': 'CustomMultiChildLayout'},
178184
{'element': 'Actions'},
179-
{'element': 'AnimatedBuilder'},
185+
_animatedBuilderElement,
180186
{'element': 'DefaultTextStyle'}
181187
],
182188
'label': 'My Icon',
@@ -204,7 +210,7 @@ void main() {
204210
{'name': '_ScaffoldSlot.body', 'element': 'LayoutId'},
205211
{'element': 'CustomMultiChildLayout'},
206212
{'element': 'Actions'},
207-
{'element': 'AnimatedBuilder'},
213+
_animatedBuilderElement,
208214
{'element': 'DefaultTextStyle'}
209215
],
210216
'label': 'Button 2',
@@ -276,7 +282,7 @@ void main() {
276282
{'name': '_ScaffoldSlot.body', 'element': 'LayoutId'},
277283
{'element': 'CustomMultiChildLayout'},
278284
{'element': 'Actions'},
279-
{'element': 'AnimatedBuilder'},
285+
_animatedBuilderElement,
280286
{'element': 'DefaultTextStyle'}
281287
],
282288
'view.id': 'popup_menu_button',
@@ -394,7 +400,7 @@ void main() {
394400
{'name': '_ScaffoldSlot.body', 'element': 'LayoutId'},
395401
{'element': 'CustomMultiChildLayout'},
396402
{'element': 'Actions'},
397-
{'element': 'AnimatedBuilder'},
403+
_animatedBuilderElement,
398404
{'element': 'DefaultTextStyle'}
399405
],
400406
'label': 'Button 5',

0 commit comments

Comments
 (0)