Skip to content

Commit

Permalink
feat: associate dart errors with replays
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind committed May 26, 2024
1 parent 8ef5d15 commit 5a90db4
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 17 deletions.
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());
}
}
14 changes: 13 additions & 1 deletion dart/lib/src/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ class Scope {
/// they must be JSON-serializable.
Map<String, dynamic> get extra => Map.unmodifiable(_extra);

SentryId? _replayId;

/// Get the active replay recording.
@internal
SentryId? get replayId => _replayId;

/// Set the active replay recording id.
@internal
set replayId(SentryId? value) => _replayId = value;

final Contexts _contexts = Contexts();

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

_clearBreadcrumbsSync();
_setUserSync(null);
Expand Down Expand Up @@ -425,7 +436,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 @@ -109,6 +109,9 @@ class SentryBaggage {
if (scope.user?.segment != null) {
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 @@ -201,5 +204,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 @@ -142,15 +142,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
12 changes: 12 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 @@ -25,6 +28,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 @@ -37,6 +43,7 @@ class SentryTraceContextHeader {
transaction: json['transaction'],
sampleRate: json['sample_rate'],
sampled: json['sampled'],
replayId: json['replay_id'],
);
}

Expand All @@ -52,6 +59,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 @@ -83,6 +91,9 @@ class SentryTraceContextHeader {
if (sampled != null) {
baggage.setSampled(sampled!);
}
if (replayId != null) {
baggage.setReplayId(replayId.toString());
}
return baggage;
}

Expand All @@ -92,6 +103,7 @@ class SentryTraceContextHeader {
baggage.get('sentry-public_key').toString(),
release: baggage.get('sentry-release'),
environment: baggage.get('sentry-environment'),
replayId: baggage.getReplayId(),
);
}
}
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 @@ -21,11 +21,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');
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal class SentryFlutterReplayRecorder(
"width" to config.recordingWidth,
"height" to config.recordingHeight,
"frameRate" to config.frameRate,
"replayId" to integration.getReplayId().toString()
),
)
} catch (ignored: Exception) {
Expand Down
1 change: 1 addition & 0 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Future<void> setupSentry(
options.navigatorKey = navigatorKey;

options.experimental.replay.sessionSampleRate = 1.0;
options.experimental.replay.errorSampleRate = 1.0;

_isIntegrationTest = isIntegrationTest;
if (_isIntegrationTest) {
Expand Down
8 changes: 3 additions & 5 deletions flutter/lib/src/event_processor/replay_event_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ class ReplayEventProcessor implements EventProcessor {
Future<SentryEvent?> apply(SentryEvent event, Hint hint) async {
if (event.eventId != SentryId.empty() &&
event.exceptions?.isNotEmpty == true) {
final isCrash = event.exceptions!
.any((element) => element.mechanism?.handled == false);
// ignore: unused_local_variable
final replayId =
await _binding.sendReplayForEvent(event.eventId, isCrash);
final isCrash =
event.exceptions!.any((e) => e.mechanism?.handled == false);
await _binding.sendReplayForEvent(event.eventId, isCrash);
}
return event;
}
Expand Down
15 changes: 15 additions & 0 deletions flutter/lib/src/native/java/sentry_native_java.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class SentryNativeJava extends SentryNativeChannel {
channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'ReplayRecorder.start':
final replayId =
SentryId.fromId(call.arguments['replayId'] as String);

_startRecorder(
call.arguments['directory'] as String,
ScreenshotRecorderConfig(
Expand All @@ -41,10 +44,22 @@ class SentryNativeJava extends SentryNativeChannel {
frameRate: call.arguments['frameRate'] as int,
),
);

Sentry.configureScope((s) {
// ignore: invalid_use_of_internal_member
s.replayId = replayId;
});

break;
case 'ReplayRecorder.stop':
await _replayRecorder?.stop();
_replayRecorder = null;

Sentry.configureScope((s) {
// ignore: invalid_use_of_internal_member
s.replayId = null;
});

break;
case 'ReplayRecorder.pause':
await _replayRecorder?.stop();
Expand Down

0 comments on commit 5a90db4

Please sign in to comment.