Skip to content

Commit

Permalink
feat: Replay support for mobile (#2208)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
4 people authored Sep 2, 2024
1 parent e0ba81f commit aba65ca
Show file tree
Hide file tree
Showing 59 changed files with 2,409 additions and 413 deletions.
103 changes: 82 additions & 21 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,30 @@

### Features

- Session replay Alpha for Android and iOS ([#2208](https://github.com/getsentry/sentry-dart/pull/2208)).

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/)):

```dart
await SentryFlutter.init(
(options) {
...
options.experimental.replay.sessionSampleRate = 1.0;
options.experimental.replay.errorSampleRate = 1.0;
},
appRunner: () => runApp(MyApp()),
);
```

- Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239))
- This can be used to test if native crash reporting works
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218))
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
- Ignored routes will also create no TTID and TTFD spans.
```dart
SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
```
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
- Ignored routes will also create no TTID and TTFD spans.

```dart
SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
```

### Improvements

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

## 8.8.0-alpha.1

### Features

- iOS Session Replay Alpha ([#2209](https://github.com/getsentry/sentry-dart/pull/2209))
- Android replay touch tracking support ([#2228](https://github.com/getsentry/sentry-dart/pull/2228))
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218))
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
- Ignored routes will also create no TTID and TTFD spans.

```dart
SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
```

### Dependencies

- Bump Android SDK from v7.13.0 to v7.14.0 ([#2228](https://github.com/getsentry/sentry-dart/pull/2228))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7140)
- [diff](https://github.com/getsentry/sentry-java/compare/7.13.0...7.14.0)

## 8.7.0

### Features

- Add support for span level measurements. ([#2214](https://github.com/getsentry/sentry-dart/pull/2214))
- Add `ignoreTransactions` and `ignoreErrors` to options ([#2207](https://github.com/getsentry/sentry-dart/pull/2207))

```dart
await SentryFlutter.init(
(options) {
Expand All @@ -55,8 +92,10 @@ SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
appRunner: () => runApp(MyApp()),
);
```

- Add proxy support ([#2192](https://github.com/getsentry/sentry-dart/pull/2192))
- Configure a `SentryProxy` object and set it on `SentryFlutter.init`

```dart
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Expand Down Expand Up @@ -96,24 +135,25 @@ SentryNavigatorObserver(ignoreRoutes: ["/ignoreThisRoute"]),
- This is enabled automatically and will change grouping if you already have issues with obfuscated titles
- If you want to disable this feature, set `enableExceptionTypeIdentification` to `false` in your Sentry options
- You can add your custom exception identifier if there are exceptions that we do not identify out of the box
```dart
// How to add your own custom exception identifier
class MyCustomExceptionIdentifier implements ExceptionIdentifier {
@override
String? identifyType(Exception exception) {
if (exception is MyCustomException) {
return 'MyCustomException';
}
if (exception is MyOtherCustomException) {
return 'MyOtherCustomException';

```dart
// How to add your own custom exception identifier
class MyCustomExceptionIdentifier implements ExceptionIdentifier {
@override
String? identifyType(Exception exception) {
if (exception is MyCustomException) {
return 'MyCustomException';
}
if (exception is MyOtherCustomException) {
return 'MyOtherCustomException';
}
return null;
}
return null;
}
}
SentryFlutter.init((options) =>
options..prependExceptionTypeIdentifier(MyCustomExceptionIdentifier()));
```
SentryFlutter.init((options) =>
options..prependExceptionTypeIdentifier(MyCustomExceptionIdentifier()));
```

### Deprecated

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

## 8.6.0-alpha.2

### Features

- Android Session Replay Alpha ([#2032](https://github.com/getsentry/sentry-dart/pull/2032))

To try out replay, you can set following options:

```dart
await SentryFlutter.init(
(options) {
...
options.experimental.replay.sessionSampleRate = 1.0;
options.experimental.replay.errorSampleRate = 1.0;
},
appRunner: () => runApp(MyApp()),
);
```

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/)

## 8.5.0

### Features
Expand All @@ -141,7 +202,7 @@ SentryFlutter.init((options) =>
### Fixes

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

### Improvements

Expand Down
30 changes: 30 additions & 0 deletions dart/lib/src/protocol/breadcrumb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class Breadcrumb {
String? httpQuery,
String? httpFragment,
}) {
// The timestamp is used as the request-end time, so we need to set it right
// now and not rely on the default constructor.
timestamp ??= getUtcDateTime();

return Breadcrumb(
type: 'http',
category: 'http',
Expand All @@ -67,6 +71,11 @@ class Breadcrumb {
if (responseBodySize != null) 'response_body_size': responseBodySize,
if (httpQuery != null) 'http.query': httpQuery,
if (httpFragment != null) 'http.fragment': httpFragment,
if (requestDuration != null)
'start_timestamp':
timestamp.millisecondsSinceEpoch - requestDuration.inMilliseconds,
if (requestDuration != null)
'end_timestamp': timestamp.millisecondsSinceEpoch,
},
);
}
Expand Down Expand Up @@ -97,11 +106,32 @@ class Breadcrumb {
String? viewClass,
}) {
final newData = data ?? {};
var path = '';

if (viewId != null) {
newData['view.id'] = viewId;
path = viewId;
}

if (newData.containsKey('label')) {
if (path.isEmpty) {
path = newData['label'];
} else {
path = "$path, label: ${newData['label']}";
}
}

if (viewClass != null) {
newData['view.class'] = viewClass;
if (path.isEmpty) {
path = viewClass;
} else {
path = "$viewClass($path)";
}
}

if (path.isNotEmpty && !newData.containsKey('path')) {
newData['path'] = path;
}

return Breadcrumb(
Expand Down
17 changes: 13 additions & 4 deletions dart/lib/src/protocol/sentry_trace_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class SentryTraceContext {
/// Id of a parent span
final SpanId? parentSpanId;

/// Replay associated with this trace.
final SentryId? replayId;

/// Whether the span is sampled or not
final bool? sampled;

Expand Down Expand Up @@ -50,6 +53,9 @@ class SentryTraceContext {
? null
: SpanId.fromId(json['parent_span_id'] as String),
traceId: SentryId.fromId(json['trace_id'] as String),
replayId: json['replay_id'] == null
? null
: SentryId.fromId(json['replay_id'] as String),
description: json['description'] as String?,
status: json['status'] == null
? null
Expand All @@ -68,6 +74,7 @@ class SentryTraceContext {
'trace_id': traceId.toString(),
'op': operation,
if (parentSpanId != null) 'parent_span_id': parentSpanId!.toString(),
if (replayId != null) 'replay_id': replayId!.toString(),
if (description != null) 'description': description,
if (status != null) 'status': status!.toString(),
if (origin != null) 'origin': origin,
Expand All @@ -84,6 +91,7 @@ class SentryTraceContext {
sampled: sampled,
origin: origin,
unknown: unknown,
replayId: replayId,
);

SentryTraceContext({
Expand All @@ -96,16 +104,17 @@ class SentryTraceContext {
this.status,
this.origin,
this.unknown,
this.replayId,
}) : traceId = traceId ?? SentryId.newId(),
spanId = spanId ?? SpanId.newId();

@internal
factory SentryTraceContext.fromPropagationContext(
PropagationContext propagationContext) {
return SentryTraceContext(
traceId: propagationContext.traceId,
spanId: propagationContext.spanId,
operation: 'default',
);
traceId: propagationContext.traceId,
spanId: propagationContext.spanId,
operation: 'default',
replayId: propagationContext.baggage?.getReplayId());
}
}
11 changes: 10 additions & 1 deletion dart/lib/src/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ class Scope {
/// they must be JSON-serializable.
Map<String, dynamic> get extra => Map.unmodifiable(_extra);

/// Active replay recording.
@internal
SentryId? get replayId => _replayId;
@internal
set replayId(SentryId? value) => _replayId = value;
SentryId? _replayId;

final Contexts _contexts = Contexts();

/// Unmodifiable map of the scope contexts key/value
Expand Down Expand Up @@ -237,6 +244,7 @@ class Scope {
_tags.clear();
_extra.clear();
_eventProcessors.clear();
_replayId = null;

_clearBreadcrumbsSync();
_setUserSync(null);
Expand Down Expand Up @@ -429,7 +437,8 @@ class Scope {
..fingerprint = List.from(fingerprint)
.._transaction = _transaction
..span = span
.._enableScopeSync = false;
.._enableScopeSync = false
.._replayId = _replayId;

clone._setUserSync(user);

Expand Down
10 changes: 10 additions & 0 deletions dart/lib/src/sentry_baggage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class SentryBaggage {
// ignore: deprecated_member_use_from_same_package
setUserSegment(scope.user!.segment!);
}
if (scope.replayId != null && scope.replayId != SentryId.empty()) {
setReplayId(scope.replayId.toString());
}
}

static Map<String, String> _extractKeyValuesFromBaggageString(
Expand Down Expand Up @@ -205,5 +208,12 @@ class SentryBaggage {
return double.tryParse(sampleRate);
}

void setReplayId(String value) => set('sentry-replay_id', value);

SentryId? getReplayId() {
final replayId = get('sentry-replay_id');
return replayId == null ? null : SentryId.fromId(replayId);
}

Map<String, String> get keyValues => Map.unmodifiable(_keyValues);
}
10 changes: 5 additions & 5 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,15 @@ class SentryClient {

var traceContext = scope?.span?.traceContext();
if (traceContext == null) {
if (scope?.propagationContext.baggage == null) {
scope?.propagationContext.baggage =
SentryBaggage({}, logger: _options.logger);
scope?.propagationContext.baggage?.setValuesFromScope(scope, _options);
}
if (scope != null) {
scope.propagationContext.baggage ??=
SentryBaggage({}, logger: _options.logger)
..setValuesFromScope(scope, _options);
traceContext = SentryTraceContextHeader.fromBaggage(
scope.propagationContext.baggage!);
}
} else {
traceContext.replayId = scope?.replayId;
}

final envelope = SentryEnvelope.fromEvent(
Expand Down
Loading

0 comments on commit aba65ca

Please sign in to comment.