Skip to content

Commit

Permalink
feat: associate dart errors with replays (#2070)
Browse files Browse the repository at this point in the history
* feat: associate dart errors with replays

* ktlint

* cleanup

* tests
  • Loading branch information
vaind authored Jun 13, 2024
1 parent a93da0b commit 114ed86
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 36 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());
}
}
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 @@ -425,7 +433,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 @@ -143,15 +143,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 @@ -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,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 @@ -52,6 +60,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 +92,9 @@ class SentryTraceContextHeader {
if (sampled != null) {
baggage.setSampled(sampled!);
}
if (replayId != null) {
baggage.setReplayId(replayId.toString());
}
return baggage;
}

Expand All @@ -92,6 +104,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');
});
});
}
19 changes: 12 additions & 7 deletions dart/test/scope_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ void main() {
expect(sut.fingerprint, fingerprints);
});

test('sets replay ID', () {
final sut = fixture.getSut();

sut.replayId = SentryId.fromId('1');

expect(sut.replayId, SentryId.fromId('1'));
});

test('adds $Breadcrumb', () {
final sut = fixture.getSut();

Expand Down Expand Up @@ -305,6 +313,7 @@ void main() {
sut.level = SentryLevel.debug;
sut.transaction = 'test';
sut.span = null;
sut.replayId = SentryId.newId();

final user = SentryUser(id: 'test');
sut.setUser(user);
Expand All @@ -320,21 +329,15 @@ void main() {
sut.clear();

expect(sut.breadcrumbs.length, 0);

expect(sut.level, null);

expect(sut.transaction, null);
expect(sut.span, null);

expect(sut.user, null);

expect(sut.fingerprint.length, 0);

expect(sut.tags.length, 0);

expect(sut.extra.length, 0);

expect(sut.eventProcessors.length, 0);
expect(sut.replayId, isNull);
});

test('clones', () async {
Expand All @@ -347,6 +350,7 @@ void main() {
sut.addAttachment(SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'));
sut.span = NoOpSentrySpan();
sut.level = SentryLevel.warning;
sut.replayId = SentryId.newId();
await sut.setUser(SentryUser(id: 'id'));
await sut.setTag('key', 'vakye');
await sut.setExtra('key', 'vakye');
Expand All @@ -367,6 +371,7 @@ void main() {
true,
);
expect(sut.span, clone.span);
expect(sut.replayId, clone.replayId);
});

test('clone does not additionally call observers', () async {
Expand Down
9 changes: 8 additions & 1 deletion dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,8 @@ void main() {
..fingerprint = fingerprint
..addBreadcrumb(crumb)
..setTag(scopeTagKey, scopeTagValue)
..setExtra(scopeExtraKey, scopeExtraValue);
..setExtra(scopeExtraKey, scopeExtraValue)
..replayId = SentryId.fromId('1');

scope.setUser(user);
});
Expand All @@ -835,6 +836,8 @@ void main() {
scopeExtraKey: scopeExtraValue,
eventExtraKey: eventExtraValue,
});
expect(
capturedEnvelope.header.traceContext?.replayId, SentryId.fromId('1'));
});
});

Expand Down Expand Up @@ -1321,13 +1324,15 @@ void main() {
final client = fixture.getSut();

final scope = Scope(fixture.options);
scope.replayId = SentryId.newId();
scope.span =
SentrySpan(fixture.tracer, fixture.tracer.context, MockHub());

await client.captureEvent(fakeEvent, scope: scope);

final envelope = fixture.transport.envelopes.first;
expect(envelope.header.traceContext, isNotNull);
expect(envelope.header.traceContext?.replayId, scope.replayId);
});

test('captureEvent adds attachments from hint', () async {
Expand Down Expand Up @@ -1384,12 +1389,14 @@ void main() {
final context = SentryTraceContextHeader.fromJson(<String, dynamic>{
'trace_id': '${tr.eventId}',
'public_key': '123',
'replay_id': '456',
});

await client.captureTransaction(tr, traceContext: context);

final envelope = fixture.transport.envelopes.first;
expect(envelope.header.traceContext, isNotNull);
expect(envelope.header.traceContext?.replayId, SentryId.fromId('456'));
});

test('captureUserFeedback calls flush', () async {
Expand Down
19 changes: 16 additions & 3 deletions dart/test/sentry_trace_context_header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ void main() {
'user_segment': 'user_segment',
'transaction': 'transaction',
'sample_rate': '1.0',
'sampled': 'false'
'sampled': 'false',
'replay_id': '456',
};
final context = SentryTraceContextHeader.fromJson(mapJson);

Expand All @@ -28,6 +29,7 @@ void main() {
expect(context.transaction, 'transaction');
expect(context.sampleRate, '1.0');
expect(context.sampled, 'false');
expect(context.replayId, SentryId.fromId('456'));
});

test('toJson', () {
Expand All @@ -39,8 +41,19 @@ void main() {
test('to baggage', () {
final baggage = context.toBaggage();

expect(baggage.toHeaderString(),
'sentry-trace_id=${id.toString()},sentry-public_key=123,sentry-release=release,sentry-environment=environment,sentry-user_id=user_id,sentry-user_segment=user_segment,sentry-transaction=transaction,sentry-sample_rate=1.0,sentry-sampled=false');
expect(
baggage.toHeaderString(),
'sentry-trace_id=${id.toString()},'
'sentry-public_key=123,'
'sentry-release=release,'
'sentry-environment=environment,'
'sentry-user_id=user_id,'
'sentry-user_segment=user_segment,'
'sentry-transaction=transaction,'
'sentry-sample_rate=1.0,'
'sentry-sampled=false,'
'sentry-replay_id=456',
);
});
});
}
Loading

0 comments on commit 114ed86

Please sign in to comment.