Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
implement propagationContext
  • Loading branch information
buenaflor committed Aug 31, 2023
commit 79053cd030b8a8e1b4a01e0474377dbc076986a2
17 changes: 17 additions & 0 deletions dart/lib/src/propagation_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:meta/meta.dart';

import '../sentry.dart';

@internal
class PropagationContext {
late SentryId traceId;
late SpanId spanId;
SpanId? parentSpanId;
bool? sampled;
SentryBaggage? baggage;

PropagationContext() {
traceId = SentryId.newId();
spanId = SpanId.newId();
}
}
14 changes: 14 additions & 0 deletions dart/lib/src/protocol/sentry_trace_context.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:meta/meta.dart';

import '../../sentry.dart';
import '../propagation_context.dart';
import '../protocol.dart';

@immutable
Expand Down Expand Up @@ -87,4 +89,16 @@ class SentryTraceContext {
this.origin,
}) : traceId = traceId ?? SentryId.newId(),
spanId = spanId ?? SpanId.newId();

@internal
factory SentryTraceContext.fromPropagationContext(
PropagationContext propagationContext) {
return SentryTraceContext(
traceId: propagationContext.traceId,
spanId: propagationContext.spanId,
parentSpanId: propagationContext.parentSpanId,
sampled: propagationContext.sampled,
operation: 'default',
);
}
}
20 changes: 16 additions & 4 deletions dart/lib/src/scope.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import 'dart:async';
import 'dart:collection';

import 'package:meta/meta.dart';

import '../sentry.dart';
import 'event_processor.dart';
import 'hint.dart';
import 'propagation_context.dart';
import 'protocol.dart';
import 'scope_observer.dart';
import 'sentry_attachment/sentry_attachment.dart';
Expand Down Expand Up @@ -39,6 +43,9 @@ class Scope {
/// Returns active transaction or null if there is no active transaction.
ISentrySpan? span;

@internal
PropagationContext propagationContext = PropagationContext();

SentryUser? _user;

/// Get the current user.
Expand Down Expand Up @@ -311,10 +318,15 @@ class Scope {
});

final newSpan = span;
if (event.contexts.trace == null && newSpan != null) {
event.contexts.trace = newSpan.context.toTraceContext(
sampled: newSpan.samplingDecision?.sampled,
);
if (event.contexts.trace == null) {
if (newSpan != null) {
event.contexts.trace = newSpan.context.toTraceContext(
sampled: newSpan.samplingDecision?.sampled,
);
} else {
event.contexts.trace =
SentryTraceContext.fromPropagationContext(propagationContext);
}
}

SentryEvent? processedEvent = event;
Expand Down
24 changes: 24 additions & 0 deletions dart/lib/src/sentry_baggage.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:meta/meta.dart';

import '../sentry.dart';
import 'protocol/sentry_level.dart';
import 'sentry_options.dart';

Expand Down Expand Up @@ -87,6 +90,27 @@ class SentryBaggage {
return SentryBaggage(keyValues, logger: logger);
}

@internal
setValuesFromScope(Scope scope, SentryOptions options) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Java we are setting transaction and sampleRate to null. Is this also needed here?

final propagationContext = scope.propagationContext;
setTraceId(propagationContext.traceId.toString());
if (options.dsn != null) {
setPublicKey(Dsn.parse(options.dsn!).publicKey);
}
if (options.release != null) {
setRelease(options.release!);
}
if (options.environment != null) {
setEnvironment(options.environment!);
}
if (scope.user?.id != null) {
setUserId(scope.user!.id!);
}
if (scope.user?.segment != null) {
setUserSegment(scope.user!.segment!);
}
}

static Map<String, String> _extractKeyValuesFromBaggageString(
String headerValue, {
SentryLogger? logger,
Expand Down
16 changes: 15 additions & 1 deletion dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math';
import 'package:meta/meta.dart';
import '../sentry.dart';
import 'sentry_attachment/sentry_attachment.dart';

import 'event_processor.dart';
Expand Down Expand Up @@ -122,11 +123,24 @@ class SentryClient {
attachments.add(viewHierarchy);
}

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) {
traceContext = SentryTraceContextHeader.fromBaggage(
scope.propagationContext.baggage!);
}
}

final envelope = SentryEnvelope.fromEvent(
preparedEvent,
_options.sdk,
dsn: _options.dsn,
traceContext: scope?.span?.traceContext(),
traceContext: traceContext,
attachments: attachments.isNotEmpty ? attachments : null,
);

Expand Down
9 changes: 9 additions & 0 deletions dart/lib/src/sentry_trace_context_header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,13 @@ class SentryTraceContextHeader {

return baggage;
}

factory SentryTraceContextHeader.fromBaggage(SentryBaggage baggage) {
return SentryTraceContextHeader(
SentryId.fromId(baggage.get('sentry-trace_id').toString()),
baggage.get('sentry-public_key').toString(),
release: baggage.get('sentry-release'),
environment: baggage.get('sentry-environment'),
);
}
}
132 changes: 132 additions & 0 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,138 @@ void main() {
fixture = Fixture();
});

test(
'when scope does not have an active transaction, trace state is set on the envelope from scope',
() async {
final client = fixture.getSut();
final scope = Scope(fixture.options);
await client.captureEvent(SentryEvent(), scope: scope);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedTraceContext = capturedEnvelope.header.traceContext;
final capturedTraceId = capturedTraceContext?.traceId;
final propagationContextTraceId = scope.propagationContext.traceId;

expect(capturedTraceContext, isNotNull);
expect(capturedTraceId, propagationContextTraceId);
});

test('attaches trace context from span if none present yet', () async {
final client = fixture.getSut();
final spanContext = SentrySpanContext(
traceId: SentryId.newId(),
spanId: SpanId.newId(),
operation: 'op.load',
);
final scope = Scope(fixture.options);
scope.span = SentrySpan(fixture.tracer, spanContext, MockHub());

final sentryEvent = SentryEvent();
await client.captureEvent(sentryEvent, scope: scope);

expect(fixture.transport.envelopes.length, 1);
expect(spanContext.spanId, sentryEvent.contexts.trace!.spanId);
expect(spanContext.traceId, sentryEvent.contexts.trace!.traceId);
});

test(
'attaches trace context from scope if none present yet and no span on scope',
() async {
final client = fixture.getSut();

final scope = Scope(fixture.options);
final scopePropagationContext = scope.propagationContext;

final sentryEvent = SentryEvent();
await client.captureEvent(sentryEvent, scope: scope);

expect(fixture.transport.envelopes.length, 1);
expect(
scopePropagationContext.traceId, sentryEvent.contexts.trace!.traceId);
expect(
scopePropagationContext.spanId, sentryEvent.contexts.trace!.spanId);
});

test('keeps existing trace context if already present', () async {
final client = fixture.getSut();

final spanContext = SentrySpanContext(
traceId: SentryId.newId(),
spanId: SpanId.newId(),
operation: 'op.load',
);
final scope = Scope(fixture.options);
scope.span = SentrySpan(fixture.tracer, spanContext, MockHub());

final propagationContext = scope.propagationContext;
final preExistingSpanContext = SentryTraceContext(
traceId: SentryId.newId(),
spanId: SpanId.newId(),
operation: 'op.load');

final sentryEvent = SentryEvent();
sentryEvent.contexts.trace = preExistingSpanContext;
await client.captureEvent(sentryEvent, scope: scope);

expect(fixture.transport.envelopes.length, 1);
expect(
preExistingSpanContext.traceId, sentryEvent.contexts.trace!.traceId);
expect(preExistingSpanContext.spanId, sentryEvent.contexts.trace!.spanId);
expect(spanContext.traceId, isNot(sentryEvent.contexts.trace!.traceId));
expect(spanContext.spanId, isNot(sentryEvent.contexts.trace!.spanId));
expect(propagationContext.traceId,
isNot(sentryEvent.contexts.trace!.traceId));
expect(
propagationContext.spanId, isNot(sentryEvent.contexts.trace!.spanId));
});

test(
'uses propagation context on scope for trace header if no transaction is on scope',
() async {
final client = fixture.getSut();

final scope = Scope(fixture.options);
final scopePropagationContext = scope.propagationContext;

final sentryEvent = SentryEvent();
await client.captureEvent(sentryEvent, scope: scope);

final capturedEnvelope = fixture.transport.envelopes.first;
final capturedTraceContext = capturedEnvelope.header.traceContext;

expect(fixture.transport.envelopes.length, 1);
expect(scope.span, isNull);
expect(capturedTraceContext, isNotNull);
expect(scopePropagationContext.traceId, capturedTraceContext!.traceId);
});

test(
'uses trace context on transaction for trace header if a transaction is on scope',
() async {
print("starting");
final client = fixture.getSut();

final spanContext = SentrySpanContext(
traceId: SentryId.newId(),
spanId: SpanId.newId(),
operation: 'op.load',
);
final scope = Scope(fixture.options);
scope.span = SentrySpan(fixture.tracer, spanContext, MockHub());

final sentryEvent = SentryEvent();
await client.captureEvent(sentryEvent, scope: scope);

final capturedEnvelope = fixture.transport.envelopes.first;
final capturedTraceContext = capturedEnvelope.header.traceContext;

expect(fixture.transport.envelopes.length, 1);
expect(scope.span, isNotNull);
expect(capturedTraceContext, isNotNull);
expect(
scope.span!.traceContext()!.traceId, capturedTraceContext!.traceId);
});

test('should contain a transaction in the envelope', () async {
try {
throw StateError('Error');
Expand Down