diff --git a/Sources/Sentry/PrivateSentrySDKOnly.mm b/Sources/Sentry/PrivateSentrySDKOnly.mm index 7ef7a75655a..c51b8d507f1 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.mm +++ b/Sources/Sentry/PrivateSentrySDKOnly.mm @@ -9,6 +9,7 @@ #import "SentryInternalDefines.h" #import "SentryMeta.h" #import "SentryOptions+Private.h" +#import "SentryPropagationContext.h" #import "SentrySDK+Private.h" #import "SentrySerialization.h" #import "SentrySessionReplayIntegration+Private.h" @@ -199,6 +200,14 @@ + (NSDictionary *)getExtraContext return [SentryDependencyContainer.sharedInstance.extraContextProvider getExtraContext]; } ++ (void)setTrace:(SentryId *)traceId spanId:(SentrySpanId *)spanId +{ + [SentrySDK.currentHub configureScope:^(SentryScope *scope) { + scope.propagationContext = [[SentryPropagationContext alloc] initWithTraceId:traceId + spanId:spanId]; + }]; +} + #if SENTRY_TARGET_PROFILING_SUPPORTED + (uint64_t)startProfilerForTrace:(SentryId *)traceId; { diff --git a/Sources/Sentry/SentryPropagationContext.h b/Sources/Sentry/SentryPropagationContext.h index 54b6e5bd542..420e331dcba 100644 --- a/Sources/Sentry/SentryPropagationContext.h +++ b/Sources/Sentry/SentryPropagationContext.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) SentrySpanId *spanId; @property (nonatomic, readonly) SentryTraceHeader *traceHeader; +- (instancetype)initWithTraceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId; + - (NSDictionary *)traceContextForEvent; @end diff --git a/Sources/Sentry/SentryPropagationContext.m b/Sources/Sentry/SentryPropagationContext.m index ac56aa1fada..83d9c32a3b3 100644 --- a/Sources/Sentry/SentryPropagationContext.m +++ b/Sources/Sentry/SentryPropagationContext.m @@ -14,6 +14,15 @@ - (instancetype)init return self; } +- (instancetype)initWithTraceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId +{ + if (self = [super init]) { + _traceId = traceId; + _spanId = spanId; + } + return self; +} + - (SentryTraceHeader *)traceHeader { return [[SentryTraceHeader alloc] initWithTraceId:self.traceId diff --git a/Sources/Sentry/SentryScope.m b/Sources/Sentry/SentryScope.m index 7a017b224e4..c29eb4ef52f 100644 --- a/Sources/Sentry/SentryScope.m +++ b/Sources/Sentry/SentryScope.m @@ -154,6 +154,20 @@ - (void)setSpan:(nullable id)span } } +- (void)setPropagationContext:(SentryPropagationContext *)propagationContext +{ + @synchronized(_propagationContext) { + _propagationContext = propagationContext; + + if (self.observers.count > 0) { + NSDictionary *traceContext = [self.propagationContext traceContextForEvent]; + for (id observer in self.observers) { + [observer setTraceContext:traceContext]; + } + } + } +} + - (nullable id)span { @synchronized(_spanLock) { diff --git a/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h b/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h index 296ae587bdc..f2aafebcc8a 100644 --- a/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h +++ b/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h @@ -18,6 +18,7 @@ @class SentryUser; @class SentryEnvelope; @class SentryId; +@class SentrySpanId; @class SentrySessionReplayIntegration; @class UIView; @@ -103,6 +104,11 @@ typedef void (^SentryOnAppStartMeasurementAvailable)( */ + (NSDictionary *)getExtraContext; +/** + * Allows Hybrids SDKs to thread-safe set the current trace. + */ ++ (void)setTrace:(SentryId *)traceId spanId:(SentrySpanId *)spanId; + #if SENTRY_TARGET_PROFILING_SUPPORTED /** * Start a profiler session associated with the given @c SentryId. diff --git a/Sources/Sentry/include/SentryScope+Private.h b/Sources/Sentry/include/SentryScope+Private.h index 7f7d934e859..6eb99cbb7a2 100644 --- a/Sources/Sentry/include/SentryScope+Private.h +++ b/Sources/Sentry/include/SentryScope+Private.h @@ -20,7 +20,10 @@ NS_ASSUME_NONNULL_BEGIN */ @property (atomic, strong) SentryUser *_Nullable userObject; -@property (atomic, strong) SentryPropagationContext *propagationContext; +/** + * The propagation context has a setter, requiring it to be nonatomic + */ +@property (nonatomic, strong) SentryPropagationContext *propagationContext; @property (nonatomic, nullable, copy) NSString *currentScreen; diff --git a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift index b2538595b28..3e8fa72b099 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -477,4 +477,21 @@ class PrivateSentrySDKOnlyTests: XCTestCase { event.exceptions?.first?.mechanism?.handled = false return SentryEnvelope(event: event) } + + func testSetTrace() { + // -- Arrange -- + let traceId = SentryId() + let spanId = SentrySpanId() + + let scope = Scope() + let hub = TestHub(client: nil, andScope: scope) + SentrySDK.setCurrentHub(hub) + + // -- Act -- + PrivateSentrySDKOnly.setTrace(traceId, spanId: spanId) + + // -- Assert -- + XCTAssertEqual(scope.propagationContext?.traceId, traceId) + XCTAssertEqual(scope.propagationContext?.spanId, spanId) + } } diff --git a/Tests/SentryTests/SentryPropagationContextTests.swift b/Tests/SentryTests/SentryPropagationContextTests.swift new file mode 100644 index 00000000000..fbb6991504f --- /dev/null +++ b/Tests/SentryTests/SentryPropagationContextTests.swift @@ -0,0 +1,50 @@ +@testable import Sentry +import XCTest + +class SentryPropagationContextTests: XCTestCase { + + func testInitWithTraceIdSpanId() { + // -- Arrange -- + let traceId = SentryId() + let spanId = SpanId() + + // -- Act -- + let context = SentryPropagationContext(traceId: traceId, spanId: spanId) + + // -- Assert -- + XCTAssertEqual(traceId, context.traceId) + XCTAssertEqual(spanId, context.spanId) + } + + func testTraceContextForEvent() { + // -- Arrange -- + let traceId = SentryId() + let spanId = SpanId() + + let context = SentryPropagationContext(traceId: traceId, spanId: spanId) + + // -- Act -- + let traceContext = context.traceContextForEvent() + + // -- Assert -- + XCTAssertEqual(traceContext.count, 2) + XCTAssertEqual(traceContext["trace_id"], traceId.sentryIdString) + XCTAssertEqual(traceContext["span_id"], spanId.sentrySpanIdString) + } + + func testTraceHeader() { + // -- Arrange + let traceId = SentryId() + let spanId = SpanId() + + let context = SentryPropagationContext(traceId: traceId, spanId: spanId) + + // -- Act -- + let traceHeader = context.traceHeader + + // -- Assert -- + XCTAssertNotNil(traceHeader) + XCTAssertEqual(traceHeader.traceId, traceId) + XCTAssertEqual(traceHeader.spanId, spanId) + } +} diff --git a/Tests/SentryTests/SentryScopeSwiftTests.swift b/Tests/SentryTests/SentryScopeSwiftTests.swift index 960e7c14532..7565dfaa734 100644 --- a/Tests/SentryTests/SentryScopeSwiftTests.swift +++ b/Tests/SentryTests/SentryScopeSwiftTests.swift @@ -843,6 +843,26 @@ class SentryScopeSwiftTests: XCTestCase { XCTAssertEqual(observer2.context as? NSDictionary, expected) } + func testScopeObserver_setPropagationContext_UpdatesTraceContext() throws { + // -- Arrange -- + let sut = Scope() + let observer = fixture.observer + sut.add(observer) + + let traceId = SentryId(uuidString: "12345678123456781234567812345678") + let spanId = SpanId(value: "1234567812345678") + let propagationContext = SentryPropagationContext(trace: traceId, spanId: spanId) + + // -- Act -- + sut.propagationContext = propagationContext + + // -- Assert -- + let traceContext = try XCTUnwrap(observer.traceContext) + XCTAssertEqual(2, traceContext.count) + XCTAssertEqual(traceId.sentryIdString, traceContext["trace_id"] as? String) + XCTAssertEqual(spanId.sentrySpanIdString, traceContext["span_id"] as? String) + } + private class TestScopeObserver: NSObject, SentryScopeObserver { var tags: [String: String]? func setTags(_ tags: [String: String]?) {