diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc575667c9..418980e2b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ ### Features -- Rename "http.method" to "http.request.method" for network Spans #3268 +- Rename "http.method" to "http.request.method" for network Spans (#3268) +- Add Sampling Decision to Trace Envelope Header (#3286) ## 8.11.0 diff --git a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj index bfc4b199797..fba37fa6079 100644 --- a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj +++ b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ diff --git a/Sources/Sentry/Public/SentryDefines.h b/Sources/Sentry/Public/SentryDefines.h index ee2721020aa..42f26a95f3e 100644 --- a/Sources/Sentry/Public/SentryDefines.h +++ b/Sources/Sentry/Public/SentryDefines.h @@ -118,6 +118,9 @@ static DEPRECATED_MSG_ATTRIBUTE( static NSUInteger const defaultMaxBreadcrumbs = 100; +static NSString *_Nonnull const kSentryTrueString = @"true"; +static NSString *_Nonnull const kSentryFalseString = @"false"; + /** * Transaction name source. */ diff --git a/Sources/Sentry/SentryBaggage.m b/Sources/Sentry/SentryBaggage.m index 7047c6414a0..2978a97848c 100644 --- a/Sources/Sentry/SentryBaggage.m +++ b/Sources/Sentry/SentryBaggage.m @@ -17,6 +17,7 @@ - (instancetype)initWithTraceId:(SentryId *)traceId transaction:(nullable NSString *)transaction userSegment:(nullable NSString *)userSegment sampleRate:(nullable NSString *)sampleRate + sampled:(nullable NSString *)sampled { if (self = [super init]) { @@ -27,6 +28,7 @@ - (instancetype)initWithTraceId:(SentryId *)traceId _transaction = transaction; _userSegment = userSegment; _sampleRate = sampleRate; + _sampled = sampled; } return self; @@ -65,6 +67,10 @@ - (NSString *)toHTTPHeaderWithOriginalBaggage:(NSDictionary *_Nullable)originalB [information setValue:_sampleRate forKey:@"sentry-sample_rate"]; } + if (_sampled != nil) { + [information setValue:_sampled forKey:@"sentry-sampled"]; + } + return [SentrySerialization baggageEncodedDictionary:information]; } diff --git a/Sources/Sentry/SentryPropagationContext.m b/Sources/Sentry/SentryPropagationContext.m index 62405282fee..fd1c80530cd 100644 --- a/Sources/Sentry/SentryPropagationContext.m +++ b/Sources/Sentry/SentryPropagationContext.m @@ -38,7 +38,8 @@ - (SentryTraceContext *)traceContext environment:options.environment transaction:nil userSegment:scope.userObject.segment - sampleRate:nil]; + sampleRate:nil + sampled:nil]; } - (NSDictionary *)traceContextForEvent diff --git a/Sources/Sentry/SentryTraceContext.m b/Sources/Sentry/SentryTraceContext.m index c6bf9360858..dc67c7ab96b 100644 --- a/Sources/Sentry/SentryTraceContext.m +++ b/Sources/Sentry/SentryTraceContext.m @@ -1,9 +1,11 @@ #import "SentryTraceContext.h" #import "SentryBaggage.h" +#import "SentryDefines.h" #import "SentryDsn.h" #import "SentryId.h" #import "SentryLog.h" #import "SentryOptions+Private.h" +#import "SentrySampleDecision.h" #import "SentryScope+Private.h" #import "SentrySerialization.h" #import "SentryTracer.h" @@ -21,6 +23,7 @@ - (instancetype)initWithTraceId:(SentryId *)traceId transaction:(nullable NSString *)transaction userSegment:(nullable NSString *)userSegment sampleRate:(nullable NSString *)sampleRate + sampled:(nullable NSString *)sampled { if (self = [super init]) { _traceId = traceId; @@ -30,6 +33,7 @@ - (instancetype)initWithTraceId:(SentryId *)traceId _transaction = transaction; _userSegment = userSegment; _sampleRate = sampleRate; + _sampled = sampled; } return self; } @@ -63,13 +67,20 @@ - (nullable instancetype)initWithTracer:(SentryTracer *)tracer [NSString stringWithFormat:@"%@", [(SentryTransactionContext *)tracer sampleRate]]; } + NSString *sampled = nil; + if (tracer.sampled != kSentrySampleDecisionUndecided) { + sampled + = tracer.sampled == kSentrySampleDecisionYes ? kSentryTrueString : kSentryFalseString; + } + return [self initWithTraceId:tracer.traceId publicKey:options.parsedDsn.url.user releaseName:options.releaseName environment:options.environment transaction:tracer.transactionContext.name userSegment:userSegment - sampleRate:sampleRate]; + sampleRate:sampleRate + sampled:sampled]; } - (nullable instancetype)initWithDict:(NSDictionary *)dictionary @@ -94,7 +105,8 @@ - (nullable instancetype)initWithDict:(NSDictionary *)dictionary environment:dictionary[@"environment"] transaction:dictionary[@"transaction"] userSegment:userSegment - sampleRate:dictionary[@"sample_rate"]]; + sampleRate:dictionary[@"sample_rate"] + sampled:dictionary[@"sampled"]]; } - (SentryBaggage *)toBaggage @@ -105,7 +117,8 @@ - (SentryBaggage *)toBaggage environment:_environment transaction:_transaction userSegment:_userSegment - sampleRate:_sampleRate]; + sampleRate:_sampleRate + sampled:_sampled]; return result; } @@ -134,6 +147,10 @@ - (SentryBaggage *)toBaggage [result setValue:_sampleRate forKey:@"sample_rate"]; } + if (_sampled != nil) { + [result setValue:_sampleRate forKey:@"sampled"]; + } + return result; } diff --git a/Sources/Sentry/include/SentryBaggage.h b/Sources/Sentry/include/SentryBaggage.h index 508752927fa..dede549ae0e 100644 --- a/Sources/Sentry/include/SentryBaggage.h +++ b/Sources/Sentry/include/SentryBaggage.h @@ -49,13 +49,19 @@ static NSString *const SENTRY_BAGGAGE_HEADER = @"baggage"; */ @property (nullable, nonatomic, readonly) NSString *sampleRate; +/** + * Value indicating whether the trace was sampled. + */ +@property (nullable, nonatomic, strong) NSString *sampled; + - (instancetype)initWithTraceId:(SentryId *)traceId publicKey:(NSString *)publicKey releaseName:(nullable NSString *)releaseName environment:(nullable NSString *)environment transaction:(nullable NSString *)transaction userSegment:(nullable NSString *)userSegment - sampleRate:(nullable NSString *)sampleRate; + sampleRate:(nullable NSString *)sampleRate + sampled:(nullable NSString *)sampled; - (NSString *)toHTTPHeader; - (NSString *)toHTTPHeaderWithOriginalBaggage:(NSDictionary *_Nullable)originalBaggage; diff --git a/Sources/Sentry/include/SentryTraceContext.h b/Sources/Sentry/include/SentryTraceContext.h index 607471c0083..61270dc24f5 100644 --- a/Sources/Sentry/include/SentryTraceContext.h +++ b/Sources/Sentry/include/SentryTraceContext.h @@ -50,7 +50,12 @@ NS_ASSUME_NONNULL_BEGIN /** * Sample rate used for this trace. */ -@property (nullable, nonatomic, strong) NSString *sampleRate; +@property (nullable, nonatomic, readonly) NSString *sampleRate; + +/** + * Value indicating whether the trace was sampled. + */ +@property (nullable, nonatomic, readonly) NSString *sampled; /** * Initializes a SentryTraceContext with given properties. @@ -61,7 +66,8 @@ NS_ASSUME_NONNULL_BEGIN environment:(nullable NSString *)environment transaction:(nullable NSString *)transaction userSegment:(nullable NSString *)userSegment - sampleRate:(nullable NSString *)sampleRate; + sampleRate:(nullable NSString *)sampleRate + sampled:(nullable NSString *)sampled; /** * Initializes a SentryTraceContext with data from scope and options. diff --git a/Tests/SentryTests/Helper/SentrySerializationTests.swift b/Tests/SentryTests/Helper/SentrySerializationTests.swift index 0e72214c313..9fa8473145b 100644 --- a/Tests/SentryTests/Helper/SentrySerializationTests.swift +++ b/Tests/SentryTests/Helper/SentrySerializationTests.swift @@ -4,7 +4,7 @@ class SentrySerializationTests: XCTestCase { private class Fixture { static var invalidData = "hi".data(using: .utf8)! - static var traceContext = SentryTraceContext(trace: SentryId(), publicKey: "PUBLIC_KEY", releaseName: "RELEASE_NAME", environment: "TEST", transaction: "transaction", userSegment: "some segment", sampleRate: "0.25") + static var traceContext = SentryTraceContext(trace: SentryId(), publicKey: "PUBLIC_KEY", releaseName: "RELEASE_NAME", environment: "TEST", transaction: "transaction", userSegment: "some segment", sampleRate: "0.25", sampled: "true") } func testSerializationFailsWithInvalidJSONObject() { @@ -123,7 +123,7 @@ class SentrySerializationTests: XCTestCase { } func testSentryEnvelopeSerializer_TraceStateWithoutUser() { - let trace = SentryTraceContext(trace: SentryId(), publicKey: "PUBLIC_KEY", releaseName: "RELEASE_NAME", environment: "TEST", transaction: "transaction", userSegment: nil, sampleRate: nil) + let trace = SentryTraceContext(trace: SentryId(), publicKey: "PUBLIC_KEY", releaseName: "RELEASE_NAME", environment: "TEST", transaction: "transaction", userSegment: nil, sampleRate: nil, sampled: nil) let envelopeHeader = SentryEnvelopeHeader(id: nil, traceContext: trace) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) diff --git a/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift b/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift index dfdb137d610..5601cf8bc83 100644 --- a/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift +++ b/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift @@ -158,7 +158,7 @@ class SentryEnvelopeTests: XCTestCase { func testInitSentryEnvelopeHeader_SetIdAndTraceState() { let eventId = SentryId() - let traceContext = SentryTraceContext(trace: SentryId(), publicKey: "publicKey", releaseName: "releaseName", environment: "environment", transaction: "transaction", userSegment: nil, sampleRate: nil) + let traceContext = SentryTraceContext(trace: SentryId(), publicKey: "publicKey", releaseName: "releaseName", environment: "environment", transaction: "transaction", userSegment: nil, sampleRate: nil, sampled: nil) let envelopeHeader = SentryEnvelopeHeader(id: eventId, traceContext: traceContext) XCTAssertEqual(eventId, envelopeHeader.eventId) diff --git a/Tests/SentryTests/Transaction/SentryBaggageTests.swift b/Tests/SentryTests/Transaction/SentryBaggageTests.swift index 2d1ea0e91f4..a728d8e9c1b 100644 --- a/Tests/SentryTests/Transaction/SentryBaggageTests.swift +++ b/Tests/SentryTests/Transaction/SentryBaggageTests.swift @@ -4,19 +4,19 @@ import XCTest class SentryBaggageTests: XCTestCase { func test_baggageToHeader() { - let header = SentryBaggage(trace: SentryId.empty, publicKey: "publicKey", releaseName: "release name", environment: "teste", transaction: "transaction", userSegment: "test user", sampleRate: "0.49").toHTTPHeader() + let header = SentryBaggage(trace: SentryId.empty, publicKey: "publicKey", releaseName: "release name", environment: "teste", transaction: "transaction", userSegment: "test user", sampleRate: "0.49", sampled: "true").toHTTPHeader() - XCTAssertEqual(header, "sentry-environment=teste,sentry-public_key=publicKey,sentry-release=release%20name,sentry-sample_rate=0.49,sentry-trace_id=00000000000000000000000000000000,sentry-transaction=transaction,sentry-user_segment=test%20user") + XCTAssertEqual(header, "sentry-environment=teste,sentry-public_key=publicKey,sentry-release=release%20name,sentry-sample_rate=0.49,sentry-sampled=true,sentry-trace_id=00000000000000000000000000000000,sentry-transaction=transaction,sentry-user_segment=test%20user") } func test_baggageToHeader_AppendToOriginal() { - let header = SentryBaggage(trace: SentryId.empty, publicKey: "publicKey", releaseName: "release name", environment: "teste", transaction: "transaction", userSegment: "test user", sampleRate: "0.49").toHTTPHeader(withOriginalBaggage: ["a": "a", "sentry-trace_id": "to-be-overwritten"]) + let header = SentryBaggage(trace: SentryId.empty, publicKey: "publicKey", releaseName: "release name", environment: "teste", transaction: "transaction", userSegment: "test user", sampleRate: "0.49", sampled: "true").toHTTPHeader(withOriginalBaggage: ["a": "a", "sentry-trace_id": "to-be-overwritten"]) - XCTAssertEqual(header, "a=a,sentry-environment=teste,sentry-public_key=publicKey,sentry-release=release%20name,sentry-sample_rate=0.49,sentry-trace_id=00000000000000000000000000000000,sentry-transaction=transaction,sentry-user_segment=test%20user") + XCTAssertEqual(header, "a=a,sentry-environment=teste,sentry-public_key=publicKey,sentry-release=release%20name,sentry-sample_rate=0.49,sentry-sampled=true,sentry-trace_id=00000000000000000000000000000000,sentry-transaction=transaction,sentry-user_segment=test%20user") } func test_baggageToHeader_onlyTrace_ignoreNils() { - let header = SentryBaggage(trace: SentryId.empty, publicKey: "publicKey", releaseName: nil, environment: nil, transaction: nil, userSegment: nil, sampleRate: nil).toHTTPHeader() + let header = SentryBaggage(trace: SentryId.empty, publicKey: "publicKey", releaseName: nil, environment: nil, transaction: nil, userSegment: nil, sampleRate: nil, sampled: nil).toHTTPHeader() XCTAssertEqual(header, "sentry-public_key=publicKey,sentry-trace_id=00000000000000000000000000000000") } diff --git a/Tests/SentryTests/Transaction/SentryTraceStateTests.swift b/Tests/SentryTests/Transaction/SentryTraceStateTests.swift index 33584d416c6..453ae9a62cf 100644 --- a/Tests/SentryTests/Transaction/SentryTraceStateTests.swift +++ b/Tests/SentryTests/Transaction/SentryTraceStateTests.swift @@ -18,6 +18,7 @@ class SentryTraceContextTests: XCTestCase { let publicKey = "SentrySessionTrackerTests" let releaseName = "SentrySessionTrackerIntegrationTests" let environment = "debug" + let sampled = "true" init() { options = Options() @@ -26,8 +27,8 @@ class SentryTraceContextTests: XCTestCase { options.environment = environment options.sendDefaultPii = true - tracer = SentryTracer(transactionContext: TransactionContext(name: transactionName, operation: transactionOperation), hub: nil) - + tracer = SentryTracer(transactionContext: TransactionContext(name: transactionName, operation: transactionOperation, sampled: .yes), hub: nil) + scope = Scope() scope.setUser(User(userId: userId)) scope.userObject?.segment = userSegment @@ -57,7 +58,8 @@ class SentryTraceContextTests: XCTestCase { environment: fixture.environment, transaction: fixture.transactionName, userSegment: fixture.userSegment, - sampleRate: fixture.sampleRate) + sampleRate: fixture.sampleRate, + sampled: fixture.sampled) assertTraceState(traceContext: traceContext) } @@ -72,6 +74,13 @@ class SentryTraceContextTests: XCTestCase { let traceContext = SentryTraceContext(tracer: fixture.tracer, scope: fixture.scope, options: fixture.options) assertTraceState(traceContext: traceContext!) } + + func testInitWithTracerNotSampled() { + let tracer = fixture.tracer + tracer.sampled = .no + let traceContext = SentryTraceContext(tracer: tracer, scope: fixture.scope, options: fixture.options) + XCTAssertEqual(traceContext?.sampled, "false") + } func testInitNil() { fixture.scope.span = nil @@ -87,7 +96,8 @@ class SentryTraceContextTests: XCTestCase { environment: fixture.environment, transaction: fixture.transactionName, userSegment: fixture.userSegment, - sampleRate: fixture.sampleRate) + sampleRate: fixture.sampleRate, + sampled: fixture.sampled) let baggage = traceContext.toBaggage() @@ -97,6 +107,7 @@ class SentryTraceContextTests: XCTestCase { XCTAssertEqual(baggage.environment, fixture.environment) XCTAssertEqual(baggage.userSegment, fixture.userSegment) XCTAssertEqual(baggage.sampleRate, fixture.sampleRate) + XCTAssertEqual(baggage.sampled, fixture.sampled) } func assertTraceState(traceContext: SentryTraceContext) { @@ -106,6 +117,7 @@ class SentryTraceContextTests: XCTestCase { XCTAssertEqual(traceContext.environment, fixture.environment) XCTAssertEqual(traceContext.transaction, fixture.transactionName) XCTAssertEqual(traceContext.userSegment, fixture.userSegment) + XCTAssertEqual(traceContext.sampled, fixture.sampled) } }