Skip to content

Commit aba65ca

Browse files
vaindbuenaflorgetsentry-botgetsentry-bot
authored
feat: Replay support for mobile (#2208)
* Flutter replay for Android (#2032) * minor gradle fixes * tmp: local sentry-java build * tmp: use relative path to sentry-java * tmp: local java build patches * replay options * replay recorder * wip: JNI native bindings * use compatible jnigen * add missing gradlew to flutter/android * replay recorder JNI binding code * replay recorder binding jni code * jni 0.6 * wip: android jni replay * replay binding * glue code for jni * chore: update to cocoa 8.24.1-alpha.0 * wip: cocoa integration * wip: ios replay * cleanup * formatting * android fixes * move native setup to the native sdk integration * cleanup & improvements * improve widget filter and implement redact options * fix image scaling * ktlint format * ci fixes * fix tests * add jnigen scripts * use android 7.9.0 alpha.1 * move native init & close to SentryNative * cleanup * add macOS integration link * rollback cocoa changes * remove jni/jnigen * wip: methodchannel based android recorder * callback * linter issues * minor fixes * more fixes * linter issues * cleanup * improve logging * move replay to experimental, same as in other SDKs * improve tree shaking * test: scheduler * support browser test * fix compat with old flutter * cleanup * rename recorder_widget_filter.dart * fixup scheduler test * improve test coverage * pr cleanup * test: widget filter * cleanup * test widget filter visibility * cleanup * always add screenshot widget * recorder test * cleanup * limit recorder test to vm * wip: integration test * cleanup * ktlint format * detekt suppression * ktlint format * improve scheduler stop behavior * wip: error replay mapping * suppress detekt TooGenericExceptionThrown * Update flutter/lib/src/replay/recorder.dart Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> * Update flutter/lib/src/native/java/sentry_native_java.dart Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> * improve comments * feat: associate dart errors with replays (#2070) * feat: associate dart errors with replays * ktlint * cleanup * tests * chote: remove path dependency * fix tests * feat: replay breadcrumbs (android) (#2163) * feat: replay breadcrumbs * ktlint format * fixup tests * cleanup * linter issues * detekt linter issue * move touch path build to dart to deduplicate * fix metrics app compilation * linter issue * test: native replay integration binding (#2189) * wip: test native integration * test: native replay binding * update example * chore: update pubspec * fixup tests * Update flutter/test/mocks.dart * chore: update changelog * fix publishing * release: 8.6.0-alpha.2 --------- Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> Co-authored-by: getsentry-bot <bot@sentry.io> Co-authored-by: getsentry-bot <bot@getsentry.com> * fix: update android calls after SDK update (#2211) * fix: update android calls after SDK update * ktlint * feat: iOS replay support (#2209) * minor gradle fixes * tmp: local sentry-java build * tmp: use relative path to sentry-java * tmp: local java build patches * replay options * replay recorder * wip: JNI native bindings * use compatible jnigen * add missing gradlew to flutter/android * replay recorder JNI binding code * replay recorder binding jni code * jni 0.6 * wip: android jni replay * replay binding * glue code for jni * chore: update to cocoa 8.24.1-alpha.0 * wip: cocoa integration * wip: ios replay * cleanup * formatting * android fixes * move native setup to the native sdk integration * cleanup & improvements * improve widget filter and implement redact options * fix image scaling * ktlint format * ci fixes * fix tests * add jnigen scripts * use android 7.9.0 alpha.1 * move native init & close to SentryNative * cleanup * add macOS integration link * rollback cocoa changes * remove jni/jnigen * wip: methodchannel based android recorder * callback * linter issues * minor fixes * more fixes * linter issues * cleanup * improve logging * move replay to experimental, same as in other SDKs * improve tree shaking * test: scheduler * support browser test * fix compat with old flutter * cleanup * rename recorder_widget_filter.dart * fixup scheduler test * improve test coverage * pr cleanup * test: widget filter * cleanup * test widget filter visibility * cleanup * always add screenshot widget * recorder test * cleanup * limit recorder test to vm * wip: integration test * cleanup * ktlint format * detekt suppression * ktlint format * improve scheduler stop behavior * wip: error replay mapping * suppress detekt TooGenericExceptionThrown * Update flutter/lib/src/replay/recorder.dart Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> * Update flutter/lib/src/native/java/sentry_native_java.dart Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> * improve comments * feat: associate dart errors with replays (#2070) * feat: associate dart errors with replays * ktlint * cleanup * tests * chote: remove path dependency * wip: ios replay * fix result callback * iOS related refactorings * logs * fix tests * call captureReplay on iOS & set * ios replay breadcrumbs * feat: replay breadcrumbs (android) (#2163) * feat: replay breadcrumbs * ktlint format * fixup tests * cleanup * linter issues * detekt linter issue * move touch path build to dart to deduplicate * fix metrics app compilation * linter issue * test: native replay integration binding (#2189) * wip: test native integration * test: native replay binding * update example * chore: update pubspec * fixup tests * Update flutter/test/mocks.dart * chore: update changelog * fix publishing * release: 8.6.0-alpha.2 * cleanup * fix macos compilation * test: iOS support * linter issues * linter issues * chore: update changelog * Update flutter/lib/src/native/cocoa/sentry_native_cocoa.dart Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> --------- Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> Co-authored-by: getsentry-bot <bot@sentry.io> Co-authored-by: getsentry-bot <bot@getsentry.com> * fix: cocoa sdk renamed errorSampleRate to onErrorSampleRate * fixup changelog * release: 8.8.0-alpha.1 * chore: update changelog * update changelog --------- Co-authored-by: Giancarlo Buenaflor <giancarlo_buenaflor@yahoo.com> Co-authored-by: getsentry-bot <bot@sentry.io> Co-authored-by: getsentry-bot <bot@getsentry.com>
1 parent e0ba81f commit aba65ca

File tree

59 files changed

+2409
-413
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2409
-413
lines changed

CHANGELOG.md

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,30 @@
1616

1717
### Features
1818

19+
- Session replay Alpha for Android and iOS ([#2208](https://github.com/getsentry/sentry-dart/pull/2208)).
20+
21+
To try out replay, you can set following options (access is limited to early access orgs on Sentry. If you're interested, [sign up for the waitlist](https://sentry.io/lp/mobile-replay-beta/)):
22+
23+
```dart
24+
await SentryFlutter.init(
25+
(options) {
26+
...
27+
options.experimental.replay.sessionSampleRate = 1.0;
28+
options.experimental.replay.errorSampleRate = 1.0;
29+
},
30+
appRunner: () => runApp(MyApp()),
31+
);
32+
```
33+
1934
- Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239))
2035
- This can be used to test if native crash reporting works
2136
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218))
22-
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
23-
- Ignored routes will also create no TTID and TTFD spans.
24-
```dart
25-
SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
26-
```
37+
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
38+
- Ignored routes will also create no TTID and TTFD spans.
39+
40+
```dart
41+
SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
42+
```
2743

2844
### Improvements
2945

@@ -38,12 +54,33 @@ SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
3854
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7140)
3955
- [diff](https://github.com/getsentry/sentry-java/compare/7.13.0...7.14.0)
4056

57+
## 8.8.0-alpha.1
58+
59+
### Features
60+
61+
- iOS Session Replay Alpha ([#2209](https://github.com/getsentry/sentry-dart/pull/2209))
62+
- Android replay touch tracking support ([#2228](https://github.com/getsentry/sentry-dart/pull/2228))
63+
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218))
64+
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
65+
- Ignored routes will also create no TTID and TTFD spans.
66+
67+
```dart
68+
SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
69+
```
70+
71+
### Dependencies
72+
73+
- Bump Android SDK from v7.13.0 to v7.14.0 ([#2228](https://github.com/getsentry/sentry-dart/pull/2228))
74+
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7140)
75+
- [diff](https://github.com/getsentry/sentry-java/compare/7.13.0...7.14.0)
76+
4177
## 8.7.0
4278

4379
### Features
4480

4581
- Add support for span level measurements. ([#2214](https://github.com/getsentry/sentry-dart/pull/2214))
4682
- Add `ignoreTransactions` and `ignoreErrors` to options ([#2207](https://github.com/getsentry/sentry-dart/pull/2207))
83+
4784
```dart
4885
await SentryFlutter.init(
4986
(options) {
@@ -55,8 +92,10 @@ SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
5592
appRunner: () => runApp(MyApp()),
5693
);
5794
```
95+
5896
- Add proxy support ([#2192](https://github.com/getsentry/sentry-dart/pull/2192))
5997
- Configure a `SentryProxy` object and set it on `SentryFlutter.init`
98+
6099
```dart
61100
import 'package:flutter/widgets.dart';
62101
import 'package:sentry_flutter/sentry_flutter.dart';
@@ -96,24 +135,25 @@ SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
96135
- This is enabled automatically and will change grouping if you already have issues with obfuscated titles
97136
- If you want to disable this feature, set `enableExceptionTypeIdentification` to `false` in your Sentry options
98137
- You can add your custom exception identifier if there are exceptions that we do not identify out of the box
99-
```dart
100-
// How to add your own custom exception identifier
101-
class MyCustomExceptionIdentifier implements ExceptionIdentifier {
102-
@override
103-
String? identifyType(Exception exception) {
104-
if (exception is MyCustomException) {
105-
return 'MyCustomException';
106-
}
107-
if (exception is MyOtherCustomException) {
108-
return 'MyOtherCustomException';
138+
139+
```dart
140+
// How to add your own custom exception identifier
141+
class MyCustomExceptionIdentifier implements ExceptionIdentifier {
142+
@override
143+
String? identifyType(Exception exception) {
144+
if (exception is MyCustomException) {
145+
return 'MyCustomException';
146+
}
147+
if (exception is MyOtherCustomException) {
148+
return 'MyOtherCustomException';
149+
}
150+
return null;
109151
}
110-
return null;
111152
}
112-
}
113153
114-
SentryFlutter.init((options) =>
115-
options..prependExceptionTypeIdentifier(MyCustomExceptionIdentifier()));
116-
```
154+
SentryFlutter.init((options) =>
155+
options..prependExceptionTypeIdentifier(MyCustomExceptionIdentifier()));
156+
```
117157

118158
### Deprecated
119159

@@ -129,6 +169,27 @@ SentryFlutter.init((options) =>
129169
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7130)
130170
- [diff](https://github.com/getsentry/sentry-java/compare/7.12.0...7.13.0)
131171

172+
## 8.6.0-alpha.2
173+
174+
### Features
175+
176+
- Android Session Replay Alpha ([#2032](https://github.com/getsentry/sentry-dart/pull/2032))
177+
178+
To try out replay, you can set following options:
179+
180+
```dart
181+
await SentryFlutter.init(
182+
(options) {
183+
...
184+
options.experimental.replay.sessionSampleRate = 1.0;
185+
options.experimental.replay.errorSampleRate = 1.0;
186+
},
187+
appRunner: () => runApp(MyApp()),
188+
);
189+
```
190+
191+
Access is limited to early access orgs on Sentry. If you're interested, [sign up for the waitlist](https://sentry.io/lp/mobile-replay-beta/)
192+
132193
## 8.5.0
133194

134195
### Features
@@ -141,7 +202,7 @@ SentryFlutter.init((options) =>
141202
### Fixes
142203

143204
- Disable sff & frame delay detection on web, linux and windows ([#2182](https://github.com/getsentry/sentry-dart/pull/2182))
144-
- Display refresh rate is locked at 60 for these platforms which can lead to inaccurate metrics
205+
- Display refresh rate is locked at 60 for these platforms which can lead to inaccurate metrics
145206

146207
### Improvements
147208

dart/lib/src/protocol/breadcrumb.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class Breadcrumb {
5252
String? httpQuery,
5353
String? httpFragment,
5454
}) {
55+
// The timestamp is used as the request-end time, so we need to set it right
56+
// now and not rely on the default constructor.
57+
timestamp ??= getUtcDateTime();
58+
5559
return Breadcrumb(
5660
type: 'http',
5761
category: 'http',
@@ -67,6 +71,11 @@ class Breadcrumb {
6771
if (responseBodySize != null) 'response_body_size': responseBodySize,
6872
if (httpQuery != null) 'http.query': httpQuery,
6973
if (httpFragment != null) 'http.fragment': httpFragment,
74+
if (requestDuration != null)
75+
'start_timestamp':
76+
timestamp.millisecondsSinceEpoch - requestDuration.inMilliseconds,
77+
if (requestDuration != null)
78+
'end_timestamp': timestamp.millisecondsSinceEpoch,
7079
},
7180
);
7281
}
@@ -97,11 +106,32 @@ class Breadcrumb {
97106
String? viewClass,
98107
}) {
99108
final newData = data ?? {};
109+
var path = '';
110+
100111
if (viewId != null) {
101112
newData['view.id'] = viewId;
113+
path = viewId;
114+
}
115+
116+
if (newData.containsKey('label')) {
117+
if (path.isEmpty) {
118+
path = newData['label'];
119+
} else {
120+
path = "$path, label: ${newData['label']}";
121+
}
102122
}
123+
103124
if (viewClass != null) {
104125
newData['view.class'] = viewClass;
126+
if (path.isEmpty) {
127+
path = viewClass;
128+
} else {
129+
path = "$viewClass($path)";
130+
}
131+
}
132+
133+
if (path.isNotEmpty && !newData.containsKey('path')) {
134+
newData['path'] = path;
105135
}
106136

107137
return Breadcrumb(

dart/lib/src/protocol/sentry_trace_context.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class SentryTraceContext {
1818
/// Id of a parent span
1919
final SpanId? parentSpanId;
2020

21+
/// Replay associated with this trace.
22+
final SentryId? replayId;
23+
2124
/// Whether the span is sampled or not
2225
final bool? sampled;
2326

@@ -50,6 +53,9 @@ class SentryTraceContext {
5053
? null
5154
: SpanId.fromId(json['parent_span_id'] as String),
5255
traceId: SentryId.fromId(json['trace_id'] as String),
56+
replayId: json['replay_id'] == null
57+
? null
58+
: SentryId.fromId(json['replay_id'] as String),
5359
description: json['description'] as String?,
5460
status: json['status'] == null
5561
? null
@@ -68,6 +74,7 @@ class SentryTraceContext {
6874
'trace_id': traceId.toString(),
6975
'op': operation,
7076
if (parentSpanId != null) 'parent_span_id': parentSpanId!.toString(),
77+
if (replayId != null) 'replay_id': replayId!.toString(),
7178
if (description != null) 'description': description,
7279
if (status != null) 'status': status!.toString(),
7380
if (origin != null) 'origin': origin,
@@ -84,6 +91,7 @@ class SentryTraceContext {
8491
sampled: sampled,
8592
origin: origin,
8693
unknown: unknown,
94+
replayId: replayId,
8795
);
8896

8997
SentryTraceContext({
@@ -96,16 +104,17 @@ class SentryTraceContext {
96104
this.status,
97105
this.origin,
98106
this.unknown,
107+
this.replayId,
99108
}) : traceId = traceId ?? SentryId.newId(),
100109
spanId = spanId ?? SpanId.newId();
101110

102111
@internal
103112
factory SentryTraceContext.fromPropagationContext(
104113
PropagationContext propagationContext) {
105114
return SentryTraceContext(
106-
traceId: propagationContext.traceId,
107-
spanId: propagationContext.spanId,
108-
operation: 'default',
109-
);
115+
traceId: propagationContext.traceId,
116+
spanId: propagationContext.spanId,
117+
operation: 'default',
118+
replayId: propagationContext.baggage?.getReplayId());
110119
}
111120
}

dart/lib/src/scope.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ class Scope {
9797
/// they must be JSON-serializable.
9898
Map<String, dynamic> get extra => Map.unmodifiable(_extra);
9999

100+
/// Active replay recording.
101+
@internal
102+
SentryId? get replayId => _replayId;
103+
@internal
104+
set replayId(SentryId? value) => _replayId = value;
105+
SentryId? _replayId;
106+
100107
final Contexts _contexts = Contexts();
101108

102109
/// Unmodifiable map of the scope contexts key/value
@@ -237,6 +244,7 @@ class Scope {
237244
_tags.clear();
238245
_extra.clear();
239246
_eventProcessors.clear();
247+
_replayId = null;
240248

241249
_clearBreadcrumbsSync();
242250
_setUserSync(null);
@@ -429,7 +437,8 @@ class Scope {
429437
..fingerprint = List.from(fingerprint)
430438
.._transaction = _transaction
431439
..span = span
432-
.._enableScopeSync = false;
440+
.._enableScopeSync = false
441+
.._replayId = _replayId;
433442

434443
clone._setUserSync(user);
435444

dart/lib/src/sentry_baggage.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ class SentryBaggage {
111111
// ignore: deprecated_member_use_from_same_package
112112
setUserSegment(scope.user!.segment!);
113113
}
114+
if (scope.replayId != null && scope.replayId != SentryId.empty()) {
115+
setReplayId(scope.replayId.toString());
116+
}
114117
}
115118

116119
static Map<String, String> _extractKeyValuesFromBaggageString(
@@ -205,5 +208,12 @@ class SentryBaggage {
205208
return double.tryParse(sampleRate);
206209
}
207210

211+
void setReplayId(String value) => set('sentry-replay_id', value);
212+
213+
SentryId? getReplayId() {
214+
final replayId = get('sentry-replay_id');
215+
return replayId == null ? null : SentryId.fromId(replayId);
216+
}
217+
208218
Map<String, String> get keyValues => Map.unmodifiable(_keyValues);
209219
}

dart/lib/src/sentry_client.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,15 @@ class SentryClient {
167167

168168
var traceContext = scope?.span?.traceContext();
169169
if (traceContext == null) {
170-
if (scope?.propagationContext.baggage == null) {
171-
scope?.propagationContext.baggage =
172-
SentryBaggage({}, logger: _options.logger);
173-
scope?.propagationContext.baggage?.setValuesFromScope(scope, _options);
174-
}
175170
if (scope != null) {
171+
scope.propagationContext.baggage ??=
172+
SentryBaggage({}, logger: _options.logger)
173+
..setValuesFromScope(scope, _options);
176174
traceContext = SentryTraceContextHeader.fromBaggage(
177175
scope.propagationContext.baggage!);
178176
}
177+
} else {
178+
traceContext.replayId = scope?.replayId;
179179
}
180180

181181
final envelope = SentryEnvelope.fromEvent(

0 commit comments

Comments
 (0)