Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flutter replay for Android #2032

Merged
merged 102 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
294317b
minor gradle fixes
vaind Apr 3, 2024
861ebab
tmp: local sentry-java build
vaind Apr 3, 2024
2f55619
tmp: use relative path to sentry-java
vaind Apr 4, 2024
209ca41
tmp: local java build patches
vaind Apr 4, 2024
a282fad
replay options
vaind Apr 10, 2024
7d8d4e8
replay recorder
vaind Apr 11, 2024
c887e6d
Merge branch 'main' into feat/replay
vaind Apr 11, 2024
3444961
wip: JNI native bindings
vaind Apr 11, 2024
ca03c95
use compatible jnigen
vaind Apr 17, 2024
0b06dd5
add missing gradlew to flutter/android
vaind Apr 17, 2024
314665d
Merge branch 'feat/jni-ffi' into feat/replay
vaind Apr 17, 2024
8bf52d8
replay recorder JNI binding code
vaind Apr 17, 2024
a4e056a
replay recorder binding jni code
vaind Apr 17, 2024
1cde833
jni 0.6
vaind Apr 18, 2024
010f575
wip: android jni replay
vaind Apr 18, 2024
4c4f132
replay binding
vaind Apr 18, 2024
87d18e7
glue code for jni
vaind Apr 22, 2024
2eaaa28
Merge branch 'main' into feat/replay
vaind Apr 23, 2024
afa9f50
chore: update to cocoa 8.24.1-alpha.0
vaind Apr 23, 2024
3df2523
wip: cocoa integration
vaind Apr 24, 2024
179b5d4
wip: ios replay
vaind Apr 24, 2024
9284db8
Merge branch 'main' into feat/replay
vaind Apr 25, 2024
6432b98
cleanup
vaind Apr 25, 2024
25fd690
formatting
vaind Apr 25, 2024
658a132
android fixes
vaind Apr 26, 2024
fb8bbb4
move native setup to the native sdk integration
vaind Apr 26, 2024
7c8fd42
cleanup & improvements
vaind Apr 29, 2024
0570f35
improve widget filter and implement redact options
vaind Apr 29, 2024
5c08b21
fix image scaling
vaind Apr 29, 2024
c00788a
Merge branch 'main' into feat/replay
vaind May 2, 2024
0c55dc6
ktlint format
vaind May 2, 2024
f136795
ci fixes
vaind May 2, 2024
2aa129c
fix tests
vaind May 2, 2024
698276d
add jnigen scripts
vaind May 2, 2024
c6c3a17
use android 7.9.0 alpha.1
vaind May 2, 2024
cf8ed52
move native init & close to SentryNative
vaind May 2, 2024
55c7056
cleanup
vaind May 2, 2024
ff2a8ed
add macOS integration link
vaind May 6, 2024
46a96b0
Merge branch 'main' into feat/replay
vaind May 6, 2024
c3b60aa
rollback cocoa changes
vaind May 6, 2024
3f6d05e
remove jni/jnigen
vaind May 6, 2024
af22a59
wip: methodchannel based android recorder
vaind May 6, 2024
6bf1f00
callback
vaind May 6, 2024
9dc14aa
linter issues
vaind May 6, 2024
9daf297
Merge branch 'main' into feat/android-replay
vaind May 6, 2024
3639f00
minor fixes
vaind May 6, 2024
14ba742
more fixes
vaind May 6, 2024
585386e
linter issues
vaind May 6, 2024
ee1dbd6
cleanup
vaind May 6, 2024
266a85a
improve logging
vaind May 6, 2024
509c15f
move replay to experimental, same as in other SDKs
vaind May 7, 2024
960f2da
improve tree shaking
vaind May 7, 2024
b5c935f
Merge branch 'main' into feat/android-replay
vaind May 7, 2024
95e3a34
test: scheduler
vaind May 7, 2024
aa28200
support browser test
vaind May 7, 2024
16f3677
fix compat with old flutter
vaind May 7, 2024
86db5c4
cleanup
vaind May 7, 2024
942044a
rename recorder_widget_filter.dart
vaind May 7, 2024
9efae7b
fixup scheduler test
vaind May 7, 2024
35ed86b
improve test coverage
vaind May 7, 2024
63af017
pr cleanup
vaind May 7, 2024
12f5774
test: widget filter
vaind May 8, 2024
50a13f6
cleanup
vaind May 8, 2024
725fd02
test widget filter visibility
vaind May 8, 2024
4bda0ab
cleanup
vaind May 8, 2024
afb65f6
always add screenshot widget
vaind May 8, 2024
f6b9266
recorder test
vaind May 8, 2024
5dc1255
cleanup
vaind May 8, 2024
225c0c0
limit recorder test to vm
vaind May 8, 2024
46527a3
wip: integration test
vaind May 9, 2024
0bc8fff
cleanup
vaind May 9, 2024
571dfbc
Merge branch 'main' into feat/android-replay
vaind May 12, 2024
81f4689
ktlint format
vaind May 12, 2024
0f6764b
detekt suppression
vaind May 12, 2024
d35f630
ktlint format
vaind May 12, 2024
fee9580
improve scheduler stop behavior
vaind May 12, 2024
8be8d20
wip: error replay mapping
vaind May 13, 2024
c7b166d
Merge branch 'main' into feat/android-replay
vaind May 13, 2024
f3057cd
suppress detekt TooGenericExceptionThrown
vaind May 13, 2024
943acea
Update flutter/lib/src/replay/recorder.dart
vaind May 14, 2024
0d82f13
Update flutter/lib/src/native/java/sentry_native_java.dart
vaind May 14, 2024
49d4239
improve comments
vaind May 14, 2024
8ef5d15
Merge branch 'main' into feat/android-replay
vaind May 20, 2024
a93da0b
Merge branch 'main' into feat/android-replay
vaind May 28, 2024
114ed86
feat: associate dart errors with replays (#2070)
vaind Jun 13, 2024
5014d1c
Merge branch 'main' into feat/android-replay
vaind Jun 25, 2024
5baab7c
Merge branch 'main' into feat/android-replay
vaind Jun 27, 2024
c26a8a2
chote: remove path dependency
vaind Jun 27, 2024
e4c0654
Merge branch 'main' into feat/android-replay
vaind Jul 10, 2024
8919fff
Merge branch 'main' into feat/android-replay
vaind Jul 12, 2024
3f12988
fix tests
vaind Jul 12, 2024
1706c68
Merge branch 'main' into feat/android-replay
vaind Jul 12, 2024
5dc8bd6
feat: replay breadcrumbs (android) (#2163)
vaind Jul 17, 2024
26d7b9c
Merge branch 'main' into feat/android-replay
vaind Jul 17, 2024
8cd2d63
test: native replay integration binding (#2189)
vaind Jul 24, 2024
4883e14
Merge branch 'main' into feat/android-replay
vaind Jul 24, 2024
8bcde3d
Merge branch 'main' into feat/android-replay
vaind Jul 24, 2024
f1157fc
chore: update changelog
vaind Jul 24, 2024
93293f7
fix publishing
vaind Jul 24, 2024
87971db
release: 8.6.0-alpha.2
getsentry-bot Jul 24, 2024
3bf3cca
Merge branch 'release/8.6.0-alpha.2' into feat/android-replay
Jul 24, 2024
14340f7
Merge branch 'feat/replay' into feat/android-replay
vaind Aug 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

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

## Unreleased

### Improvements
Expand Down Expand Up @@ -50,11 +71,11 @@ SentryFlutter.init((options) =>
- This allows viewing the correct dart formatted raw stacktrace in the Sentry UI
- Support `ignoredExceptionsForType` ([#2150](https://github.com/getsentry/sentry-dart/pull/2150))
- Filter out exception types by calling `SentryOptions.addExceptionFilterForType(Type exceptionType)`

### 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 @@ -50,6 +50,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 @@ -65,6 +69,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 @@ -95,11 +104,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 @@ -17,6 +17,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 @@ -45,6 +48,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 @@ -61,6 +67,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 @@ -76,6 +83,7 @@ class SentryTraceContext {
parentSpanId: parentSpanId,
sampled: sampled,
origin: origin,
replayId: replayId,
);

SentryTraceContext({
Expand All @@ -87,16 +95,17 @@ class SentryTraceContext {
this.description,
this.status,
this.origin,
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 @@ -157,15 +157,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
13 changes: 13 additions & 0 deletions dart/lib/src/sentry_trace_context_header.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:meta/meta.dart';

import 'protocol/sentry_id.dart';
import 'sentry_baggage.dart';
import 'sentry_options.dart';
Expand All @@ -13,6 +15,7 @@ class SentryTraceContextHeader {
this.transaction,
this.sampleRate,
this.sampled,
this.replayId,
});

final SentryId traceId;
Expand All @@ -27,6 +30,9 @@ class SentryTraceContextHeader {
final String? sampleRate;
final String? sampled;

@internal
SentryId? replayId;

/// Deserializes a [SentryTraceContextHeader] from JSON [Map].
factory SentryTraceContextHeader.fromJson(Map<String, dynamic> json) {
return SentryTraceContextHeader(
Expand All @@ -39,6 +45,8 @@ class SentryTraceContextHeader {
transaction: json['transaction'],
sampleRate: json['sample_rate'],
sampled: json['sampled'],
replayId:
json['replay_id'] == null ? null : SentryId.fromId(json['replay_id']),
);
}

Expand All @@ -55,6 +63,7 @@ class SentryTraceContextHeader {
if (transaction != null) 'transaction': transaction,
if (sampleRate != null) 'sample_rate': sampleRate,
if (sampled != null) 'sampled': sampled,
if (replayId != null) 'replay_id': replayId.toString(),
};
}

Expand Down Expand Up @@ -88,6 +97,9 @@ class SentryTraceContextHeader {
if (sampled != null) {
baggage.setSampled(sampled!);
}
if (replayId != null) {
baggage.setReplayId(replayId.toString());
}
return baggage;
}

Expand All @@ -97,6 +109,7 @@ class SentryTraceContextHeader {
baggage.get('sentry-public_key').toString(),
release: baggage.get('sentry-release'),
environment: baggage.get('sentry-environment'),
replayId: baggage.getReplayId(),
);
}
}
2 changes: 1 addition & 1 deletion dart/lib/src/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
library version;

/// The SDK version reported to Sentry.io in the submitted events.
const String sdkVersion = '8.5.0';
const String sdkVersion = '8.6.0-alpha.2';

String sdkName(bool isWeb) => isWeb ? _browserSdkName : _ioSdkName;

Expand Down
2 changes: 1 addition & 1 deletion dart/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: sentry
version: 8.5.0
version: 8.6.0-alpha.2
description: >
A crash reporting library for Dart that sends crash reports to Sentry.io.
This library supports Dart VM and Web. For Flutter consider sentry_flutter instead.
Expand Down
33 changes: 30 additions & 3 deletions dart/test/protocol/breadcrumb_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void main() {
level: SentryLevel.fatal,
reason: 'OK',
statusCode: 200,
requestDuration: Duration.zero,
requestDuration: Duration(milliseconds: 55),
timestamp: DateTime.now(),
requestBodySize: 2,
responseBodySize: 3,
Expand All @@ -103,17 +103,43 @@ void main() {
'method': 'GET',
'status_code': 200,
'reason': 'OK',
'duration': '0:00:00.000000',
'duration': '0:00:00.055000',
'request_body_size': 2,
'response_body_size': 3,
'http.query': 'foo=bar',
'http.fragment': 'baz'
'http.fragment': 'baz',
'start_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - 55,
'end_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch
},
'level': 'fatal',
'type': 'http',
});
});

test('Breadcrumb http', () {
final breadcrumb = Breadcrumb.http(
url: Uri.parse('https://example.org'),
method: 'GET',
requestDuration: Duration(milliseconds: 10),
);
final json = breadcrumb.toJson();

expect(json, {
'timestamp':
formatDateAsIso8601WithMillisPrecision(breadcrumb.timestamp),
'category': 'http',
'data': {
'url': 'https://example.org',
'method': 'GET',
'duration': '0:00:00.010000',
'start_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch - 10,
'end_timestamp': breadcrumb.timestamp.millisecondsSinceEpoch
},
'level': 'info',
'type': 'http',
});
});

test('Minimal Breadcrumb http', () {
final breadcrumb = Breadcrumb.http(
url: Uri.parse('https://example.org'),
Expand Down Expand Up @@ -192,6 +218,7 @@ void main() {
'foo': 'bar',
'view.id': 'foo',
'view.class': 'bar',
'path': 'bar(foo)',
},
});
});
Expand Down
16 changes: 14 additions & 2 deletions dart/test/protocol/sentry_baggage_header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,23 @@ void main() {
baggage.setTransaction('transaction');
baggage.setSampleRate('1.0');
baggage.setSampled('false');
final replayId = SentryId.newId().toString();
baggage.setReplayId(replayId);

final baggageHeader = SentryBaggageHeader.fromBaggage(baggage);

expect(baggageHeader.value,
'sentry-trace_id=$id,sentry-public_key=publicKey,sentry-release=release,sentry-environment=environment,sentry-user_id=userId,sentry-user_segment=userSegment,sentry-transaction=transaction,sentry-sample_rate=1.0,sentry-sampled=false');
expect(
baggageHeader.value,
'sentry-trace_id=$id,'
'sentry-public_key=publicKey,'
'sentry-release=release,'
'sentry-environment=environment,'
'sentry-user_id=userId,'
'sentry-user_segment=userSegment,'
'sentry-transaction=transaction,'
'sentry-sample_rate=1.0,'
'sentry-sampled=false,'
'sentry-replay_id=$replayId');
});
});
}
Loading
Loading