From 5ccb9595d80e0d87f5f1a0bde369f66e938faeaf Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 18 Jan 2021 17:14:43 -0500 Subject: [PATCH 01/14] feat: performance monitoring --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b4ba776516..915f01fb4a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## 6.1.4 - fix: Sessions for Hybrid SDKs #913 +- feat: Performance Monitoring API ## 6.1.3 From 8ff411a77d44c6b246714ad7362d217049eb4671 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 19 Jan 2021 10:37:39 +0100 Subject: [PATCH 02/14] build: Setup CI for performance monitoring branch --- .github/workflows/buildandtest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml index 9fa3b81a10a..16233161959 100644 --- a/.github/workflows/buildandtest.yml +++ b/.github/workflows/buildandtest.yml @@ -4,6 +4,7 @@ on: branches: - master - release/** + - feat/performance-monitoring pull_request: jobs: From 427b16410a36fd3194ce7dcfa168f77baacdc2c5 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 28 Jan 2021 20:30:17 -0300 Subject: [PATCH 03/14] feat: Performance API: Transaction (#908) Co-authored-by: Philipp Hofmann Co-authored-by: Clang Robot Co-authored-by: Bruno Garcia --- CHANGELOG.md | 1 + .../iOS-ObjectiveC/Base.lproj/Main.storyboard | 17 ++-- .../iOS-ObjectiveC/ViewController.m | 8 ++ Sentry.xcodeproj/project.pbxproj | 48 ++++++++++ Sources/Sentry/Public/Sentry.h | 5 ++ Sources/Sentry/Public/SentryClient.h | 5 +- Sources/Sentry/Public/SentryEnvelope.h | 3 +- Sources/Sentry/Public/SentryEvent.h | 2 +- Sources/Sentry/Public/SentryHub.h | 30 ++++++- Sources/Sentry/Public/SentrySDK.h | 20 ++++- Sources/Sentry/Public/SentrySpan.h | 9 ++ Sources/Sentry/Public/SentrySpanContext.h | 36 ++++++++ Sources/Sentry/Public/SentrySpanId.h | 38 ++++++++ Sources/Sentry/Public/SentryTransaction.h | 24 +++++ .../Sentry/Public/SentryTransactionContext.h | 23 +++++ Sources/Sentry/SentryClient.m | 6 ++ Sources/Sentry/SentryEnvelope.m | 14 ++- Sources/Sentry/SentryHub.m | 19 ++++ Sources/Sentry/SentrySDK.m | 10 +++ Sources/Sentry/SentrySpanContext.m | 34 +++++++ Sources/Sentry/SentrySpanId.m | 80 +++++++++++++++++ Sources/Sentry/SentryTransaction.m | 90 +++++++++++++++++++ Sources/Sentry/SentryTransactionContext.m | 32 +++++++ Tests/SentryTests/SentryClientTests.swift | 11 +++ .../SentryTests/SentryTests-Bridging-Header.h | 2 +- .../SentryTests/SentryTransactionTests.swift | 56 ++++++++++++ 26 files changed, 608 insertions(+), 15 deletions(-) create mode 100644 Sources/Sentry/Public/SentrySpan.h create mode 100644 Sources/Sentry/Public/SentrySpanContext.h create mode 100644 Sources/Sentry/Public/SentrySpanId.h create mode 100644 Sources/Sentry/Public/SentryTransaction.h create mode 100644 Sources/Sentry/Public/SentryTransactionContext.h create mode 100644 Sources/Sentry/SentrySpanContext.m create mode 100644 Sources/Sentry/SentrySpanId.m create mode 100644 Sources/Sentry/SentryTransaction.m create mode 100644 Sources/Sentry/SentryTransactionContext.m create mode 100644 Tests/SentryTests/SentryTransactionTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 915f01fb4a2..b7fefb9c075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - feat: Add sendDefaultPii to SentryOptions #923 +- feat: Performance Monitoring API ## 6.1.4 - fix: Sessions for Hybrid SDKs #913 diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard index 9b2e8adeb85..79dfb2b67a0 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -24,7 +24,7 @@ - + - + - + - + diff --git a/Samples/macOS-Swift/macOS-Swift/ViewController.swift b/Samples/macOS-Swift/macOS-Swift/ViewController.swift index ce8d43d0e84..d4cf134962b 100644 --- a/Samples/macOS-Swift/macOS-Swift/ViewController.swift +++ b/Samples/macOS-Swift/macOS-Swift/ViewController.swift @@ -49,6 +49,13 @@ class ViewController: NSViewController { NSApp.perform("_crashOnException:", with: exception) } + @IBAction func captureTransaction(_ sender: Any) { + let transaction = SentrySDK.startTransaction(name: "Some Transaction") + DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0.4...0.6), execute: { + transaction.finish() + }) + } + @IBAction func sentryCrash(_ sender: Any) { SentrySDK.crash() } diff --git a/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift b/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift index 905b0fbb15b..bcc3df8c184 100644 --- a/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift +++ b/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift @@ -41,6 +41,13 @@ struct ContentView: View { SentrySDK.capture(exception: exception, scope: scope) } + var captureTransactionAction: () -> Void = { + let transaction = SentrySDK.startTransaction(name: "Some Transaction") + DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0.4...0.6), execute: { + transaction.finish() + }) + } + var body: some View { VStack { Button(action: addBreadcrumbAction) { @@ -63,6 +70,10 @@ struct ContentView: View { Text("Capture NSException") } + Button(action: captureTransactionAction) { + Text("Capture Transaction") + } + Button(action: { SentrySDK.crash() }) { diff --git a/Samples/watchOS-Swift/watchOS-Swift WatchKit Extension/ContentView.swift b/Samples/watchOS-Swift/watchOS-Swift WatchKit Extension/ContentView.swift index bc18892303b..b9537a5c4f0 100644 --- a/Samples/watchOS-Swift/watchOS-Swift WatchKit Extension/ContentView.swift +++ b/Samples/watchOS-Swift/watchOS-Swift WatchKit Extension/ContentView.swift @@ -27,6 +27,13 @@ struct ContentView: View { SentrySDK.capture(exception: exception, scope: scope) } + var captureTransaction: () -> Void = { + let transaction = SentrySDK.startTransaction(name: "Some Transaction") + DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0.4...0.6), execute: { + transaction.finish() + }) + } + var body: some View { ScrollView { VStack { @@ -45,6 +52,10 @@ struct ContentView: View { Button(action: captureNSExceptionAction) { Text("Capture NSException") } + + Button(action: captureTransaction) { + Text("Capture Transaction") + } } } } From 380bc9ee799ef863910dd562d431c2ebc017f959 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 2 Feb 2021 11:18:08 +0100 Subject: [PATCH 05/14] docs: Improve code comments on transactions (#921) Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> --- Sources/Sentry/Public/SentryHub.h | 16 +++++++++------- Sources/Sentry/Public/SentrySDK.h | 13 +++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Sources/Sentry/Public/SentryHub.h b/Sources/Sentry/Public/SentryHub.h index 81c4dfe438c..31e5cdf1aca 100644 --- a/Sources/Sentry/Public/SentryHub.h +++ b/Sources/Sentry/Public/SentryHub.h @@ -45,7 +45,7 @@ SENTRY_NO_INIT /** * Captures a transaction and sends it to Sentry. * - * @param transaction The Transaction to send to Sentry. + * @param transaction The transaction to send to Sentry. * * @return The SentryId of the transaction or SentryId.empty if the transaction is not sent. */ @@ -53,19 +53,21 @@ SENTRY_NO_INIT NS_SWIFT_NAME(capture(transaction:)); /** - * Creates a Transaction bound to the current hub and returns the instance. + * Creates a transaction bound to the hub and returns the instance. * - * @param name the transaction name - * @return created transaction + * @param name The transaction name. + * + * @return The created transaction. */ - (SentryTransaction *)startTransactionWithName:(NSString *)name NS_SWIFT_NAME(startTransaction(name:)); /** - * Creates a Transaction bound to the current hub and returns the instance. + * Creates a transaction bound to the hub and returns the instance. + * + * @param transactionContext The transaction context. * - * @param transactionContext the transaction contexts - * @return created transaction + * @return The created transaction. */ - (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext NS_SWIFT_NAME(startTransaction(transactionContext:)); diff --git a/Sources/Sentry/Public/SentrySDK.h b/Sources/Sentry/Public/SentrySDK.h index 5be9c8238d3..1d47281efb4 100644 --- a/Sources/Sentry/Public/SentrySDK.h +++ b/Sources/Sentry/Public/SentrySDK.h @@ -82,19 +82,20 @@ SENTRY_NO_INIT withScopeBlock:(void (^)(SentryScope *scope))block NS_SWIFT_NAME(capture(event:block:)); /** - * Creates a Transaction bound to the current hub and returns the instance. + * Creates a transaction bound to the current hub and returns the instance. * - * @param name the transaction name - * @return created transaction + * @param name The transaction name. + * + * @return The created transaction. */ + (SentryTransaction *)startTransactionWithName:(NSString *)name NS_SWIFT_NAME(startTransaction(name:)); /** - * Creates a Transaction bound to the current hub and returns the instance. + * Creates a transaction bound to the current hub and returns the instance. * - * @param transactionContext the transaction contexts - * @return created transaction + * @param transactionContext The transaction context. + * @return The created transaction. */ + (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext NS_SWIFT_NAME(startTransaction(transactionContext:)); From 1195ec03692fd4585ba8358ba6934f2f251d689f Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 2 Feb 2021 20:51:17 -0300 Subject: [PATCH 06/14] feat: Add missing properties to SpanContext and Transaction (#919) Co-authored-by: Philipp Hofmann Co-authored-by: Bruno Garcia --- CHANGELOG.md | 4 +- .../iOS-ObjectiveC/ViewController.m | 1 + Sentry.xcodeproj/project.pbxproj | 8 ++ Sources/Sentry/Public/Sentry.h | 1 + Sources/Sentry/Public/SentrySpan.h | 1 + Sources/Sentry/Public/SentrySpanContext.h | 66 ++++++++++-- Sources/Sentry/Public/SentrySpanId.h | 2 + Sources/Sentry/Public/SentrySpanStatus.h | 102 ++++++++++++++++++ Sources/Sentry/Public/SentryTransaction.h | 63 ++++++++++- .../Sentry/Public/SentryTransactionContext.h | 35 +++++- Sources/Sentry/SentrySpanContext.m | 36 ++++++- Sources/Sentry/SentryTransaction.m | 98 ++++++++++++----- Sources/Sentry/SentryTransactionContext.m | 2 +- Tests/SentryTests/SentryClientTests.swift | 2 +- .../SentryTests/SentrySpanContextTests.swift | 66 ++++++++++++ .../SentryTests/SentryTransactionTests.swift | 73 +++++++++++-- 16 files changed, 507 insertions(+), 53 deletions(-) create mode 100644 Sources/Sentry/Public/SentrySpanStatus.h create mode 100644 Tests/SentryTests/SentrySpanContextTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index b7fefb9c075..695aeed9c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,13 @@ ## unreleased +- feat: Expand SentrySpanContext through SentryTransaction #919 +- feat: Performance Monitoring API - feat: Add sendDefaultPii to SentryOptions #923 -- feat: Performance Monitoring API ## 6.1.4 - fix: Sessions for Hybrid SDKs #913 -- feat: Performance Monitoring API ## 6.1.3 diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m index 02ff03d87fe..d982dfa9a1c 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m @@ -103,6 +103,7 @@ - (IBAction)captureException:(id)sender - (IBAction)captureTransaction:(id)sender { SentryTransaction *fakeTransaction = [SentrySDK startTransactionWithName:@"Some Transaction"]; + dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(arc4random_uniform(100) + 400 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ [fakeTransaction finish]; }); diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index f1747d4e7d0..a2f70ce10af 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -398,6 +398,8 @@ 861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */; }; 861265FA2404EC1500C4AFDE /* NSArray+SentrySanitize.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */; }; 8E80D0C325BB29690029B150 /* SentryTransactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */; }; + 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8EC4CF5025C3A0070093DEE9 /* SentrySpanContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */; }; 8ECC673C25C23996000E2BF6 /* SentrySpan.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673725C23995000E2BF6 /* SentrySpan.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673825C23995000E2BF6 /* SentrySpanId.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ECC673E25C23996000E2BF6 /* SentrySpanContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673925C23996000E2BF6 /* SentrySpanContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -854,6 +856,8 @@ 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSArray+SentrySanitize.h"; path = "include/NSArray+SentrySanitize.h"; sourceTree = ""; }; 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+SentrySanitize.m"; sourceTree = ""; }; 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionTests.swift; sourceTree = ""; }; + 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanStatus.h; path = Public/SentrySpanStatus.h; sourceTree = ""; }; + 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanContextTests.swift; sourceTree = ""; }; 8ECC673725C23995000E2BF6 /* SentrySpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpan.h; path = Public/SentrySpan.h; sourceTree = ""; }; 8ECC673825C23995000E2BF6 /* SentrySpanId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpanId.h; path = Public/SentrySpanId.h; sourceTree = ""; }; 8ECC673925C23996000E2BF6 /* SentrySpanContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpanContext.h; path = Public/SentrySpanContext.h; sourceTree = ""; }; @@ -1186,6 +1190,7 @@ 7B944FAF2469B46000A10721 /* TestClient.swift */, 7B0A54552523178700A71716 /* SentryScopeSwiftTests.swift */, 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */, + 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */, ); path = SentryTests; sourceTree = ""; @@ -1645,6 +1650,7 @@ 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */, 8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */, 8ECC673725C23995000E2BF6 /* SentrySpan.h */, + 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */, ); name = Transaction; sourceTree = ""; @@ -1656,6 +1662,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */, 8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */, 8ECC673C25C23996000E2BF6 /* SentrySpan.h in Headers */, 8ECC673E25C23996000E2BF6 /* SentrySpanContext.h in Headers */, @@ -2132,6 +2139,7 @@ 7BBD18B7245180FF00427C76 /* SentryDsnTests.m in Sources */, 63FE721A20DA66EC00CDBAE8 /* SentryCrashSysCtl_Tests.m in Sources */, 7B88F30424BC8E6500ADF90A /* SentrySerializationTests.swift in Sources */, + 8EC4CF5025C3A0070093DEE9 /* SentrySpanContextTests.swift in Sources */, 7B869EBE249B964D004F4FDB /* SentryThreadEquality.swift in Sources */, 7BC6EBF8255C05060059822A /* TestData.swift in Sources */, 7B88F30224BC5C6D00ADF90A /* SentrySdkInfoTests.swift in Sources */, diff --git a/Sources/Sentry/Public/Sentry.h b/Sources/Sentry/Public/Sentry.h index f19ece3f8c8..92378a81e37 100644 --- a/Sources/Sentry/Public/Sentry.h +++ b/Sources/Sentry/Public/Sentry.h @@ -33,6 +33,7 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[]; #import "SentrySpan.h" #import "SentrySpanContext.h" #import "SentrySpanId.h" +#import "SentrySpanStatus.h" #import "SentryStacktrace.h" #import "SentryThread.h" #import "SentryTransaction.h" diff --git a/Sources/Sentry/Public/SentrySpan.h b/Sources/Sentry/Public/SentrySpan.h index 76306a87022..825252285ef 100644 --- a/Sources/Sentry/Public/SentrySpan.h +++ b/Sources/Sentry/Public/SentrySpan.h @@ -2,6 +2,7 @@ NS_ASSUME_NONNULL_BEGIN +NS_SWIFT_NAME(Span) @protocol SentrySpan @end diff --git a/Sources/Sentry/Public/SentrySpanContext.h b/Sources/Sentry/Public/SentrySpanContext.h index e1e8fcd49e9..5c7f01640ac 100644 --- a/Sources/Sentry/Public/SentrySpanContext.h +++ b/Sources/Sentry/Public/SentrySpanContext.h @@ -1,36 +1,88 @@ #import "SentryDefines.h" #import "SentrySerializable.h" +#import "SentrySpanStatus.h" NS_ASSUME_NONNULL_BEGIN @class SentryId, SentrySpanId; NS_SWIFT_NAME(SpanContext) -@interface SentrySpanContext : NSObject +@interface SentrySpanContext : NSObject +/** + * Determines which trace the Span belongs to. + */ @property (nonatomic, strong) SentryId *traceId; +/** + * Span id. + */ @property (nonatomic, strong) SentrySpanId *spanId; -@property (nonatomic, strong) SentrySpanId *_Nullable parentSpanId; +/** + * Id of a parent span. + */ +@property (nullable, nonatomic, strong) SentrySpanId *parentSpanId; +/** + * If trace is sampled. + */ @property (nonatomic) BOOL sampled; -@property (nonatomic, copy) NSString *_Nullable operation; +/** + * Short code identifying the type of operation the span is measuring. + */ +@property (nullable, nonatomic, copy) NSString *operation; -@property (nonatomic, copy) NSString *_Nullable spanDescription; +/** + * Longer description of the span's operation, which uniquely identifies the span but is + * consistent across instances of the span. + */ +@property (nullable, nonatomic, copy) NSString *spanDescription; -@property (nonatomic, copy) NSString *_Nullable status; +/** + * Describes the status of the Transaction. + */ +@property (nonatomic) SentrySpanStatus status; +/** + * A map or list of tags for this event. Each tag must be less than 200 characters. + */ @property (nonatomic, readonly) NSMutableDictionary *tags; +/** + * Init a SentryContext and sets all fields by default. + * + * @return SentryContext + */ - (instancetype)init; + +/** + * Init a SentryContext and mark it as sampled or not, sets the other fields by default. + * + * @param sampled Determines whether the trace is sampled + * + * @return SentryContext + */ + - (instancetype)initWithSampled:(BOOL)sampled; -- (instancetype)initWithtraceId:(SentryId *)traceId + +/** + * Init a SentryContext with given traceId, spanId and parentId. + * + * @param traceId Determines which trace the Span belongs to. + * @param spanId The Span Id + * @param parentId Id of a parent span. + * + * @return SentryContext + */ +- (instancetype)initWithTraceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId - parentId:(SentrySpanId *_Nullable)parentId + parentId:(nullable SentrySpanId *)parentId andSampled:(BOOL)sampled; +@property (class, nonatomic, readonly, copy) NSString *type; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Public/SentrySpanId.h b/Sources/Sentry/Public/SentrySpanId.h index fde0f588702..9ecc514b7a7 100644 --- a/Sources/Sentry/Public/SentrySpanId.h +++ b/Sources/Sentry/Public/SentrySpanId.h @@ -5,6 +5,8 @@ NS_ASSUME_NONNULL_BEGIN /** * A 16 character Id. */ + +NS_SWIFT_NAME(SpanId) @interface SentrySpanId : NSObject /** diff --git a/Sources/Sentry/Public/SentrySpanStatus.h b/Sources/Sentry/Public/SentrySpanStatus.h new file mode 100644 index 00000000000..612e659ae65 --- /dev/null +++ b/Sources/Sentry/Public/SentrySpanStatus.h @@ -0,0 +1,102 @@ + +/** + * Describes the status of the Span/Transaction. + */ +typedef NS_ENUM(NSUInteger, SentrySpanStatus) { + /** + * An undefined status. + */ + kSentrySpanStatusUndefined, + + /** + * Not an error, returned on success. + */ + kSentrySpanStatusOk, + + /** + * The deadline expired before the operation could succeed. + */ + kSentrySpanStatusDeadlineExceeded, + + /** + * The requester doesn't have valid authentication credentials for the operation. + */ + kSentrySpanStatusUnauthenticated, + + /** + * The caller doesn't have permission to execute the specified operation. + */ + kSentrySpanStatusPermissionDenied, + + /** + * Content was not found or request was denied for an entire class of users. + */ + kSentrySpanStatusNotFound, + + /** + * The resource has been exhausted e.g. per-user quota exhausted, file system out of space. + */ + kSentrySpanStatusResourceExhausted, + + /** + * The client specified an invalid argument. + */ + kSentrySpanStatusInvalidArgument, + + /** + * 501 Not Implemented. + */ + kSentrySpanStatusUnimplemented, + + /** + * The operation is not implemented or is not supported/enabled for this operation. + */ + kSentrySpanStatusUnavailable, + + /** + * Some invariants expected by the underlying system have been broken. This code is reserved for + * serious errors. + */ + kSentrySpanStatusInternalError, + + /** + * An unknown error raised by APIs that don't return enough error information. + */ + kSentrySpanStatusUnknownError, + + /** + * The operation was cancelled, typically by the caller. + */ + kSentrySpanStatusCancelled, + + /** + * The entity attempted to be created already exists. + */ + kSentrySpanStatusAlreadyExists, + + /** + * The client shouldn't retry until the system state has been explicitly handled. + */ + kSentrySpanStatusFailedPrecondition, + + /** + * The operation was aborted. + */ + kSentrySpanStatusAborted, + + /** + * The operation was attempted past the valid range e.g. seeking past the end of a file. + */ + kSentrySpanStatusOutOfRange, + + /** + * Unrecoverable data loss or corruption. + */ + kSentrySpanStatusDataLoss, +}; + +static NSString *_Nonnull const SentrySpanStatusNames[] + = { @"undefined", @"ok", @"deadline_exceeded", @"unauthenticated", @"permission_denied", + @"not_found", @"resource_exhausted", @"invalid_argument", @"unimplemented", + @"unavailable", @"internal_error", @"unknown_error", @"cancelled", @"already_exists", + @"failed_precondition", @"aborted", @"out_of_range", @"data_loss" }; diff --git a/Sources/Sentry/Public/SentryTransaction.h b/Sources/Sentry/Public/SentryTransaction.h index c854c889d2a..d3592a91da9 100644 --- a/Sources/Sentry/Public/SentryTransaction.h +++ b/Sources/Sentry/Public/SentryTransaction.h @@ -4,19 +4,78 @@ NS_ASSUME_NONNULL_BEGIN @class SentrySpanContext, SentryTransactionContext, SentryHub; -NS_SWIFT_NAME(SentryTransaction) +NS_SWIFT_NAME(Transaction) @interface SentryTransaction : SentryEvent SENTRY_NO_INIT +/** + * Transaction span id + */ +@property (readonly) SentrySpanId *spanId; + +/** + * Transaction trace id + */ +@property (readonly) SentryId *traceId; + +/** + * If transaction is sampled + */ +@property (readonly) BOOL isSampled; + +/** + * Longer description of the span's operation, which uniquely identifies the span but is + * consistent across instances of the span. + */ +@property (nonatomic, copy) NSString *_Nullable spanDescription; + +/** + * Short code identifying the type of operation the transaction is measuring. + */ +@property (nonatomic, copy) NSString *_Nullable operation; + +/** + * Describes the status of the Transaction + */ +@property (nonatomic) enum SentrySpanStatus status; + +/** + * Init a SentryTransaction with given name and set other fields by default + * + * @param name Transaction name + * + * @return SentryTransaction + */ - (instancetype)initWithName:(NSString *)name; +/** + * Init a SentryTransaction with given transaction context and hub and set other fields by default + * + * @param transactionContext Transaction context + * @param hub A hub to bind this transaction + * + * @return SentryTransaction + */ + - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext andHub:(SentryHub *_Nullable)hub; +/** + * Init a SentryTransaction with given name, span context and hub and set other fields by default + * + * @param name Transaction name + * @param spanContext Span context + * @param hub A hub to bind this transaction + * + * @return SentryTransaction + */ - (instancetype)initWithName:(NSString *)name spanContext:(SentrySpanContext *)spanContext - andHub:(SentryHub *_Nullable)hub; + andHub:(nullable SentryHub *)hub; +/** + * Finishes the transaction by setting the end time and capturing the transaction with binded hub. + */ - (void)finish; @end diff --git a/Sources/Sentry/Public/SentryTransactionContext.h b/Sources/Sentry/Public/SentryTransactionContext.h index 270d6e15865..0bb6a7fb194 100644 --- a/Sources/Sentry/Public/SentryTransactionContext.h +++ b/Sources/Sentry/Public/SentryTransactionContext.h @@ -7,15 +7,48 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(TransactionContext) @interface SentryTransactionContext : SentrySpanContext +/** + * Transaction name + */ @property (nonatomic, readonly) NSString *name; + +/** + * Parent sampled + */ @property (nonatomic) bool parentSampled; +/** + * Init a SentryTransactionContext and set all fields by default + * + * @return SentryTransactionContext + */ - (instancetype)init; + +/** + * Init a SentryTransactionContext with given name and set other fields by default + * + * @param name Transaction name + * + * @return SentryTransactionContext + */ - (instancetype)initWithName:(NSString *)name; + +/** + * Init a SentryTransactionContext with given name, traceId, SpanId, parentSpanId and whether the + * parent is sampled. + * + * @param name Transaction name + * @param traceId Trace Id + * @param spanId Span Id + * @param parentSpanId Parent span id + * @param parentSampled Whether the parent is sampled + * + * @return SentryTransactionContext + */ - (instancetype)initWithName:(NSString *)name traceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId - parentSpanId:(SentrySpanId *)parentSpanId + parentSpanId:(nullable SentrySpanId *)parentSpanId andParentSampled:(BOOL)parentSampled; @end diff --git a/Sources/Sentry/SentrySpanContext.m b/Sources/Sentry/SentrySpanContext.m index ab2c8783512..79cfed0d6b8 100644 --- a/Sources/Sentry/SentrySpanContext.m +++ b/Sources/Sentry/SentrySpanContext.m @@ -11,13 +11,13 @@ - (instancetype)init - (instancetype)initWithSampled:(BOOL)sampled { - return [self initWithtraceId:[[SentryId alloc] init] + return [self initWithTraceId:[[SentryId alloc] init] spanId:[[SentrySpanId alloc] init] parentId:nil andSampled:sampled]; } -- (instancetype)initWithtraceId:(SentryId *)traceId +- (instancetype)initWithTraceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId parentId:(SentrySpanId *_Nullable)parentId andSampled:(BOOL)sampled @@ -27,8 +27,40 @@ - (instancetype)initWithtraceId:(SentryId *)traceId self.spanId = spanId; self.parentSpanId = parentId; self.sampled = sampled; + self.status = kSentrySpanStatusUndefined; + _tags = [[NSMutableDictionary alloc] init]; } return self; } ++ (NSString *)type +{ + static NSString *type; + if (type == nil) + type = @"trace"; + return type; +} + +- (NSDictionary *)serialize +{ + NSMutableDictionary *mutabledictionary = @{ + @"type" : SentrySpanContext.type, + @"span_id" : self.spanId.sentrySpanIdString, + @"trace_id" : self.traceId.sentryIdString, + @"sampled" : self.sampled ? @"true" : @"false", + @"tags" : _tags.copy + } + .mutableCopy; + + if (self.operation != nil) + [mutabledictionary setValue:self.operation forKey:@"op"]; + + if (self.parentSpanId != nil) + [mutabledictionary setValue:self.parentSpanId.sentrySpanIdString forKey:@"parent_span_id"]; + + if (self.status != kSentrySpanStatusUndefined) + [mutabledictionary setValue:SentrySpanStatusNames[self.status] forKey:@"status"]; + + return mutabledictionary; +} @end diff --git a/Sources/Sentry/SentryTransaction.m b/Sources/Sentry/SentryTransaction.m index e17cf89f4d8..556d23e1f8d 100644 --- a/Sources/Sentry/SentryTransaction.m +++ b/Sources/Sentry/SentryTransaction.m @@ -9,9 +9,13 @@ #import "SentryTransactionContext.h" @interface -SentryTransaction () { - SentrySpanContext *trace; -} + +SentryTransaction () + +/** + * This transaction span context + */ +@property (nonatomic, strong) SentrySpanContext *spanContext; /** * A hub this transaction is attached to. @@ -22,37 +26,13 @@ @implementation SentryTransaction -- (NSDictionary *)serialize -{ - if (nil == self.timestamp) { - self.timestamp = [SentryCurrentDate date]; - } - - NSMutableDictionary *serializedData = - [[NSMutableDictionary alloc] initWithDictionary:[super serialize]]; - serializedData[@"spans"] = @[]; - - NSMutableDictionary *mutableContext = [[NSMutableDictionary alloc] init]; - if (serializedData[@"contexts"] != nil) { - [mutableContext addEntriesFromDictionary:serializedData[@"contexts"]]; - } - mutableContext[@"trace"] = @{ - @"name" : self.transaction, - @"span_id" : trace.spanId.sentrySpanIdString, - @"tags" : @ {}, - @"trace_id" : [[SentryId alloc] init].sentryIdString - }; - [serializedData setValue:mutableContext forKey:@"contexts"]; - - return serializedData; -} - - (instancetype)init { self = [super init]; if (self) { self.eventId = [[SentryId alloc] init]; self.type = @"transaction"; + self.spanContext = [[SentrySpanContext alloc] init]; } return self; } @@ -76,7 +56,7 @@ - (instancetype)initWithName:(NSString *)name self.transaction = name; self.startTimestamp = [SentryCurrentDate date]; _hub = hub; - trace = spanContext; + self.spanContext = spanContext; } return self; } @@ -87,4 +67,64 @@ - (void)finish [self.hub captureTransaction:self]; } +- (SentrySpanId *)spanId +{ + return self.spanContext.spanId; +} + +- (SentryId *)traceId +{ + return self.spanContext.traceId; +} + +- (BOOL)isSampled +{ + return self.spanContext.sampled; +} + +- (NSString *)spanDescription +{ + return self.spanContext.spanDescription; +} + +- (void)setSpanDescription:(NSString *)spanDescription +{ + [_spanContext setSpanDescription:spanDescription]; +} + +- (SentrySpanStatus)status +{ + return self.spanContext.status; +} + +- (void)setStatus:(SentrySpanStatus)status +{ + [self.spanContext setStatus:status]; +} + +- (NSString *)operation +{ + return self.spanContext.operation; +} + +- (void)setOperation:(NSString *)operation +{ + [self.spanContext setOperation:operation]; +} + +- (NSDictionary *)serialize +{ + NSMutableDictionary *serializedData = + [[NSMutableDictionary alloc] initWithDictionary:[super serialize]]; + serializedData[@"spans"] = @[]; + + NSMutableDictionary *mutableContext = [[NSMutableDictionary alloc] init]; + if (serializedData[@"contexts"] != nil) { + [mutableContext addEntriesFromDictionary:serializedData[@"contexts"]]; + } + mutableContext[@"trace"] = [_spanContext serialize]; + [serializedData setValue:mutableContext forKey:@"contexts"]; + + return serializedData; +} @end diff --git a/Sources/Sentry/SentryTransactionContext.m b/Sources/Sentry/SentryTransactionContext.m index e432a88e471..e14cfb5e9b0 100644 --- a/Sources/Sentry/SentryTransactionContext.m +++ b/Sources/Sentry/SentryTransactionContext.m @@ -22,7 +22,7 @@ - (instancetype)initWithName:(NSString *)name parentSpanId:(SentrySpanId *)parentSpanId andParentSampled:(BOOL)parentSampled { - if ([self initWithtraceId:traceId spanId:spanId parentId:parentSpanId andSampled:false]) { + if ([super initWithTraceId:traceId spanId:spanId parentId:parentSpanId andSampled:false]) { _name = [NSString stringWithString:name]; self.parentSampled = parentSampled; } diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 8a5a433c08d..0a724c21ab2 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -159,7 +159,7 @@ class SentryClientTest: XCTestCase { } func testCaptureTransaction() { - let transaction = SentryTransaction(name: "Some Transaction") + let transaction = Transaction(name: "Some Transaction") let eventId = fixture.getSut().capture(transaction: transaction) eventId.assertIsNotEmpty() diff --git a/Tests/SentryTests/SentrySpanContextTests.swift b/Tests/SentryTests/SentrySpanContextTests.swift new file mode 100644 index 00000000000..34dfa1a2808 --- /dev/null +++ b/Tests/SentryTests/SentrySpanContextTests.swift @@ -0,0 +1,66 @@ +import XCTest + +class SentrySpanContextTests: XCTestCase { + + func testInit() { + let spanContext = SpanContext() + XCTAssertFalse(spanContext.sampled) + XCTAssertNil(spanContext.parentSpanId) + XCTAssertNil(spanContext.operation) + XCTAssertNil(spanContext.spanDescription) + XCTAssertEqual(spanContext.tags.count, 0) + XCTAssertEqual(spanContext.traceId.sentryIdString.count, 32) + XCTAssertEqual(spanContext.spanId.sentrySpanIdString.count, 16) + } + + func testInitWithSampled() { + let spanContext = SpanContext(sampled: true) + XCTAssertTrue(spanContext.sampled) + XCTAssertNil(spanContext.operation) + XCTAssertNil(spanContext.parentSpanId) + XCTAssertNil(spanContext.spanDescription) + XCTAssertEqual(spanContext.tags.count, 0) + XCTAssertEqual(spanContext.traceId.sentryIdString.count, 32) + XCTAssertEqual(spanContext.spanId.sentrySpanIdString.count, 16) + } + + func testInitWithTraceIdSpanIdParentIdSampled() { + let id = SentryId() + let spanId = SpanId() + let parentId = SpanId() + + let spanContext = SpanContext(trace: id, spanId: spanId, parentId: parentId, andSampled: true) + + XCTAssertEqual(id, spanContext.traceId) + XCTAssertEqual(spanId, spanContext.spanId) + XCTAssertEqual(parentId, spanContext.parentSpanId) + XCTAssertTrue(spanContext.sampled) + XCTAssertNil(spanContext.spanDescription) + XCTAssertEqual(spanContext.tags.count, 0) + } + + func testSerialization() { + let id = SentryId() + let spanId = SpanId() + let parentId = SpanId() + let operation = "Some Operation" + + let spanContext = SpanContext(trace: id, spanId: spanId, parentId: parentId, andSampled: true) + spanContext.operation = operation + spanContext.status = .ok + + let data = spanContext.serialize() + + XCTAssertEqual(data["span_id"] as? String, spanId.sentrySpanIdString) + XCTAssertEqual(data["trace_id"] as? String, id.sentryIdString) + XCTAssertEqual(data["type"] as? String, SpanContext.type) + XCTAssertEqual(data["op"] as? String, operation) + XCTAssertEqual(data["sampled"] as? String, "true") + XCTAssertEqual(data["parent_span_id"] as? String, parentId.sentrySpanIdString) + XCTAssertEqual(data["status"] as? String, "ok") + } + + func testSpanContextTraceTypeValue() { + XCTAssertEqual(SpanContext.type, "trace") + } +} diff --git a/Tests/SentryTests/SentryTransactionTests.swift b/Tests/SentryTests/SentryTransactionTests.swift index ba6eb271646..630a35fe6b4 100644 --- a/Tests/SentryTests/SentryTransactionTests.swift +++ b/Tests/SentryTests/SentryTransactionTests.swift @@ -2,28 +2,67 @@ import XCTest class SentryTransactionTest: XCTestCase { let someTransactionName = "Some Transaction" + let someOperation = "Some Operation" func testInitWithName() { - let transaction = SentryTransaction(name: someTransactionName) + let transaction = Transaction(name: someTransactionName) + XCTAssertNotNil(transaction.startTimestamp) XCTAssertNil(transaction.timestamp) XCTAssertEqual(transaction.transaction, someTransactionName) } func testInitWithTransactionContext() { - let context = TransactionContext(name: someTransactionName) - let transaction = SentryTransaction(transactionContext: context, andHub: nil) + let someOperation = "Some Operation" + let someSpanDescription = "Some Span Description" + + let context = TransactionContext(name: someTransactionName, trace: SentryId(), spanId: SpanId(), parentSpanId: SpanId(), andParentSampled: true) + context.operation = someOperation + context.status = .ok + context.sampled = true + context.spanDescription = someSpanDescription + + let transaction = Transaction(transactionContext: context, andHub: nil) XCTAssertNotNil(transaction.startTimestamp) XCTAssertNil(transaction.timestamp) XCTAssertEqual(transaction.transaction, someTransactionName) + XCTAssertEqual(transaction.traceId, context.traceId) + XCTAssertEqual(transaction.spanId, context.spanId) + XCTAssertEqual(transaction.operation, someOperation) + XCTAssertEqual(transaction.status, SentrySpanStatus.ok) + XCTAssertTrue(transaction.isSampled) + XCTAssertEqual(transaction.spanDescription, someSpanDescription) + } + + func testIndirectManipulationOfContext() { + let someOperation = "Some Operation" + let spanDescription = "Span Description" + + let context = TransactionContext(name: someTransactionName) + + let transaction = Transaction(transactionContext: context, andHub: nil) + transaction.spanDescription = spanDescription + transaction.operation = someOperation + transaction.status = .ok + + XCTAssertEqual(context.spanDescription, spanDescription) + XCTAssertEqual(context.operation, someOperation) + XCTAssertEqual(context.status, SentrySpanStatus.ok) } func testInitWithNameAndContext() { let context = SpanContext() - let transaction = SentryTransaction(name: someTransactionName, spanContext: context, andHub: nil) + context.operation = someOperation + context.status = .ok + + let transaction = Transaction(name: someTransactionName, spanContext: context, andHub: nil) XCTAssertNotNil(transaction.startTimestamp) XCTAssertNil(transaction.timestamp) XCTAssertEqual(transaction.transaction, someTransactionName) + XCTAssertEqual(transaction.traceId, context.traceId) + XCTAssertEqual(transaction.spanId, context.spanId) + XCTAssertEqual(transaction.operation, someOperation) + XCTAssertEqual(transaction.status, SentrySpanStatus.ok) } func testFinishCapturesTransaction() { @@ -32,7 +71,7 @@ class SentryTransactionTest: XCTestCase { let client = TestClient(options: Options(), andTransport: transport, andFileManager: fileManager) let hub = SentryHub(client: client, andScope: nil, andCrashAdapter: TestSentryCrashWrapper()) - let transaction = SentryTransaction(name: someTransactionName, spanContext: SpanContext(), andHub: hub) + let transaction = Transaction(name: someTransactionName, spanContext: SpanContext(), andHub: hub) transaction.finish() XCTAssertNotNil(transaction.startTimestamp) @@ -40,17 +79,35 @@ class SentryTransactionTest: XCTestCase { XCTAssertTrue(transaction.timestamp! >= transaction.startTimestamp!) XCTAssertTrue(client.captureEventWithScopeArguments.last!.event === transaction) } + + func testSerializationWithoutContext() { + let transaction = Transaction(name: someTransactionName) + + let serialization = transaction.serialize() + XCTAssertNotNil(serialization) + XCTAssertEqual(serialization["type"] as? String, "transaction") + XCTAssertNotNil(serialization["event_id"]) + XCTAssertNotNil(serialization["start_timestamp"]) + XCTAssertNotNil(serialization["timestamp"]) + XCTAssertEqual(serialization["transaction"] as? String, someTransactionName) + XCTAssertNotNil(serialization["contexts"]) + XCTAssertNotNil((serialization["contexts"] as! Dictionary)["trace"]) + XCTAssertNotNil(serialization["spans"]) + } - func testSerialization() { - let transaction = SentryTransaction(name: someTransactionName) - transaction.finish() + func testSerializationWithContext() { + let transaction = Transaction(name: someTransactionName) + transaction.context = [String: [String: Any]]() let serialization = transaction.serialize() + XCTAssertNotNil(serialization) XCTAssertEqual(serialization["type"] as? String, "transaction") XCTAssertNotNil(serialization["event_id"]) XCTAssertNotNil(serialization["start_timestamp"]) XCTAssertNotNil(serialization["timestamp"]) XCTAssertEqual(serialization["transaction"] as? String, someTransactionName) XCTAssertNotNil(serialization["contexts"]) + XCTAssertNotNil((serialization["contexts"] as! Dictionary)["trace"]) + XCTAssertNotNil(serialization["spans"]) } } From 16a008809ecb34bb6d6a5e63003b4400de9d33d4 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 11 Feb 2021 20:37:28 -0300 Subject: [PATCH 07/14] feat: Adding SentrySpan to performance monitoring. (#932) Co-authored-by: Bruno Garcia Co-authored-by: Philipp Hofmann Co-authored-by: Clang Robot --- CHANGELOG.md | 1 + Sentry.xcodeproj/project.pbxproj | 22 ++- Sources/Sentry/Public/SentryHub.h | 4 +- Sources/Sentry/Public/SentrySDK.h | 4 +- Sources/Sentry/Public/SentrySpan.h | 95 ++++++++++++- Sources/Sentry/Public/SentrySpanContext.h | 31 ++++- Sources/Sentry/Public/SentryTransaction.h | 36 ++++- .../Sentry/Public/SentryTransactionContext.h | 17 +-- Sources/Sentry/SentryHub.m | 12 +- Sources/Sentry/SentrySDK.m | 4 +- Sources/Sentry/SentrySpan.m | 98 ++++++++++++++ Sources/Sentry/SentrySpanContext.m | 42 +++++- Sources/Sentry/SentrySpanId.m | 8 +- Sources/Sentry/SentryTransaction.m | 62 +++++++-- Sources/Sentry/SentryTransactionContext.m | 18 +-- .../include/SentryTransaction+Private.h | 28 ++++ Tests/SentryTests/SentryClientTests.swift | 2 +- .../SentryTests/SentrySpanContextTests.swift | 76 +++++++++-- Tests/SentryTests/SentrySpanIdTests.swift | 78 +++++++++++ Tests/SentryTests/SentrySpanTests.swift | 126 ++++++++++++++++++ .../SentryTests/SentryTests-Bridging-Header.h | 1 + Tests/SentryTests/SentryTransaction+Private.h | 11 ++ .../SentryTests/SentryTransactionTests.swift | 82 +++++++++--- 23 files changed, 761 insertions(+), 97 deletions(-) create mode 100644 Sources/Sentry/SentrySpan.m create mode 100644 Sources/Sentry/include/SentryTransaction+Private.h create mode 100644 Tests/SentryTests/SentrySpanIdTests.swift create mode 100644 Tests/SentryTests/SentrySpanTests.swift create mode 100644 Tests/SentryTests/SentryTransaction+Private.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 695aeed9c27..a2169ae674e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## unreleased +- feat: Add SentrySpan #932 - feat: Expand SentrySpanContext through SentryTransaction #919 - feat: Performance Monitoring API - feat: Add sendDefaultPii to SentryOptions #923 diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index a2f70ce10af..4af9d3c95a4 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -397,7 +397,11 @@ 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; 861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */; }; 861265FA2404EC1500C4AFDE /* NSArray+SentrySanitize.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */; }; + 8E70B0FD25CB72BE002B3155 /* SentrySpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E70B0FC25CB72BE002B3155 /* SentrySpanTests.swift */; }; + 8E70B10125CB8695002B3155 /* SentrySpanIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */; }; 8E80D0C325BB29690029B150 /* SentryTransactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */; }; + 8EBAF05A25CCD066006FDD53 /* SentryTransaction+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC3AE9025CA3B6700E7591A /* SentryTransaction+Private.h */; }; + 8EC3AE7A25CA23B600E7591A /* SentrySpan.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EC3AE7925CA23B600E7591A /* SentrySpan.m */; }; 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8EC4CF5025C3A0070093DEE9 /* SentrySpanContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */; }; 8ECC673C25C23996000E2BF6 /* SentrySpan.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673725C23995000E2BF6 /* SentrySpan.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -855,7 +859,12 @@ 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashIntegration.m; sourceTree = ""; }; 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSArray+SentrySanitize.h"; path = "include/NSArray+SentrySanitize.h"; sourceTree = ""; }; 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+SentrySanitize.m"; sourceTree = ""; }; + 8E70B0D125CB67F3002B3155 /* SentryTransaction+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryTransaction+Private.h"; sourceTree = ""; }; + 8E70B0FC25CB72BE002B3155 /* SentrySpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanTests.swift; sourceTree = ""; }; + 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanIdTests.swift; sourceTree = ""; }; 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionTests.swift; sourceTree = ""; }; + 8EC3AE7925CA23B600E7591A /* SentrySpan.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpan.m; sourceTree = ""; }; + 8EC3AE9025CA3B6700E7591A /* SentryTransaction+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryTransaction+Private.h"; path = "include/SentryTransaction+Private.h"; sourceTree = ""; }; 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanStatus.h; path = Public/SentrySpanStatus.h; sourceTree = ""; }; 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanContextTests.swift; sourceTree = ""; }; 8ECC673725C23995000E2BF6 /* SentrySpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpan.h; path = Public/SentrySpan.h; sourceTree = ""; }; @@ -1189,8 +1198,11 @@ 7B0002332477F52D0035FEF1 /* SentrySessionTests.swift */, 7B944FAF2469B46000A10721 /* TestClient.swift */, 7B0A54552523178700A71716 /* SentryScopeSwiftTests.swift */, - 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */, + 8E70B0D125CB67F3002B3155 /* SentryTransaction+Private.h */, 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */, + 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */, + 8E70B0FC25CB72BE002B3155 /* SentrySpanTests.swift */, + 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */, ); path = SentryTests; sourceTree = ""; @@ -1643,13 +1655,15 @@ children = ( 8ECC673925C23996000E2BF6 /* SentrySpanContext.h */, 8ECC674325C23A1F000E2BF6 /* SentrySpanContext.m */, + 8ECC673725C23995000E2BF6 /* SentrySpan.h */, + 8EC3AE7925CA23B600E7591A /* SentrySpan.m */, 8ECC673825C23995000E2BF6 /* SentrySpanId.h */, 8ECC674525C23A20000E2BF6 /* SentrySpanId.m */, 8ECC673A25C23996000E2BF6 /* SentryTransaction.h */, 8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */, + 8EC3AE9025CA3B6700E7591A /* SentryTransaction+Private.h */, 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */, 8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */, - 8ECC673725C23995000E2BF6 /* SentrySpan.h */, 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */, ); name = Transaction; @@ -1807,6 +1821,7 @@ 63FE710120DA4C1000CDBAE8 /* SentryCrashDate.h in Headers */, 7B4E24FC251C97B500060D68 /* SentrySession.h in Headers */, 7B7D872C2486480B00D2ECFF /* SentryStacktraceBuilder.h in Headers */, + 8EBAF05A25CCD066006FDD53 /* SentryTransaction+Private.h in Headers */, 6334314120AD9AE40077E581 /* SentryMechanism.h in Headers */, 7B610D642512399600B0B5D9 /* SentryHub+Private.h in Headers */, 639FCF9C1EBC7F9500778193 /* SentryThread.h in Headers */, @@ -1977,6 +1992,7 @@ 63FE70D720DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.c in Sources */, 7BBD188B244841FB00427C76 /* SentryHttpDateParser.m in Sources */, 15E0A8E5240C457D00F044E3 /* SentryEnvelope.m in Sources */, + 8EC3AE7A25CA23B600E7591A /* SentrySpan.m in Sources */, 6360850E1ED2AFE100E8599E /* SentryBreadcrumb.m in Sources */, 7B56D73324616D9500B842DA /* SentryConcurrentRateLimitsDictionary.m in Sources */, 8ECC674825C23A20000E2BF6 /* SentryTransaction.m in Sources */, @@ -2151,6 +2167,7 @@ 63FE722120DA66EC00CDBAE8 /* SentryCrashDynamicLinker_Tests.m in Sources */, 63FE720120DA66EC00CDBAE8 /* RFC3339UTFString_Tests.m in Sources */, 63AA76701EB8CB4B00D153DE /* SentryTests.m in Sources */, + 8E70B0FD25CB72BE002B3155 /* SentrySpanTests.swift in Sources */, 7BBD188F2448469A00427C76 /* HttpDateFormatter.swift in Sources */, 63FE720C20DA66EC00CDBAE8 /* SentryCrashMonitor_Tests.m in Sources */, 63FE721820DA66EC00CDBAE8 /* TestThread.m in Sources */, @@ -2163,6 +2180,7 @@ 63FE720420DA66EC00CDBAE8 /* SentryCrashString_Tests.m in Sources */, 7B944FB22469C01E00A10721 /* TestClient.swift in Sources */, 7BC6EC0C255C3DF80059822A /* SentryThreadTests.swift in Sources */, + 8E70B10125CB8695002B3155 /* SentrySpanIdTests.swift in Sources */, 7BA61CAF247BBF3C00C130A8 /* SentryDebugMetaBuilderTests.swift in Sources */, 7BAF3DD2243DD05C008A5414 /* SentryTransportInitializerTests.swift in Sources */, 7BA61CBD247BC6B900C130A8 /* TestSentryCrashBinaryImageProvider.swift in Sources */, diff --git a/Sources/Sentry/Public/SentryHub.h b/Sources/Sentry/Public/SentryHub.h index 31e5cdf1aca..06ac52d4f73 100644 --- a/Sources/Sentry/Public/SentryHub.h +++ b/Sources/Sentry/Public/SentryHub.h @@ -56,11 +56,13 @@ SENTRY_NO_INIT * Creates a transaction bound to the hub and returns the instance. * * @param name The transaction name. + * @param operation Short code identifying the type of operation the span is measuring. * * @return The created transaction. */ - (SentryTransaction *)startTransactionWithName:(NSString *)name - NS_SWIFT_NAME(startTransaction(name:)); + operation:(NSString *)operation + NS_SWIFT_NAME(startTransaction(name:operation:)); /** * Creates a transaction bound to the hub and returns the instance. diff --git a/Sources/Sentry/Public/SentrySDK.h b/Sources/Sentry/Public/SentrySDK.h index 1d47281efb4..973f62dedd3 100644 --- a/Sources/Sentry/Public/SentrySDK.h +++ b/Sources/Sentry/Public/SentrySDK.h @@ -85,11 +85,13 @@ SENTRY_NO_INIT * Creates a transaction bound to the current hub and returns the instance. * * @param name The transaction name. + * @param operation Short code identifying the type of operation the transaction is measuring. * * @return The created transaction. */ + (SentryTransaction *)startTransactionWithName:(NSString *)name - NS_SWIFT_NAME(startTransaction(name:)); + operation:(NSString *)operation + NS_SWIFT_NAME(startTransaction(name:operation:)); /** * Creates a transaction bound to the current hub and returns the instance. diff --git a/Sources/Sentry/Public/SentrySpan.h b/Sources/Sentry/Public/SentrySpan.h index 825252285ef..ad08156754e 100644 --- a/Sources/Sentry/Public/SentrySpan.h +++ b/Sources/Sentry/Public/SentrySpan.h @@ -1,9 +1,100 @@ -#import +#import "SentrySpanContext.h" +#import NS_ASSUME_NONNULL_BEGIN +@class SentryTransaction, SentrySpanId, SentryId, SentryHub; + NS_SWIFT_NAME(Span) -@protocol SentrySpan +@interface SentrySpan : SentrySpanContext +SENTRY_NO_INIT + +/** + * The timestamp of which the span ended. + */ +@property (nullable, nonatomic, strong) NSDate *timestamp; + +/** + * The start time of the span. + */ +@property (nullable, nonatomic, strong) NSDate *startTimestamp; + +/** + * An arbitrary mapping of additional metadata of the span. + */ +@property (nullable, readonly) NSDictionary *extras; + +/** + * Whether the span is finished. + */ +@property (readonly) BOOL isFinished; + +/** + * Init a SentrySpan with given transaction, traceId, parentSpanId and hub. + * + * @param transaction The transaction associated with this span. + * @param traceId Determines which trace the Span belongs to. + * @param parentId Id of a parent span. + * + * @return SentrySpan + */ +- (instancetype)initWithTransaction:(SentryTransaction *)transaction + operation:(NSString *)operation + traceId:(SentryId *)traceId + parentId:(SentrySpanId *)parentId; + +/* + Removed because SentrySpan requires a transaction + */ +- (instancetype)initWithOperation:(NSString *)operation Sampled:(BOOL)sampled NS_UNAVAILABLE; + +/* + Removed because SentrySpan requires a transaction + */ +- (instancetype)initWithTraceId:(SentryId *)traceId + spanId:(SentrySpanId *)spanId + parentId:(nullable SentrySpanId *)parentId + operation:(NSString *)operation + sampled:(BOOL)sampled NS_UNAVAILABLE; + +/** + * Starts a child span. + * + * @param operation Short code identifying the type of operation the span is measuring. + * + * @return SentrySpan + */ +- (SentrySpan *)startChildWithOperation:(NSString *)operation NS_SWIFT_NAME(startChild(operation:)); + +/** + * Starts a child span. + * + * @param operation Defines the child span operation. + * @param description Define the child span description. + * + * @return SentrySpan + */ +- (SentrySpan *)startChildWithOperation:(NSString *)operation + description:(nullable NSString *)description + NS_SWIFT_NAME(startChild(operation:description:)); + +/** + * Sets an extra. + */ + +- (void)setExtraValue:(nullable id)value forKey:(NSString *)key NS_SWIFT_NAME(setExtra(value:key:)); + +/** + * Finishes the span by setting the end time. + */ +- (void)finish; + +/** + * Finishes the span by setting the end time and span status. + * + * @param status The status of this span + * */ +- (void)finishWithStatus:(SentrySpanStatus)status NS_SWIFT_NAME(finish(status:)); @end diff --git a/Sources/Sentry/Public/SentrySpanContext.h b/Sources/Sentry/Public/SentrySpanContext.h index 5c7f01640ac..35ed602ebdc 100644 --- a/Sources/Sentry/Public/SentrySpanContext.h +++ b/Sources/Sentry/Public/SentrySpanContext.h @@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(SpanContext) @interface SentrySpanContext : NSObject +SENTRY_NO_INIT /** * Determines which trace the Span belongs to. @@ -32,7 +33,7 @@ NS_SWIFT_NAME(SpanContext) /** * Short code identifying the type of operation the span is measuring. */ -@property (nullable, nonatomic, copy) NSString *operation; +@property (nonatomic, copy) NSString *operation; /** * Longer description of the span's operation, which uniquely identifies the span but is @@ -48,30 +49,35 @@ NS_SWIFT_NAME(SpanContext) /** * A map or list of tags for this event. Each tag must be less than 200 characters. */ -@property (nonatomic, readonly) NSMutableDictionary *tags; +@property (nonatomic, readonly) NSDictionary *tags; /** - * Init a SentryContext and sets all fields by default. + * Init a SentryContext with an operation code, + * traceId and spanId with be randomly created, + * sampled by default is false. * * @return SentryContext */ -- (instancetype)init; +- (instancetype)initWithOperation:(NSString *)operation; /** - * Init a SentryContext and mark it as sampled or not, sets the other fields by default. + * Init a SentryContext with an operation code and mark it as sampled or not. + * TraceId and SpanId with be randomly created. * + * @param operation The operation this span is measuring. * @param sampled Determines whether the trace is sampled * * @return SentryContext */ -- (instancetype)initWithSampled:(BOOL)sampled; +- (instancetype)initWithOperation:(NSString *)operation sampled:(BOOL)sampled; /** * Init a SentryContext with given traceId, spanId and parentId. * * @param traceId Determines which trace the Span belongs to. * @param spanId The Span Id + * @param operation The operation this span is measuring. * @param parentId Id of a parent span. * * @return SentryContext @@ -79,7 +85,18 @@ NS_SWIFT_NAME(SpanContext) - (instancetype)initWithTraceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId parentId:(nullable SentrySpanId *)parentId - andSampled:(BOOL)sampled; + operation:(NSString *)operation + sampled:(BOOL)sampled; + +/** + * Sets a tag with given value. + */ +- (void)setTagValue:(NSString *)value forKey:(NSString *)key NS_SWIFT_NAME(setTag(value:key:)); + +/** + * Removes a tag. + */ +- (void)removeTagForKey:(NSString *)key NS_SWIFT_NAME(removeTag(key:)); @property (class, nonatomic, readonly, copy) NSString *type; diff --git a/Sources/Sentry/Public/SentryTransaction.h b/Sources/Sentry/Public/SentryTransaction.h index d3592a91da9..5a241862b00 100644 --- a/Sources/Sentry/Public/SentryTransaction.h +++ b/Sources/Sentry/Public/SentryTransaction.h @@ -2,7 +2,7 @@ NS_ASSUME_NONNULL_BEGIN -@class SentrySpanContext, SentryTransactionContext, SentryHub; +@class SentrySpanContext, SentryTransactionContext, SentryHub, SentrySpan; NS_SWIFT_NAME(Transaction) @interface SentryTransaction : SentryEvent @@ -27,12 +27,12 @@ SENTRY_NO_INIT * Longer description of the span's operation, which uniquely identifies the span but is * consistent across instances of the span. */ -@property (nonatomic, copy) NSString *_Nullable spanDescription; +@property (nullable, nonatomic, copy) NSString *spanDescription; /** * Short code identifying the type of operation the transaction is measuring. */ -@property (nonatomic, copy) NSString *_Nullable operation; +@property (nullable, nonatomic, copy) NSString *operation; /** * Describes the status of the Transaction @@ -42,11 +42,12 @@ SENTRY_NO_INIT /** * Init a SentryTransaction with given name and set other fields by default * - * @param name Transaction name + * @param name Transaction name. + * @param operation Short code identifying the type of operation the transaction is measuring. * * @return SentryTransaction */ -- (instancetype)initWithName:(NSString *)name; +- (instancetype)initWithName:(NSString *)name operation:(NSString *)operation; /** * Init a SentryTransaction with given transaction context and hub and set other fields by default @@ -58,7 +59,7 @@ SENTRY_NO_INIT */ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext - andHub:(SentryHub *_Nullable)hub; + hub:(nullable SentryHub *)hub; /** * Init a SentryTransaction with given name, span context and hub and set other fields by default @@ -71,13 +72,34 @@ SENTRY_NO_INIT */ - (instancetype)initWithName:(NSString *)name spanContext:(SentrySpanContext *)spanContext - andHub:(nullable SentryHub *)hub; + hub:(nullable SentryHub *)hub; /** * Finishes the transaction by setting the end time and capturing the transaction with binded hub. */ - (void)finish; +/** + * Starts a child span. + * + * @param operation Defines the child span operation. + * + * @return SentrySpan + */ +- (SentrySpan *)startChildWithOperation:(NSString *)operation NS_SWIFT_NAME(startChild(operation:)); + +/** + * Starts a child span. + * + * @param operation Defines the child span operation. + * @param description Define the child span description. + * + * @return SentrySpan + */ +- (SentrySpan *)startChildWithOperation:(NSString *)operation + description:(nullable NSString *)description + NS_SWIFT_NAME(startChild(operation:description:)); + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Public/SentryTransactionContext.h b/Sources/Sentry/Public/SentryTransactionContext.h index 0bb6a7fb194..c3b1b0ff235 100644 --- a/Sources/Sentry/Public/SentryTransactionContext.h +++ b/Sources/Sentry/Public/SentryTransactionContext.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(TransactionContext) @interface SentryTransactionContext : SentrySpanContext +SENTRY_NO_INIT /** * Transaction name @@ -15,29 +16,24 @@ NS_SWIFT_NAME(TransactionContext) /** * Parent sampled */ -@property (nonatomic) bool parentSampled; - -/** - * Init a SentryTransactionContext and set all fields by default - * - * @return SentryTransactionContext - */ -- (instancetype)init; +@property (nonatomic) BOOL parentSampled; /** * Init a SentryTransactionContext with given name and set other fields by default * * @param name Transaction name + * @param operation The operation this span is measuring. * * @return SentryTransactionContext */ -- (instancetype)initWithName:(NSString *)name; +- (instancetype)initWithName:(NSString *)name operation:(NSString *)operation; /** * Init a SentryTransactionContext with given name, traceId, SpanId, parentSpanId and whether the * parent is sampled. * * @param name Transaction name + * @param operation The operation this span is measuring. * @param traceId Trace Id * @param spanId Span Id * @param parentSpanId Parent span id @@ -46,10 +42,11 @@ NS_SWIFT_NAME(TransactionContext) * @return SentryTransactionContext */ - (instancetype)initWithName:(NSString *)name + operation:(NSString *)operation traceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId parentSpanId:(nullable SentrySpanId *)parentSpanId - andParentSampled:(BOOL)parentSampled; + parentSampled:(BOOL)parentSampled; @end diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index e2ac83286fe..c55eefec3c0 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -232,17 +232,17 @@ - (SentryId *)captureTransaction:(SentryTransaction *)transaction return [self.client captureTransaction:transaction]; } -- (SentryTransaction *)startTransactionWithName:(NSString *)name +- (SentryTransaction *)startTransactionWithName:(NSString *)name operation:(NSString *)operation { - return - [[SentryTransaction alloc] initWithName:name - spanContext:[[SentryTransactionContext alloc] initWithName:name] - andHub:self]; + return [[SentryTransaction alloc] + initWithName:name + spanContext:[[SentryTransactionContext alloc] initWithName:name operation:operation] + hub:self]; } - (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext { - return [[SentryTransaction alloc] initWithTransactionContext:transactionContext andHub:self]; + return [[SentryTransaction alloc] initWithTransactionContext:transactionContext hub:self]; } - (SentryId *)captureMessage:(NSString *)message diff --git a/Sources/Sentry/SentrySDK.m b/Sources/Sentry/SentrySDK.m index 8341ff2edce..6aa0e4a4299 100644 --- a/Sources/Sentry/SentrySDK.m +++ b/Sources/Sentry/SentrySDK.m @@ -111,9 +111,9 @@ + (SentryId *)captureEvent:(SentryEvent *)event withScope:(SentryScope *)scope return [SentrySDK.currentHub captureEvent:event withScope:scope]; } -+ (SentryTransaction *)startTransactionWithName:(NSString *)name ++ (SentryTransaction *)startTransactionWithName:(NSString *)name operation:(NSString *)operation { - return [SentrySDK.currentHub startTransactionWithName:name]; + return [SentrySDK.currentHub startTransactionWithName:name operation:operation]; } + (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext diff --git a/Sources/Sentry/SentrySpan.m b/Sources/Sentry/SentrySpan.m new file mode 100644 index 00000000000..923311aa4d0 --- /dev/null +++ b/Sources/Sentry/SentrySpan.m @@ -0,0 +1,98 @@ +#import "NSDate+SentryExtras.h" +#import "SentryCurrentDate.h" +#import "SentryTransaction+Private.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface +SentrySpan () { + NSMutableDictionary *_extras; +} + +/** + * The transaction associated with this span. + */ +@property (nonatomic) SentryTransaction *transaction; + +@end + +@implementation SentrySpan + +- (instancetype)initWithTransaction:(SentryTransaction *)transaction + operation:(NSString *)operation + traceId:(SentryId *)traceId + parentId:(SentrySpanId *)parentId +{ + if ([super initWithTraceId:traceId + spanId:[[SentrySpanId alloc] init] + parentId:parentId + operation:operation + sampled:transaction.isSampled]) { + self.transaction = transaction; + self.startTimestamp = [SentryCurrentDate date]; + _extras = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (SentrySpan *)startChildWithOperation:(NSString *)operation +{ + return [self startChildWithOperation:operation description:nil]; +} + +- (SentrySpan *)startChildWithOperation:(NSString *)operation + description:(nullable NSString *)description +{ + return [self.transaction startChildWithParentId:[self spanId] + operation:operation + description:description]; +} + +- (void)setExtraValue:(nullable NSString *)value forKey:(NSString *)key +{ + @synchronized(_extras) { + [_extras setValue:value forKey:key]; + } +} + +- (nullable NSDictionary *)extras +{ + return _extras; +} + +- (BOOL)isFinished +{ + return self.timestamp != nil; +} + +- (void)finish +{ + self.timestamp = [SentryCurrentDate date]; +} + +- (void)finishWithStatus:(SentrySpanStatus)status +{ + self.status = status; + [self finish]; +} + +- (NSDictionary *)serialize +{ + NSMutableDictionary *mutableDictionary = + [[NSMutableDictionary alloc] initWithDictionary:[super serialize]]; + [mutableDictionary setValue:[self.timestamp sentry_toIso8601String] forKey:@"timestamp"]; + [mutableDictionary setValue:[self.startTimestamp sentry_toIso8601String] + forKey:@"start_timestamp"]; + + if (_extras != nil) { + [mutableDictionary setValue:_extras.copy forKey:@"data"]; + } + + return mutableDictionary; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentrySpanContext.m b/Sources/Sentry/SentrySpanContext.m index 79cfed0d6b8..c1c374f606e 100644 --- a/Sources/Sentry/SentrySpanContext.m +++ b/Sources/Sentry/SentrySpanContext.m @@ -2,31 +2,41 @@ #import "SentryId.h" #import "SentrySpanId.h" +@interface +SentrySpanContext () { + NSMutableDictionary *_tags; +} + +@end + @implementation SentrySpanContext -- (instancetype)init +- (instancetype)initWithOperation:(NSString *)operation { - return [self initWithSampled:false]; + return [self initWithOperation:operation sampled:false]; } -- (instancetype)initWithSampled:(BOOL)sampled +- (instancetype)initWithOperation:(NSString *)operation sampled:(BOOL)sampled { return [self initWithTraceId:[[SentryId alloc] init] spanId:[[SentrySpanId alloc] init] parentId:nil - andSampled:sampled]; + operation:operation + sampled:sampled]; } - (instancetype)initWithTraceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId - parentId:(SentrySpanId *_Nullable)parentId - andSampled:(BOOL)sampled + parentId:(nullable SentrySpanId *)parentId + operation:(NSString *)operation + sampled:(BOOL)sampled { if (self = [super init]) { self.traceId = traceId; self.spanId = spanId; self.parentSpanId = parentId; self.sampled = sampled; + self.operation = operation; self.status = kSentrySpanStatusUndefined; _tags = [[NSMutableDictionary alloc] init]; } @@ -41,6 +51,26 @@ + (NSString *)type return type; } +- (NSDictionary *)tags +{ + @synchronized(_tags) { + return _tags.copy; + } +} +- (void)setTagValue:(NSString *)value forKey:(NSString *)key +{ + @synchronized(_tags) { + [_tags setValue:value forKey:key]; + } +} + +- (void)removeTagForKey:(NSString *)key +{ + @synchronized(_tags) { + [_tags removeObjectForKey:key]; + } +} + - (NSDictionary *)serialize { NSMutableDictionary *mutabledictionary = @{ diff --git a/Sources/Sentry/SentrySpanId.m b/Sources/Sentry/SentrySpanId.m index bdcf3e3bedc..b3d7a51ff35 100644 --- a/Sources/Sentry/SentrySpanId.m +++ b/Sources/Sentry/SentrySpanId.m @@ -22,9 +22,9 @@ - (instancetype)init - (instancetype)initWithUUID:(NSUUID *)uuid { - return [self initWithValue:[[uuid.UUIDString stringByReplacingOccurrencesOfString:@"-" - withString:@""] - substringToIndex:16]]; + return [self initWithValue:[[uuid.UUIDString.lowercaseString + stringByReplacingOccurrencesOfString:@"-" + withString:@""] substringToIndex:16]]; } - (instancetype)initWithValue:(NSString *)value @@ -32,6 +32,8 @@ - (instancetype)initWithValue:(NSString *)value if (self = [super init]) { if (value.length != 16) return [SentrySpanId empty]; + value = value.lowercaseString; + self.value = value; } diff --git a/Sources/Sentry/SentryTransaction.m b/Sources/Sentry/SentryTransaction.m index 556d23e1f8d..43d4ce90869 100644 --- a/Sources/Sentry/SentryTransaction.m +++ b/Sources/Sentry/SentryTransaction.m @@ -13,14 +13,19 @@ SentryTransaction () /** - * This transaction span context + * This transaction span context. */ -@property (nonatomic, strong) SentrySpanContext *spanContext; +@property (nonatomic) SentrySpanContext *spanContext; /** * A hub this transaction is attached to. */ -@property (nonatomic, readonly) SentryHub *_Nullable hub; +@property (nullable, nonatomic) SentryHub *hub; + +/** + * A list of child spans. + */ +@property (nonatomic) NSMutableArray *spans; @end @@ -37,26 +42,28 @@ - (instancetype)init return self; } -- (instancetype)initWithName:(NSString *)name +- (instancetype)initWithName:(NSString *)name operation:(NSString *)operation { - return [self initWithName:name spanContext:[[SentrySpanContext alloc] init] andHub:nil]; + return [self initWithName:name + spanContext:[[SentrySpanContext alloc] initWithOperation:operation] + hub:nil]; } -- (instancetype)initWithTransactionContext:(SentryTransactionContext *)context - andHub:(SentryHub *)hub +- (instancetype)initWithTransactionContext:(SentryTransactionContext *)context hub:(SentryHub *)hub { - return [self initWithName:context.name spanContext:context andHub:hub]; + return [self initWithName:context.name spanContext:context hub:hub]; } - (instancetype)initWithName:(NSString *)name spanContext:(nonnull SentrySpanContext *)spanContext - andHub:(SentryHub *)hub + hub:(SentryHub *)hub { if ([self init]) { self.transaction = name; self.startTimestamp = [SentryCurrentDate date]; - _hub = hub; + self.hub = hub; self.spanContext = spanContext; + self.spans = [[NSMutableArray alloc] init]; } return self; } @@ -112,11 +119,44 @@ - (void)setOperation:(NSString *)operation [self.spanContext setOperation:operation]; } +- (SentrySpan *)startChildWithOperation:(NSString *)operation +{ + return [self startChildWithOperation:operation description:nil]; +} + +- (SentrySpan *)startChildWithOperation:(NSString *)operation + description:(nullable NSString *)description +{ + return [self startChildWithParentId:self.spanId operation:operation description:description]; +} + +- (SentrySpan *)startChildWithParentId:(SentrySpanId *)parentId + operation:(NSString *)operation + description:(nullable NSString *)description +{ + SentrySpan *span = [[SentrySpan alloc] initWithTransaction:self + operation:operation + traceId:self.traceId + parentId:parentId]; + + span.spanDescription = description; + span.sampled = self.isSampled; + @synchronized(self.spans) { + [self.spans addObject:span]; + } + return span; +} + - (NSDictionary *)serialize { NSMutableDictionary *serializedData = [[NSMutableDictionary alloc] initWithDictionary:[super serialize]]; - serializedData[@"spans"] = @[]; + + NSMutableArray *spans = [[NSMutableArray alloc] init]; + for (SentrySpan *span in self.spans) { + [spans addObject:[span serialize]]; + } + serializedData[@"spans"] = spans; NSMutableDictionary *mutableContext = [[NSMutableDictionary alloc] init]; if (serializedData[@"contexts"] != nil) { diff --git a/Sources/Sentry/SentryTransactionContext.m b/Sources/Sentry/SentryTransactionContext.m index e14cfb5e9b0..7747b3cd627 100644 --- a/Sources/Sentry/SentryTransactionContext.m +++ b/Sources/Sentry/SentryTransactionContext.m @@ -2,14 +2,9 @@ @implementation SentryTransactionContext -- (instancetype)init +- (instancetype)initWithName:(NSString *)name operation:(NSString *)operation { - return [super init]; -} - -- (instancetype)initWithName:(NSString *)name -{ - if (self = [self init]) { + if (self = [super initWithOperation:operation]) { _name = [NSString stringWithString:name]; self.parentSampled = false; } @@ -17,12 +12,17 @@ - (instancetype)initWithName:(NSString *)name } - (instancetype)initWithName:(NSString *)name + operation:(nonnull NSString *)operation traceId:(SentryId *)traceId spanId:(SentrySpanId *)spanId parentSpanId:(SentrySpanId *)parentSpanId - andParentSampled:(BOOL)parentSampled + parentSampled:(BOOL)parentSampled { - if ([super initWithTraceId:traceId spanId:spanId parentId:parentSpanId andSampled:false]) { + if ([super initWithTraceId:traceId + spanId:spanId + parentId:parentSpanId + operation:operation + sampled:false]) { _name = [NSString stringWithString:name]; self.parentSampled = parentSampled; } diff --git a/Sources/Sentry/include/SentryTransaction+Private.h b/Sources/Sentry/include/SentryTransaction+Private.h new file mode 100644 index 00000000000..84fa76af498 --- /dev/null +++ b/Sources/Sentry/include/SentryTransaction+Private.h @@ -0,0 +1,28 @@ +#import "SentryTransaction.h" + +@class SentrySpanId, SentrySpan; + +NS_ASSUME_NONNULL_BEGIN + +/** + * SentryTransaction SDK internal methods. + * This should not be in the public API. + */ +@interface SentryTransaction (Private) + +/** + * Starts a child span. + * + * @param parentId The child span parent id. + * @param operation The child span operation. + * @param description The child span description. + * + * @return SentrySpan + */ +- (SentrySpan *)startChildWithParentId:(SentrySpanId *)parentId + operation:(NSString *)operation + description:(nullable NSString *)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 0a724c21ab2..09e3c5e80d2 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -159,7 +159,7 @@ class SentryClientTest: XCTestCase { } func testCaptureTransaction() { - let transaction = Transaction(name: "Some Transaction") + let transaction = Transaction(name: "Some Transaction", operation: "Some Operation") let eventId = fixture.getSut().capture(transaction: transaction) eventId.assertIsNotEmpty() diff --git a/Tests/SentryTests/SentrySpanContextTests.swift b/Tests/SentryTests/SentrySpanContextTests.swift index 34dfa1a2808..aaca2af0493 100644 --- a/Tests/SentryTests/SentrySpanContextTests.swift +++ b/Tests/SentryTests/SentrySpanContextTests.swift @@ -1,12 +1,13 @@ import XCTest class SentrySpanContextTests: XCTestCase { - + let someOperation = "Some Operation" + func testInit() { - let spanContext = SpanContext() + let spanContext = SpanContext(operation: someOperation) XCTAssertFalse(spanContext.sampled) XCTAssertNil(spanContext.parentSpanId) - XCTAssertNil(spanContext.operation) + XCTAssertEqual(spanContext.operation, someOperation) XCTAssertNil(spanContext.spanDescription) XCTAssertEqual(spanContext.tags.count, 0) XCTAssertEqual(spanContext.traceId.sentryIdString.count, 32) @@ -14,9 +15,9 @@ class SentrySpanContextTests: XCTestCase { } func testInitWithSampled() { - let spanContext = SpanContext(sampled: true) + let spanContext = SpanContext(operation: someOperation, sampled: true) XCTAssertTrue(spanContext.sampled) - XCTAssertNil(spanContext.operation) + XCTAssertEqual(spanContext.operation, someOperation) XCTAssertNil(spanContext.parentSpanId) XCTAssertNil(spanContext.spanDescription) XCTAssertEqual(spanContext.tags.count, 0) @@ -29,7 +30,7 @@ class SentrySpanContextTests: XCTestCase { let spanId = SpanId() let parentId = SpanId() - let spanContext = SpanContext(trace: id, spanId: spanId, parentId: parentId, andSampled: true) + let spanContext = SpanContext(trace: id, spanId: spanId, parentId: parentId, operation: someOperation, sampled: true) XCTAssertEqual(id, spanContext.traceId) XCTAssertEqual(spanId, spanContext.spanId) @@ -37,16 +38,15 @@ class SentrySpanContextTests: XCTestCase { XCTAssertTrue(spanContext.sampled) XCTAssertNil(spanContext.spanDescription) XCTAssertEqual(spanContext.tags.count, 0) + XCTAssertEqual(spanContext.operation, someOperation) } func testSerialization() { let id = SentryId() let spanId = SpanId() let parentId = SpanId() - let operation = "Some Operation" - let spanContext = SpanContext(trace: id, spanId: spanId, parentId: parentId, andSampled: true) - spanContext.operation = operation + let spanContext = SpanContext(trace: id, spanId: spanId, parentId: parentId, operation: someOperation, sampled: true) spanContext.status = .ok let data = spanContext.serialize() @@ -54,7 +54,7 @@ class SentrySpanContextTests: XCTestCase { XCTAssertEqual(data["span_id"] as? String, spanId.sentrySpanIdString) XCTAssertEqual(data["trace_id"] as? String, id.sentryIdString) XCTAssertEqual(data["type"] as? String, SpanContext.type) - XCTAssertEqual(data["op"] as? String, operation) + XCTAssertEqual(data["op"] as? String, someOperation) XCTAssertEqual(data["sampled"] as? String, "true") XCTAssertEqual(data["parent_span_id"] as? String, parentId.sentrySpanIdString) XCTAssertEqual(data["status"] as? String, "ok") @@ -63,4 +63,60 @@ class SentrySpanContextTests: XCTestCase { func testSpanContextTraceTypeValue() { XCTAssertEqual(SpanContext.type, "trace") } + + func testSetTags() { + let tagKey = "tag_key" + let tagValue = "tag_value" + + let spanContext = SpanContext(operation: someOperation) + spanContext.setTag(value: tagValue, key: tagKey) + XCTAssertEqual(spanContext.tags.count, 1) + XCTAssertEqual(spanContext.tags[tagKey], tagValue) + } + + func testUnsetTags() { + let tagKey = "tag_key" + let tagValue = "tag_value" + + let spanContext = SpanContext(operation: someOperation) + spanContext.setTag(value: tagValue, key: tagKey) + XCTAssertEqual(spanContext.tags.count, 1) + spanContext.removeTag(key: tagKey) + XCTAssertEqual(spanContext.tags.count, 0) + XCTAssertNil(spanContext.tags[tagKey]) + } + + @available(tvOS 10.0, *) + @available(OSX 10.12, *) + @available(iOS 10.0, *) + func testModifyingTagsFromMultipleThreads() { + let queue = DispatchQueue(label: "SentrySpanTests", qos: .userInteractive, attributes: [.concurrent, .initiallyInactive]) + let group = DispatchGroup() + + let tagValue = "tag_value" + + let spanContext = SpanContext(operation: someOperation) + + // The number is kept small for the CI to not take to long. + // If you really want to test this increase to 100_000 or so. + let innerLoop = 1_000 + let outerLoop = 20 + + for i in 0.. Span { + let transaction = Transaction(name: someTransaction, operation: someOperation) + return Span(transaction: transaction, operation: someOperation, trace: SentryId(), parentId: transaction.spanId) + } + } + + private var fixture: Fixture! + override func setUp() { + let testDataProvider = TestCurrentDateProvider() + CurrentDate.setCurrentDateProvider(testDataProvider) + testDataProvider.setDate(date: TestData.timestamp) + + fixture = Fixture() + } + + func testInitAndCheckForTimestamps() { + let span = fixture.getSut() + XCTAssertNotNil(span.startTimestamp) + XCTAssertNil(span.timestamp) + XCTAssertFalse(span.isFinished) + } + + func testFinish() { + let span = fixture.getSut() + + span.finish() + + XCTAssertEqual(span.startTimestamp, TestData.timestamp) + XCTAssertEqual(span.timestamp, TestData.timestamp) + XCTAssertTrue(span.isFinished) + } + + func testFinishWithStatus() { + let span = fixture.getSut() + span.finish(status: .ok) + + XCTAssertEqual(span.startTimestamp, TestData.timestamp) + XCTAssertEqual(span.timestamp, TestData.timestamp) + XCTAssertEqual(span.status, .ok) + XCTAssertTrue(span.isFinished) + } + + func testStartChildWithOperation() { + let span = fixture.getSut() + + let childSpan = span.startChild(operation: fixture.someOperation) + XCTAssertEqual(childSpan.parentSpanId, span.spanId) + XCTAssertEqual(childSpan.operation, fixture.someOperation) + XCTAssertNil(childSpan.spanDescription) + } + + func testStartChildWithOperationAndDescription() { + let span = fixture.getSut() + + let childSpan = span.startChild(operation: fixture.someOperation, description: fixture.someDescription) + + XCTAssertEqual(childSpan.parentSpanId, span.spanId) + XCTAssertEqual(childSpan.operation, fixture.someOperation) + XCTAssertEqual(childSpan.spanDescription, fixture.someDescription) + } + + func testSetExtras() { + let span = fixture.getSut() + + span.setExtra(value: fixture.extraValue, key: fixture.extraKey) + + XCTAssertEqual(span.extras!.count, 1) + XCTAssertEqual(span.extras![fixture.extraKey] as! String, fixture.extraValue) + } + + func testSerialization() { + let span = fixture.getSut() + + span.setExtra(value: fixture.extraValue, key: fixture.extraKey) + span.finish() + + let serialization = span.serialize() + XCTAssertEqual(serialization["timestamp"] as? String, TestData.timestampAs8601String) + XCTAssertEqual(serialization["start_timestamp"] as? String, TestData.timestampAs8601String) + XCTAssertNotNil(serialization["data"]) + XCTAssertEqual((serialization["data"] as! Dictionary)[fixture.extraKey], fixture.extraValue) + } + + @available(tvOS 10.0, *) + @available(OSX 10.12, *) + @available(iOS 10.0, *) + func testModifyingTagsFromMultipleThreads() { + let queue = DispatchQueue(label: "SentrySpanTests", qos: .userInteractive, attributes: [.concurrent, .initiallyInactive]) + let group = DispatchGroup() + + let span = fixture.getSut() + + // The number is kept small for the CI to not take to long. + // If you really want to test this increase to 100_000 or so. + let innerLoop = 1_000 + let outerLoop = 20 + let value = fixture.extraValue + + for i in 0.. *spans; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/SentryTransactionTests.swift b/Tests/SentryTests/SentryTransactionTests.swift index 630a35fe6b4..e2a469295a8 100644 --- a/Tests/SentryTests/SentryTransactionTests.swift +++ b/Tests/SentryTests/SentryTransactionTests.swift @@ -5,8 +5,8 @@ class SentryTransactionTest: XCTestCase { let someOperation = "Some Operation" func testInitWithName() { - let transaction = Transaction(name: someTransactionName) - + let transaction = Transaction(name: someTransactionName, operation: someOperation) + XCTAssertNotNil(transaction.startTimestamp) XCTAssertNil(transaction.timestamp) XCTAssertEqual(transaction.transaction, someTransactionName) @@ -16,13 +16,13 @@ class SentryTransactionTest: XCTestCase { let someOperation = "Some Operation" let someSpanDescription = "Some Span Description" - let context = TransactionContext(name: someTransactionName, trace: SentryId(), spanId: SpanId(), parentSpanId: SpanId(), andParentSampled: true) + let context = TransactionContext(name: someTransactionName, operation: someOperation, trace: SentryId(), spanId: SpanId(), parentSpanId: SpanId(), parentSampled: true) context.operation = someOperation context.status = .ok context.sampled = true context.spanDescription = someSpanDescription - let transaction = Transaction(transactionContext: context, andHub: nil) + let transaction = Transaction(transactionContext: context, hub: nil) XCTAssertNotNil(transaction.startTimestamp) XCTAssertNil(transaction.timestamp) XCTAssertEqual(transaction.transaction, someTransactionName) @@ -38,9 +38,9 @@ class SentryTransactionTest: XCTestCase { let someOperation = "Some Operation" let spanDescription = "Span Description" - let context = TransactionContext(name: someTransactionName) + let context = TransactionContext(name: someTransactionName, operation: someOperation) - let transaction = Transaction(transactionContext: context, andHub: nil) + let transaction = Transaction(transactionContext: context, hub: nil) transaction.spanDescription = spanDescription transaction.operation = someOperation transaction.status = .ok @@ -51,11 +51,11 @@ class SentryTransactionTest: XCTestCase { } func testInitWithNameAndContext() { - let context = SpanContext() + let context = SpanContext(operation: someOperation) context.operation = someOperation context.status = .ok - let transaction = Transaction(name: someTransactionName, spanContext: context, andHub: nil) + let transaction = Transaction(name: someTransactionName, spanContext: context, hub: nil) XCTAssertNotNil(transaction.startTimestamp) XCTAssertNil(transaction.timestamp) XCTAssertEqual(transaction.transaction, someTransactionName) @@ -66,29 +66,36 @@ class SentryTransactionTest: XCTestCase { } func testFinishCapturesTransaction() { + let testDataProvider = TestCurrentDateProvider() + CurrentDate.setCurrentDateProvider(testDataProvider) + let fileManager = try! SentryFileManager(dsn: TestConstants.dsn, andCurrentDateProvider: TestCurrentDateProvider()) let transport = TestTransport() let client = TestClient(options: Options(), andTransport: transport, andFileManager: fileManager) let hub = SentryHub(client: client, andScope: nil, andCrashAdapter: TestSentryCrashWrapper()) - let transaction = Transaction(name: someTransactionName, spanContext: SpanContext(), andHub: hub) + let transaction = Transaction(name: someTransactionName, spanContext: SpanContext(operation: someOperation), hub: hub) transaction.finish() - XCTAssertNotNil(transaction.startTimestamp) - XCTAssertNotNil(transaction.timestamp) + XCTAssertEqual(transaction.startTimestamp, testDataProvider.date()) + XCTAssertEqual(transaction.timestamp, testDataProvider.date()) XCTAssertTrue(transaction.timestamp! >= transaction.startTimestamp!) XCTAssertTrue(client.captureEventWithScopeArguments.last!.event === transaction) } func testSerializationWithoutContext() { - let transaction = Transaction(name: someTransactionName) + let testDataProvider = TestCurrentDateProvider() + CurrentDate.setCurrentDateProvider(testDataProvider) + testDataProvider.setDate(date: TestData.timestamp) + + let transaction = Transaction(name: someTransactionName, operation: someOperation) let serialization = transaction.serialize() XCTAssertNotNil(serialization) XCTAssertEqual(serialization["type"] as? String, "transaction") - XCTAssertNotNil(serialization["event_id"]) - XCTAssertNotNil(serialization["start_timestamp"]) - XCTAssertNotNil(serialization["timestamp"]) + XCTAssertEqual(serialization["event_id"] as? String, transaction.eventId.sentryIdString) + XCTAssertNotNil(serialization["start_timestamp"] as? String, TestData.timestampAs8601String) + XCTAssertEqual(serialization["timestamp"] as? String, TestData.timestampAs8601String) XCTAssertEqual(serialization["transaction"] as? String, someTransactionName) XCTAssertNotNil(serialization["contexts"]) XCTAssertNotNil((serialization["contexts"] as! Dictionary)["trace"]) @@ -96,18 +103,55 @@ class SentryTransactionTest: XCTestCase { } func testSerializationWithContext() { - let transaction = Transaction(name: someTransactionName) + let testDataProvider = TestCurrentDateProvider() + CurrentDate.setCurrentDateProvider(testDataProvider) + testDataProvider.setDate(date: TestData.timestamp) + + let transaction = Transaction(name: someTransactionName, operation: someOperation) transaction.context = [String: [String: Any]]() let serialization = transaction.serialize() XCTAssertNotNil(serialization) XCTAssertEqual(serialization["type"] as? String, "transaction") - XCTAssertNotNil(serialization["event_id"]) - XCTAssertNotNil(serialization["start_timestamp"]) - XCTAssertNotNil(serialization["timestamp"]) + XCTAssertEqual(serialization["event_id"] as? String, transaction.eventId.sentryIdString) + XCTAssertNotNil(serialization["start_timestamp"] as? String, TestData.timestampAs8601String) + XCTAssertEqual(serialization["timestamp"] as? String, TestData.timestampAs8601String) XCTAssertEqual(serialization["transaction"] as? String, someTransactionName) XCTAssertNotNil(serialization["contexts"]) XCTAssertNotNil((serialization["contexts"] as! Dictionary)["trace"]) XCTAssertNotNil(serialization["spans"]) } + + func testAdditionOfChild() { + let transaction = Transaction(name: someTransactionName, operation: someOperation) + let span = transaction.startChild(operation: someOperation) + + XCTAssertEqual(transaction.spans.count, 1) + XCTAssertEqual(span.operation, someOperation) + XCTAssertEqual(span.parentSpanId, transaction.spanId) + XCTAssertNotNil(span.startTimestamp) + XCTAssertNil(span.spanDescription) + XCTAssertNil(span.timestamp) + } + + func testAddChildWithOperationAndDescription() { + let transaction = Transaction(name: someTransactionName, operation: someOperation) + let someDescription = "Some Description" + let span = transaction.startChild(operation: someOperation, description: someDescription) + + XCTAssertEqual(span.operation, someOperation) + XCTAssertEqual(span.spanDescription, someDescription) + XCTAssertEqual(span.parentSpanId, transaction.spanId) + XCTAssertNotNil(span.startTimestamp) + XCTAssertNil(span.timestamp) + } + + func testSerializeWithSpan() { + let transaction = Transaction(name: someTransactionName, operation: someOperation) + transaction.startChild(operation: someOperation) + + let serialization = transaction.serialize() + let spansSerialized = serialization["spans"] as! [Any] + XCTAssertEqual(spansSerialized.count, 1) + } } From 9059a8db991017dacba1c4de0095e1902da01495 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 25 Feb 2021 11:34:19 -0300 Subject: [PATCH 08/14] ref: Remove SentryTransaction from public API. (#950) Co-authored-by: Sentry Github Bot --- .../iOS-Swift/iOS-Swift/ViewController.swift | 2 +- Sentry.xcodeproj/project.pbxproj | 38 +++-- Sources/Sentry/Public/Sentry.h | 3 +- Sources/Sentry/Public/SentryClient.h | 3 - Sources/Sentry/Public/SentryHub.h | 19 +-- Sources/Sentry/Public/SentrySDK.h | 10 +- Sources/Sentry/Public/SentrySpan.h | 101 ----------- Sources/Sentry/Public/SentrySpanContext.h | 6 +- Sources/Sentry/Public/SentrySpanProtocol.h | 87 ++++++++++ Sources/Sentry/Public/SentryTransaction.h | 105 ------------ Sources/Sentry/SentryClient.m | 5 - Sources/Sentry/SentryHub.m | 21 +-- Sources/Sentry/SentrySDK.m | 4 +- Sources/Sentry/SentrySpan.m | 70 ++++---- Sources/Sentry/SentrySpanContext.m | 10 +- Sources/Sentry/SentryTracer.m | 132 +++++++++++++++ Sources/Sentry/SentryTransaction.m | 155 ++--------------- Sources/Sentry/include/SentrySpan.h | 113 +++++++++++++ Sources/Sentry/include/SentryTracer.h | 110 ++++++++++++ .../include/SentryTransaction+Private.h | 28 ---- Sources/Sentry/include/SentryTransaction.h | 13 ++ Tests/SentryTests/SentryClientTests.swift | 11 -- Tests/SentryTests/SentrySpanTests.swift | 78 ++++++--- .../SentryTests/SentryTests-Bridging-Header.h | 1 - Tests/SentryTests/SentryTransaction+Private.h | 11 -- .../SentryTests/SentryTransactionTests.swift | 157 ------------------ 26 files changed, 618 insertions(+), 675 deletions(-) delete mode 100644 Sources/Sentry/Public/SentrySpan.h create mode 100644 Sources/Sentry/Public/SentrySpanProtocol.h delete mode 100644 Sources/Sentry/Public/SentryTransaction.h create mode 100644 Sources/Sentry/SentryTracer.m create mode 100644 Sources/Sentry/include/SentrySpan.h create mode 100644 Sources/Sentry/include/SentryTracer.h delete mode 100644 Sources/Sentry/include/SentryTransaction+Private.h create mode 100644 Sources/Sentry/include/SentryTransaction.h delete mode 100644 Tests/SentryTests/SentryTransaction+Private.h delete mode 100644 Tests/SentryTests/SentryTransactionTests.swift diff --git a/Samples/iOS-Swift/iOS-Swift/ViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewController.swift index a27436a105d..cc78ce5a28a 100644 --- a/Samples/iOS-Swift/iOS-Swift/ViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ViewController.swift @@ -81,7 +81,7 @@ class ViewController: UIViewController { } @IBAction func captureTransaction(_ sender: Any) { - let transaction = SentrySDK.startTransaction(name: "Some Transaction") + let transaction = SentrySDK.startTransaction(name: "Some Transaction", operation: "Some Operation") DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0.4...0.6), execute: { transaction.finish() }) diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index a5a0865d87b..8a8792fa8d8 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -400,17 +400,18 @@ 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; 861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */; }; 861265FA2404EC1500C4AFDE /* NSArray+SentrySanitize.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */; }; + 8E4E7C6D25DAAAFE006AB9E2 /* SentryTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C6B25DAAAFE006AB9E2 /* SentryTransaction.h */; }; + 8E4E7C6E25DAAAFE006AB9E2 /* SentrySpan.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */; }; + 8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C7325DAAB49006AB9E2 /* SentrySpanProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8E4E7C7D25DAB287006AB9E2 /* SentryTracer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C7B25DAB287006AB9E2 /* SentryTracer.h */; }; + 8E4E7C8225DAB2A5006AB9E2 /* SentryTracer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E4E7C8125DAB2A5006AB9E2 /* SentryTracer.m */; }; 8E70B0FD25CB72BE002B3155 /* SentrySpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E70B0FC25CB72BE002B3155 /* SentrySpanTests.swift */; }; 8E70B10125CB8695002B3155 /* SentrySpanIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */; }; - 8E80D0C325BB29690029B150 /* SentryTransactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */; }; - 8EBAF05A25CCD066006FDD53 /* SentryTransaction+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC3AE9025CA3B6700E7591A /* SentryTransaction+Private.h */; }; 8EC3AE7A25CA23B600E7591A /* SentrySpan.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EC3AE7925CA23B600E7591A /* SentrySpan.m */; }; 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8EC4CF5025C3A0070093DEE9 /* SentrySpanContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */; }; - 8ECC673C25C23996000E2BF6 /* SentrySpan.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673725C23995000E2BF6 /* SentrySpan.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673825C23995000E2BF6 /* SentrySpanId.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ECC673E25C23996000E2BF6 /* SentrySpanContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673925C23996000E2BF6 /* SentrySpanContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8ECC673F25C23996000E2BF6 /* SentryTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673A25C23996000E2BF6 /* SentryTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ECC674025C23996000E2BF6 /* SentryTransactionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ECC674725C23A20000E2BF6 /* SentrySpanContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ECC674325C23A1F000E2BF6 /* SentrySpanContext.m */; }; 8ECC674825C23A20000E2BF6 /* SentryTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */; }; @@ -865,18 +866,18 @@ 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashIntegration.m; sourceTree = ""; }; 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSArray+SentrySanitize.h"; path = "include/NSArray+SentrySanitize.h"; sourceTree = ""; }; 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+SentrySanitize.m"; sourceTree = ""; }; - 8E70B0D125CB67F3002B3155 /* SentryTransaction+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryTransaction+Private.h"; sourceTree = ""; }; + 8E4E7C6B25DAAAFE006AB9E2 /* SentryTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryTransaction.h; path = include/SentryTransaction.h; sourceTree = ""; }; + 8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpan.h; path = include/SentrySpan.h; sourceTree = ""; }; + 8E4E7C7325DAAB49006AB9E2 /* SentrySpanProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanProtocol.h; path = Public/SentrySpanProtocol.h; sourceTree = ""; }; + 8E4E7C7B25DAB287006AB9E2 /* SentryTracer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTracer.h; path = include/SentryTracer.h; sourceTree = ""; }; + 8E4E7C8125DAB2A5006AB9E2 /* SentryTracer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryTracer.m; sourceTree = ""; }; 8E70B0FC25CB72BE002B3155 /* SentrySpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanTests.swift; sourceTree = ""; }; 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanIdTests.swift; sourceTree = ""; }; - 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionTests.swift; sourceTree = ""; }; 8EC3AE7925CA23B600E7591A /* SentrySpan.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpan.m; sourceTree = ""; }; - 8EC3AE9025CA3B6700E7591A /* SentryTransaction+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryTransaction+Private.h"; path = "include/SentryTransaction+Private.h"; sourceTree = ""; }; 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanStatus.h; path = Public/SentrySpanStatus.h; sourceTree = ""; }; 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanContextTests.swift; sourceTree = ""; }; - 8ECC673725C23995000E2BF6 /* SentrySpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpan.h; path = Public/SentrySpan.h; sourceTree = ""; }; 8ECC673825C23995000E2BF6 /* SentrySpanId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpanId.h; path = Public/SentrySpanId.h; sourceTree = ""; }; 8ECC673925C23996000E2BF6 /* SentrySpanContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpanContext.h; path = Public/SentrySpanContext.h; sourceTree = ""; }; - 8ECC673A25C23996000E2BF6 /* SentryTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryTransaction.h; path = Public/SentryTransaction.h; sourceTree = ""; }; 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryTransactionContext.h; path = Public/SentryTransactionContext.h; sourceTree = ""; }; 8ECC674325C23A1F000E2BF6 /* SentrySpanContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentrySpanContext.m; sourceTree = ""; }; 8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryTransaction.m; sourceTree = ""; }; @@ -1206,9 +1207,7 @@ 7B0002332477F52D0035FEF1 /* SentrySessionTests.swift */, 7B944FAF2469B46000A10721 /* TestClient.swift */, 7B0A54552523178700A71716 /* SentryScopeSwiftTests.swift */, - 8E70B0D125CB67F3002B3155 /* SentryTransaction+Private.h */, 8EC4CF4F25C3A0070093DEE9 /* SentrySpanContextTests.swift */, - 8E80D0C225BB29690029B150 /* SentryTransactionTests.swift */, 8E70B0FC25CB72BE002B3155 /* SentrySpanTests.swift */, 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */, ); @@ -1664,16 +1663,18 @@ children = ( 8ECC673925C23996000E2BF6 /* SentrySpanContext.h */, 8ECC674325C23A1F000E2BF6 /* SentrySpanContext.m */, - 8ECC673725C23995000E2BF6 /* SentrySpan.h */, + 8E4E7C7325DAAB49006AB9E2 /* SentrySpanProtocol.h */, + 8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */, 8EC3AE7925CA23B600E7591A /* SentrySpan.m */, 8ECC673825C23995000E2BF6 /* SentrySpanId.h */, 8ECC674525C23A20000E2BF6 /* SentrySpanId.m */, - 8ECC673A25C23996000E2BF6 /* SentryTransaction.h */, + 8E4E7C6B25DAAAFE006AB9E2 /* SentryTransaction.h */, 8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */, - 8EC3AE9025CA3B6700E7591A /* SentryTransaction+Private.h */, 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */, 8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */, 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */, + 8E4E7C7B25DAB287006AB9E2 /* SentryTracer.h */, + 8E4E7C8125DAB2A5006AB9E2 /* SentryTracer.m */, ); name = Transaction; sourceTree = ""; @@ -1685,12 +1686,11 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */, 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */, 8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */, - 8ECC673C25C23996000E2BF6 /* SentrySpan.h in Headers */, 8ECC673E25C23996000E2BF6 /* SentrySpanContext.h in Headers */, 8ECC674025C23996000E2BF6 /* SentryTransactionContext.h in Headers */, - 8ECC673F25C23996000E2BF6 /* SentryTransaction.h in Headers */, 63FE71BA20DA4C1100CDBAE8 /* SentryCrashInstallation+Private.h in Headers */, 63FE71AE20DA4C1100CDBAE8 /* SentryCrashInstallation.h in Headers */, 63FE70F120DA4C1000CDBAE8 /* SentryCrashMonitorType.h in Headers */, @@ -1709,6 +1709,7 @@ 639FCFA41EBC809A00778193 /* SentryStacktrace.h in Headers */, 63FE716320DA4C1100CDBAE8 /* SentryCrashDynamicLinker.h in Headers */, 639FCF981EBC7B9700778193 /* SentryEvent.h in Headers */, + 8E4E7C6D25DAAAFE006AB9E2 /* SentryTransaction.h in Headers */, 63FE715D20DA4C1100CDBAE8 /* SentryCrashSymbolicator.h in Headers */, 63FE718920DA4C1100CDBAE8 /* SentryCrash.h in Headers */, 63FE70FB20DA4C1000CDBAE8 /* SentryCrashMonitor_Zombie.h in Headers */, @@ -1744,6 +1745,7 @@ 63AA76971EB9C1C200D153DE /* Sentry.h in Headers */, 63FE711F20DA4C1000CDBAE8 /* SentryCrashObjC.h in Headers */, 7BC3936825B1AB3E004F03D3 /* SentryLevelMapper.h in Headers */, + 8E4E7C6E25DAAAFE006AB9E2 /* SentrySpan.h in Headers */, 631E6D331EBC679C00712345 /* SentryQueueableRequestManager.h in Headers */, 7B3398632459C14000BD9C96 /* SentryEnvelopeRateLimit.h in Headers */, 6304360A1EC0595B00C4D3FA /* NSData+SentryCompression.h in Headers */, @@ -1825,13 +1827,13 @@ 7B04A9AF24EAC02C00E710B1 /* SentryRetryAfterHeaderParser.h in Headers */, 7DC83100239826280043DD9A /* SentryIntegrationProtocol.h in Headers */, 7BA61CB9247BC57B00C130A8 /* SentryCrashDefaultBinaryImageProvider.h in Headers */, + 8E4E7C7D25DAB287006AB9E2 /* SentryTracer.h in Headers */, 7BC8523724588115005A70F0 /* SentryRateLimitCategory.h in Headers */, 63FE714B20DA4C1100CDBAE8 /* SentryCrashString.h in Headers */, 63FE715320DA4C1100CDBAE8 /* SentryCrashObjCApple.h in Headers */, 63FE710120DA4C1000CDBAE8 /* SentryCrashDate.h in Headers */, 7B4E24FC251C97B500060D68 /* SentrySession.h in Headers */, 7B7D872C2486480B00D2ECFF /* SentryStacktraceBuilder.h in Headers */, - 8EBAF05A25CCD066006FDD53 /* SentryTransaction+Private.h in Headers */, 6334314120AD9AE40077E581 /* SentryMechanism.h in Headers */, 7B610D642512399600B0B5D9 /* SentryHub+Private.h in Headers */, 639FCF9C1EBC7F9500778193 /* SentryThread.h in Headers */, @@ -2002,6 +2004,7 @@ 63FE712120DA4C1000CDBAE8 /* SentryCrashSymbolicator.c in Sources */, 63FE70D720DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.c in Sources */, 7BBD188B244841FB00427C76 /* SentryHttpDateParser.m in Sources */, + 8E4E7C8225DAB2A5006AB9E2 /* SentryTracer.m in Sources */, 15E0A8E5240C457D00F044E3 /* SentryEnvelope.m in Sources */, 8EC3AE7A25CA23B600E7591A /* SentrySpan.m in Sources */, 6360850E1ED2AFE100E8599E /* SentryBreadcrumb.m in Sources */, @@ -2162,7 +2165,6 @@ 7B0A542E2521C62400A71716 /* SentryFrameRemoverTests.swift in Sources */, 7BA61CCC247D14E600C130A8 /* SentryThreadInspectorTests.swift in Sources */, 15360CF52433C59B00112302 /* SentryInstallationTests.m in Sources */, - 8E80D0C325BB29690029B150 /* SentryTransactionTests.swift in Sources */, 7BB6550D253EEB3900887E87 /* SentryUserFeedbackTests.swift in Sources */, 7BBD18B7245180FF00427C76 /* SentryDsnTests.m in Sources */, 63FE721A20DA66EC00CDBAE8 /* SentryCrashSysCtl_Tests.m in Sources */, diff --git a/Sources/Sentry/Public/Sentry.h b/Sources/Sentry/Public/Sentry.h index 9bb308a08cb..c4376798d8f 100644 --- a/Sources/Sentry/Public/Sentry.h +++ b/Sources/Sentry/Public/Sentry.h @@ -31,13 +31,12 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[]; #import "SentrySdkInfo.h" #import "SentrySerializable.h" #import "SentrySession.h" -#import "SentrySpan.h" #import "SentrySpanContext.h" #import "SentrySpanId.h" +#import "SentrySpanProtocol.h" #import "SentrySpanStatus.h" #import "SentryStacktrace.h" #import "SentryThread.h" -#import "SentryTransaction.h" #import "SentryTransactionContext.h" #import "SentryUser.h" #import "SentryUserFeedback.h" diff --git a/Sources/Sentry/Public/SentryClient.h b/Sources/Sentry/Public/SentryClient.h index 145fa98ca8c..10ca9102962 100644 --- a/Sources/Sentry/Public/SentryClient.h +++ b/Sources/Sentry/Public/SentryClient.h @@ -41,9 +41,6 @@ SENTRY_NO_INIT - (SentryId *)captureEvent:(SentryEvent *)event withScope:(SentryScope *)scope NS_SWIFT_NAME(capture(event:scope:)); -- (SentryId *)captureTransaction:(SentryTransaction *)transaction - NS_SWIFT_NAME(capture(transaction:)); - /** * Captures an error event and sends it to Sentry. * diff --git a/Sources/Sentry/Public/SentryHub.h b/Sources/Sentry/Public/SentryHub.h index 06ac52d4f73..adecd0235fc 100644 --- a/Sources/Sentry/Public/SentryHub.h +++ b/Sources/Sentry/Public/SentryHub.h @@ -1,8 +1,9 @@ #import "SentryDefines.h" #import "SentryIntegrationProtocol.h" +#import "SentrySpanProtocol.h" @class SentryEvent, SentryClient, SentryScope, SentrySession, SentryUser, SentryBreadcrumb, - SentryId, SentryUserFeedback, SentryEnvelope, SentryTransactionContext, SentryTransaction; + SentryId, SentryUserFeedback, SentryEnvelope, SentryTransactionContext; NS_ASSUME_NONNULL_BEGIN @interface SentryHub : NSObject @@ -42,16 +43,6 @@ SENTRY_NO_INIT - (SentryId *)captureEvent:(SentryEvent *)event withScope:(SentryScope *)scope NS_SWIFT_NAME(capture(event:scope:)); -/** - * Captures a transaction and sends it to Sentry. - * - * @param transaction The transaction to send to Sentry. - * - * @return The SentryId of the transaction or SentryId.empty if the transaction is not sent. - */ -- (SentryId *)captureTransaction:(SentryTransaction *)transaction - NS_SWIFT_NAME(capture(transaction:)); - /** * Creates a transaction bound to the hub and returns the instance. * @@ -60,8 +51,8 @@ SENTRY_NO_INIT * * @return The created transaction. */ -- (SentryTransaction *)startTransactionWithName:(NSString *)name - operation:(NSString *)operation +- (id)startTransactionWithName:(NSString *)name + operation:(NSString *)operation NS_SWIFT_NAME(startTransaction(name:operation:)); /** @@ -71,7 +62,7 @@ SENTRY_NO_INIT * * @return The created transaction. */ -- (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext +- (id)startTransactionWithContext:(SentryTransactionContext *)transactionContext NS_SWIFT_NAME(startTransaction(transactionContext:)); /** diff --git a/Sources/Sentry/Public/SentrySDK.h b/Sources/Sentry/Public/SentrySDK.h index 973f62dedd3..3a091455edd 100644 --- a/Sources/Sentry/Public/SentrySDK.h +++ b/Sources/Sentry/Public/SentrySDK.h @@ -2,8 +2,10 @@ #import "SentryDefines.h" +@protocol SentrySpan; + @class SentryHub, SentryOptions, SentryEvent, SentryBreadcrumb, SentryScope, SentryUser, SentryId, - SentryUserFeedback, SentryTransaction, SentryTransactionContext; + SentryUserFeedback, SentryTransactionContext; NS_ASSUME_NONNULL_BEGIN @@ -89,8 +91,8 @@ SENTRY_NO_INIT * * @return The created transaction. */ -+ (SentryTransaction *)startTransactionWithName:(NSString *)name - operation:(NSString *)operation ++ (id)startTransactionWithName:(NSString *)name + operation:(NSString *)operation NS_SWIFT_NAME(startTransaction(name:operation:)); /** @@ -99,7 +101,7 @@ SENTRY_NO_INIT * @param transactionContext The transaction context. * @return The created transaction. */ -+ (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext ++ (id)startTransactionWithContext:(SentryTransactionContext *)transactionContext NS_SWIFT_NAME(startTransaction(transactionContext:)); /** diff --git a/Sources/Sentry/Public/SentrySpan.h b/Sources/Sentry/Public/SentrySpan.h deleted file mode 100644 index ad08156754e..00000000000 --- a/Sources/Sentry/Public/SentrySpan.h +++ /dev/null @@ -1,101 +0,0 @@ -#import "SentrySpanContext.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class SentryTransaction, SentrySpanId, SentryId, SentryHub; - -NS_SWIFT_NAME(Span) -@interface SentrySpan : SentrySpanContext -SENTRY_NO_INIT - -/** - * The timestamp of which the span ended. - */ -@property (nullable, nonatomic, strong) NSDate *timestamp; - -/** - * The start time of the span. - */ -@property (nullable, nonatomic, strong) NSDate *startTimestamp; - -/** - * An arbitrary mapping of additional metadata of the span. - */ -@property (nullable, readonly) NSDictionary *extras; - -/** - * Whether the span is finished. - */ -@property (readonly) BOOL isFinished; - -/** - * Init a SentrySpan with given transaction, traceId, parentSpanId and hub. - * - * @param transaction The transaction associated with this span. - * @param traceId Determines which trace the Span belongs to. - * @param parentId Id of a parent span. - * - * @return SentrySpan - */ -- (instancetype)initWithTransaction:(SentryTransaction *)transaction - operation:(NSString *)operation - traceId:(SentryId *)traceId - parentId:(SentrySpanId *)parentId; - -/* - Removed because SentrySpan requires a transaction - */ -- (instancetype)initWithOperation:(NSString *)operation Sampled:(BOOL)sampled NS_UNAVAILABLE; - -/* - Removed because SentrySpan requires a transaction - */ -- (instancetype)initWithTraceId:(SentryId *)traceId - spanId:(SentrySpanId *)spanId - parentId:(nullable SentrySpanId *)parentId - operation:(NSString *)operation - sampled:(BOOL)sampled NS_UNAVAILABLE; - -/** - * Starts a child span. - * - * @param operation Short code identifying the type of operation the span is measuring. - * - * @return SentrySpan - */ -- (SentrySpan *)startChildWithOperation:(NSString *)operation NS_SWIFT_NAME(startChild(operation:)); - -/** - * Starts a child span. - * - * @param operation Defines the child span operation. - * @param description Define the child span description. - * - * @return SentrySpan - */ -- (SentrySpan *)startChildWithOperation:(NSString *)operation - description:(nullable NSString *)description - NS_SWIFT_NAME(startChild(operation:description:)); - -/** - * Sets an extra. - */ - -- (void)setExtraValue:(nullable id)value forKey:(NSString *)key NS_SWIFT_NAME(setExtra(value:key:)); - -/** - * Finishes the span by setting the end time. - */ -- (void)finish; - -/** - * Finishes the span by setting the end time and span status. - * - * @param status The status of this span - * */ -- (void)finishWithStatus:(SentrySpanStatus)status NS_SWIFT_NAME(finish(status:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Public/SentrySpanContext.h b/Sources/Sentry/Public/SentrySpanContext.h index 35ed602ebdc..5b6f6faefe0 100644 --- a/Sources/Sentry/Public/SentrySpanContext.h +++ b/Sources/Sentry/Public/SentrySpanContext.h @@ -13,17 +13,17 @@ SENTRY_NO_INIT /** * Determines which trace the Span belongs to. */ -@property (nonatomic, strong) SentryId *traceId; +@property (nonatomic, readonly) SentryId *traceId; /** * Span id. */ -@property (nonatomic, strong) SentrySpanId *spanId; +@property (nonatomic, readonly) SentrySpanId *spanId; /** * Id of a parent span. */ -@property (nullable, nonatomic, strong) SentrySpanId *parentSpanId; +@property (nullable, nonatomic, readonly) SentrySpanId *parentSpanId; /** * If trace is sampled. diff --git a/Sources/Sentry/Public/SentrySpanProtocol.h b/Sources/Sentry/Public/SentrySpanProtocol.h new file mode 100644 index 00000000000..0b5b68a6776 --- /dev/null +++ b/Sources/Sentry/Public/SentrySpanProtocol.h @@ -0,0 +1,87 @@ +#import "SentryDefines.h" +#import "SentrySerializable.h" +#import "SentrySpanContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@class SentrySpanId, SentryId; + +NS_SWIFT_NAME(Span) +@protocol SentrySpan + +/** + *Span name. + */ +@property (nonatomic, copy) NSString *name; + +/** + * The context information of the span. + */ +@property (nonatomic, readonly) SentrySpanContext *context; + +/** + * The timestamp of which the span ended. + */ +@property (nullable, nonatomic, strong) NSDate *timestamp; + +/** + * The start time of the span. + */ +@property (nullable, nonatomic, strong) NSDate *startTimestamp; + +/** + * An arbitrary mapping of additional metadata of the span. + */ +@property (nullable, readonly) NSDictionary *data; + +/** + * Whether the span is finished. + */ +@property (readonly) BOOL isFinished; + +/** + * Starts a child span. + * + * @param name Child span name. + * @param operation Short code identifying the type of operation the span is measuring. + * + * @return SentrySpan + */ +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + NS_SWIFT_NAME(startChild(name:operation:)); + +/** + * Starts a child span. + * + * @param name Child span name. + * @param operation Defines the child span operation. + * @param description Define the child span description. + * + * @return SentrySpan + */ +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description + NS_SWIFT_NAME(startChild(name:operation:description:)); + +/** + * Sets an extra. + */ +- (void)setDataValue:(nullable id)value forKey:(NSString *)key NS_SWIFT_NAME(setExtra(value:key:)); + +/** + * Finishes the span by setting the end time. + */ +- (void)finish; + +/** + * Finishes the span by setting the end time and span status. + * + * @param status The status of this span + * */ +- (void)finishWithStatus:(SentrySpanStatus)status NS_SWIFT_NAME(finish(status:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Public/SentryTransaction.h b/Sources/Sentry/Public/SentryTransaction.h deleted file mode 100644 index 5a241862b00..00000000000 --- a/Sources/Sentry/Public/SentryTransaction.h +++ /dev/null @@ -1,105 +0,0 @@ -#import - -NS_ASSUME_NONNULL_BEGIN - -@class SentrySpanContext, SentryTransactionContext, SentryHub, SentrySpan; - -NS_SWIFT_NAME(Transaction) -@interface SentryTransaction : SentryEvent -SENTRY_NO_INIT - -/** - * Transaction span id - */ -@property (readonly) SentrySpanId *spanId; - -/** - * Transaction trace id - */ -@property (readonly) SentryId *traceId; - -/** - * If transaction is sampled - */ -@property (readonly) BOOL isSampled; - -/** - * Longer description of the span's operation, which uniquely identifies the span but is - * consistent across instances of the span. - */ -@property (nullable, nonatomic, copy) NSString *spanDescription; - -/** - * Short code identifying the type of operation the transaction is measuring. - */ -@property (nullable, nonatomic, copy) NSString *operation; - -/** - * Describes the status of the Transaction - */ -@property (nonatomic) enum SentrySpanStatus status; - -/** - * Init a SentryTransaction with given name and set other fields by default - * - * @param name Transaction name. - * @param operation Short code identifying the type of operation the transaction is measuring. - * - * @return SentryTransaction - */ -- (instancetype)initWithName:(NSString *)name operation:(NSString *)operation; - -/** - * Init a SentryTransaction with given transaction context and hub and set other fields by default - * - * @param transactionContext Transaction context - * @param hub A hub to bind this transaction - * - * @return SentryTransaction - */ - -- (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext - hub:(nullable SentryHub *)hub; - -/** - * Init a SentryTransaction with given name, span context and hub and set other fields by default - * - * @param name Transaction name - * @param spanContext Span context - * @param hub A hub to bind this transaction - * - * @return SentryTransaction - */ -- (instancetype)initWithName:(NSString *)name - spanContext:(SentrySpanContext *)spanContext - hub:(nullable SentryHub *)hub; - -/** - * Finishes the transaction by setting the end time and capturing the transaction with binded hub. - */ -- (void)finish; - -/** - * Starts a child span. - * - * @param operation Defines the child span operation. - * - * @return SentrySpan - */ -- (SentrySpan *)startChildWithOperation:(NSString *)operation NS_SWIFT_NAME(startChild(operation:)); - -/** - * Starts a child span. - * - * @param operation Defines the child span operation. - * @param description Define the child span description. - * - * @return SentrySpan - */ -- (SentrySpan *)startChildWithOperation:(NSString *)operation - description:(nullable NSString *)description - NS_SWIFT_NAME(startChild(operation:description:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index 885b8bb7c35..eadda4b7703 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -248,11 +248,6 @@ - (SentryId *)sendEvent:(SentryEvent *)event return SentryId.empty; } -- (SentryId *)captureTransaction:(SentryTransaction *)transaction -{ - return [self captureEvent:transaction withScope:[[SentryScope alloc] init]]; -} - - (SentryId *)sendEvent:(SentryEvent *)event withSession:(SentrySession *)session withScope:(SentryScope *)scope diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index c55eefec3c0..bd0a5f0aebe 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -10,7 +10,8 @@ #import "SentrySDK.h" #import "SentryScope.h" #import "SentrySerialization.h" -#import "SentryTransaction.h" +#import "SentryTracer.h" +#import "SentryTransactionContext.h" @interface SentryHub () @@ -227,22 +228,16 @@ - (SentryId *)captureEvent:(SentryEvent *)event withScope:(SentryScope *)scope return SentryId.empty; } -- (SentryId *)captureTransaction:(SentryTransaction *)transaction +- (id)startTransactionWithName:(NSString *)name operation:(NSString *)operation { - return [self.client captureTransaction:transaction]; + return [self + startTransactionWithContext:[[SentryTransactionContext alloc] initWithName:name + operation:operation]]; } -- (SentryTransaction *)startTransactionWithName:(NSString *)name operation:(NSString *)operation +- (id)startTransactionWithContext:(SentryTransactionContext *)transactionContext { - return [[SentryTransaction alloc] - initWithName:name - spanContext:[[SentryTransactionContext alloc] initWithName:name operation:operation] - hub:self]; -} - -- (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext -{ - return [[SentryTransaction alloc] initWithTransactionContext:transactionContext hub:self]; + return [[SentryTracer alloc] initWithTransactionContext:transactionContext hub:self]; } - (SentryId *)captureMessage:(NSString *)message diff --git a/Sources/Sentry/SentrySDK.m b/Sources/Sentry/SentrySDK.m index 6aa0e4a4299..75e036dc9d6 100644 --- a/Sources/Sentry/SentrySDK.m +++ b/Sources/Sentry/SentrySDK.m @@ -111,12 +111,12 @@ + (SentryId *)captureEvent:(SentryEvent *)event withScope:(SentryScope *)scope return [SentrySDK.currentHub captureEvent:event withScope:scope]; } -+ (SentryTransaction *)startTransactionWithName:(NSString *)name operation:(NSString *)operation ++ (id)startTransactionWithName:(NSString *)name operation:(NSString *)operation { return [SentrySDK.currentHub startTransactionWithName:name operation:operation]; } -+ (SentryTransaction *)startTransactionWithContext:(SentryTransactionContext *)transactionContext ++ (id)startTransactionWithContext:(SentryTransactionContext *)transactionContext { return [SentrySDK.currentHub startTransactionWithContext:transactionContext]; } diff --git a/Sources/Sentry/SentrySpan.m b/Sources/Sentry/SentrySpan.m index 923311aa4d0..81a6a319de9 100644 --- a/Sources/Sentry/SentrySpan.m +++ b/Sources/Sentry/SentrySpan.m @@ -1,63 +1,66 @@ +#import "SentrySpan.h" #import "NSDate+SentryExtras.h" #import "SentryCurrentDate.h" -#import "SentryTransaction+Private.h" +#import "SentryTracer.h" #import NS_ASSUME_NONNULL_BEGIN @interface -SentrySpan () { - NSMutableDictionary *_extras; -} +SentrySpan () -/** - * The transaction associated with this span. - */ -@property (nonatomic) SentryTransaction *transaction; +@property (nonatomic) SentryTracer *tracer; @end -@implementation SentrySpan +@implementation SentrySpan { + NSMutableDictionary *_extras; +} -- (instancetype)initWithTransaction:(SentryTransaction *)transaction - operation:(NSString *)operation - traceId:(SentryId *)traceId - parentId:(SentrySpanId *)parentId +- (instancetype)initWithTracer:(SentryTracer *)tracer + name:(NSString *)name + context:(SentrySpanContext *)context { - if ([super initWithTraceId:traceId - spanId:[[SentrySpanId alloc] init] - parentId:parentId - operation:operation - sampled:transaction.isSampled]) { - self.transaction = transaction; + if ([self initWithName:name context:context]) { + self.tracer = tracer; + } + return self; +} + +- (instancetype)initWithName:(NSString *)name context:(SentrySpanContext *)context +{ + if ([super init]) { + self.name = name; + _context = context; self.startTimestamp = [SentryCurrentDate date]; _extras = [[NSMutableDictionary alloc] init]; } - return self; } -- (SentrySpan *)startChildWithOperation:(NSString *)operation +- (id)startChildWithName:(NSString *)name operation:(NSString *)operation { - return [self startChildWithOperation:operation description:nil]; + return [self startChildWithName:name operation:operation description:nil]; } -- (SentrySpan *)startChildWithOperation:(NSString *)operation - description:(nullable NSString *)description +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description { - return [self.transaction startChildWithParentId:[self spanId] - operation:operation - description:description]; + return [self.tracer startChildWithParentId:[self.context spanId] + name:name + operation:operation + description:description]; } -- (void)setExtraValue:(nullable NSString *)value forKey:(NSString *)key +- (void)setDataValue:(nullable NSString *)value forKey:(NSString *)key { @synchronized(_extras) { [_extras setValue:value forKey:key]; } } -- (nullable NSDictionary *)extras +- (nullable NSDictionary *)data { return _extras; } @@ -74,20 +77,23 @@ - (void)finish - (void)finishWithStatus:(SentrySpanStatus)status { - self.status = status; + self.context.status = status; [self finish]; } - (NSDictionary *)serialize { NSMutableDictionary *mutableDictionary = - [[NSMutableDictionary alloc] initWithDictionary:[super serialize]]; + [[NSMutableDictionary alloc] initWithDictionary:[self.context serialize]]; + [mutableDictionary setValue:[self.timestamp sentry_toIso8601String] forKey:@"timestamp"]; [mutableDictionary setValue:[self.startTimestamp sentry_toIso8601String] forKey:@"start_timestamp"]; if (_extras != nil) { - [mutableDictionary setValue:_extras.copy forKey:@"data"]; + @synchronized(_extras) { + [mutableDictionary setValue:_extras.copy forKey:@"data"]; + } } return mutableDictionary; diff --git a/Sources/Sentry/SentrySpanContext.m b/Sources/Sentry/SentrySpanContext.m index c1c374f606e..3e53be84128 100644 --- a/Sources/Sentry/SentrySpanContext.m +++ b/Sources/Sentry/SentrySpanContext.m @@ -2,6 +2,8 @@ #import "SentryId.h" #import "SentrySpanId.h" +NS_ASSUME_NONNULL_BEGIN + @interface SentrySpanContext () { NSMutableDictionary *_tags; @@ -32,9 +34,9 @@ - (instancetype)initWithTraceId:(SentryId *)traceId sampled:(BOOL)sampled { if (self = [super init]) { - self.traceId = traceId; - self.spanId = spanId; - self.parentSpanId = parentId; + _traceId = traceId; + _spanId = spanId; + _parentSpanId = parentId; self.sampled = sampled; self.operation = operation; self.status = kSentrySpanStatusUndefined; @@ -94,3 +96,5 @@ - (void)removeTagForKey:(NSString *)key return mutabledictionary; } @end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m new file mode 100644 index 00000000000..4a4bd3fb374 --- /dev/null +++ b/Sources/Sentry/SentryTracer.m @@ -0,0 +1,132 @@ +#import "SentryTracer.h" +#import "SentrySpan.h" +#import "SentrySpanContext.h" +#import "SentrySpanId.h" +#import "SentryTransaction.h" + +@implementation SentryTracer { + SentrySpan *_rootSpan; + NSMutableArray> *_spans; + SentryHub *_hub; +} + +- (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext + hub:(nullable SentryHub *)hub +{ + if ([super init]) { + _rootSpan = [[SentrySpan alloc] initWithTracer:self + name:transactionContext.name + context:transactionContext]; + _spans = [[NSMutableArray alloc] init]; + _hub = hub; + } + + return self; +} + +- (id)startChildWithName:(NSString *)name operation:(NSString *)operation +{ + return [_rootSpan startChildWithName:name operation:operation]; +} + +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description +{ + return [_rootSpan startChildWithName:name operation:operation description:description]; +} + +- (id)startChildWithParentId:(SentrySpanId *)parentId + name:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description +{ + SentrySpanContext *context = + [[SentrySpanContext alloc] initWithTraceId:_rootSpan.context.traceId + spanId:[[SentrySpanId alloc] init] + parentId:parentId + operation:operation + sampled:_rootSpan.context.sampled]; + context.spanDescription = description; + + SentrySpan *span = [[SentrySpan alloc] initWithName:name context:context]; + @synchronized(_spans) { + [_spans addObject:span]; + } + return span; +} + +- (NSString *)name +{ + return _rootSpan.name; +} + +- (SentrySpanContext *)context +{ + return _rootSpan.context; +} + +- (NSDate *)timestamp +{ + return _rootSpan.timestamp; +} + +- (void)setTimestamp:(NSDate *)timestamp +{ + _rootSpan.timestamp = timestamp; +} + +- (NSDate *)startTimestamp +{ + return _rootSpan.startTimestamp; +} + +- (void)setStartTimestamp:(NSDate *)startTimestamp +{ + _rootSpan.startTimestamp = startTimestamp; +} + +- (NSDictionary *)data +{ + return _rootSpan.data; +} + +- (BOOL)isFinished +{ + return _rootSpan.isFinished; +} + +- (void)setDataValue:(nullable id)value forKey:(NSString *)key +{ + [_rootSpan setDataValue:value forKey:key]; +} + +- (void)finish +{ + [_rootSpan finish]; + [self captureTransaction]; +} + +- (void)finishWithStatus:(SentrySpanStatus)status +{ + [_rootSpan finishWithStatus:status]; + [self captureTransaction]; +} + +- (void)captureTransaction +{ + NSArray *spans; + @synchronized(_spans) { + spans = [[NSArray alloc] initWithArray:_spans]; + } + + SentryTransaction *transaction = [[SentryTransaction alloc] initWithTrace:self childs:spans]; + [_hub captureEvent:transaction]; +} + +- (NSDictionary *)serialize +{ + return [_rootSpan serialize]; +} + +@end diff --git a/Sources/Sentry/SentryTransaction.m b/Sources/Sentry/SentryTransaction.m index 43d4ce90869..cd9e13d3d6c 100644 --- a/Sources/Sentry/SentryTransaction.m +++ b/Sources/Sentry/SentryTransaction.m @@ -1,159 +1,30 @@ #import "SentryTransaction.h" -#import "NSDate+SentryExtras.h" -#import "NSDictionary+SentrySanitize.h" -#import "SentryCurrentDate.h" -#import "SentryHub.h" -#import "SentryId.h" -#import "SentrySpanContext.h" -#import "SentrySpanId.h" -#import "SentryTransactionContext.h" -@interface - -SentryTransaction () - -/** - * This transaction span context. - */ -@property (nonatomic) SentrySpanContext *spanContext; - -/** - * A hub this transaction is attached to. - */ -@property (nullable, nonatomic) SentryHub *hub; - -/** - * A list of child spans. - */ -@property (nonatomic) NSMutableArray *spans; - -@end - -@implementation SentryTransaction - -- (instancetype)init -{ - self = [super init]; - if (self) { - self.eventId = [[SentryId alloc] init]; - self.type = @"transaction"; - self.spanContext = [[SentrySpanContext alloc] init]; - } - return self; +@implementation SentryTransaction { + id _trace; + NSArray> *_spans; } -- (instancetype)initWithName:(NSString *)name operation:(NSString *)operation +- (instancetype)initWithTrace:(id)trace childs:(NSArray> *)childs { - return [self initWithName:name - spanContext:[[SentrySpanContext alloc] initWithOperation:operation] - hub:nil]; -} - -- (instancetype)initWithTransactionContext:(SentryTransactionContext *)context hub:(SentryHub *)hub -{ - return [self initWithName:context.name spanContext:context hub:hub]; -} - -- (instancetype)initWithName:(NSString *)name - spanContext:(nonnull SentrySpanContext *)spanContext - hub:(SentryHub *)hub -{ - if ([self init]) { - self.transaction = name; - self.startTimestamp = [SentryCurrentDate date]; - self.hub = hub; - self.spanContext = spanContext; - self.spans = [[NSMutableArray alloc] init]; + if ([super init]) { + self.timestamp = trace.timestamp; + self.startTimestamp = trace.startTimestamp; + _trace = trace; + _spans = childs; + self.transaction = trace.name; + self.type = SentryEnvelopeItemTypeTransaction; } return self; } -- (void)finish -{ - self.timestamp = [SentryCurrentDate date]; - [self.hub captureTransaction:self]; -} - -- (SentrySpanId *)spanId -{ - return self.spanContext.spanId; -} - -- (SentryId *)traceId -{ - return self.spanContext.traceId; -} - -- (BOOL)isSampled -{ - return self.spanContext.sampled; -} - -- (NSString *)spanDescription -{ - return self.spanContext.spanDescription; -} - -- (void)setSpanDescription:(NSString *)spanDescription -{ - [_spanContext setSpanDescription:spanDescription]; -} - -- (SentrySpanStatus)status -{ - return self.spanContext.status; -} - -- (void)setStatus:(SentrySpanStatus)status -{ - [self.spanContext setStatus:status]; -} - -- (NSString *)operation -{ - return self.spanContext.operation; -} - -- (void)setOperation:(NSString *)operation -{ - [self.spanContext setOperation:operation]; -} - -- (SentrySpan *)startChildWithOperation:(NSString *)operation -{ - return [self startChildWithOperation:operation description:nil]; -} - -- (SentrySpan *)startChildWithOperation:(NSString *)operation - description:(nullable NSString *)description -{ - return [self startChildWithParentId:self.spanId operation:operation description:description]; -} - -- (SentrySpan *)startChildWithParentId:(SentrySpanId *)parentId - operation:(NSString *)operation - description:(nullable NSString *)description -{ - SentrySpan *span = [[SentrySpan alloc] initWithTransaction:self - operation:operation - traceId:self.traceId - parentId:parentId]; - - span.spanDescription = description; - span.sampled = self.isSampled; - @synchronized(self.spans) { - [self.spans addObject:span]; - } - return span; -} - - (NSDictionary *)serialize { NSMutableDictionary *serializedData = [[NSMutableDictionary alloc] initWithDictionary:[super serialize]]; NSMutableArray *spans = [[NSMutableArray alloc] init]; - for (SentrySpan *span in self.spans) { + for (id span in _spans) { [spans addObject:[span serialize]]; } serializedData[@"spans"] = spans; @@ -162,7 +33,7 @@ - (SentrySpan *)startChildWithParentId:(SentrySpanId *)parentId if (serializedData[@"contexts"] != nil) { [mutableContext addEntriesFromDictionary:serializedData[@"contexts"]]; } - mutableContext[@"trace"] = [_spanContext serialize]; + mutableContext[@"trace"] = [_trace serialize]; [serializedData setValue:mutableContext forKey:@"contexts"]; return serializedData; diff --git a/Sources/Sentry/include/SentrySpan.h b/Sources/Sentry/include/SentrySpan.h new file mode 100644 index 00000000000..e874615521d --- /dev/null +++ b/Sources/Sentry/include/SentrySpan.h @@ -0,0 +1,113 @@ +#import "SentryDefines.h" +#import "SentrySerializable.h" +#import "SentrySpanContext.h" +#import "SentrySpanProtocol.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SentryTracer; + +@interface SentrySpan : NSObject +SENTRY_NO_INIT + +/** + *Span name. + */ +@property (nonatomic, copy) NSString *name; + +/** + * The context information of the span. + */ +@property (nonatomic, readonly) SentrySpanContext *context; + +/** + * The timestamp of which the span ended. + */ +@property (nullable, nonatomic, strong) NSDate *timestamp; + +/** + * The start time of the span. + */ +@property (nullable, nonatomic, strong) NSDate *startTimestamp; + +/** + * An arbitrary mapping of additional metadata of the span. + */ +@property (nullable, readonly) NSDictionary *data; + +/** + * Whether the span is finished. + */ +@property (readonly) BOOL isFinished; + +/** + * Init a SentrySpan with given tracer, name and context. + * + * @param tracer The tracer responsable for this span. + * @param name The name of the span. + * @param context This span context information. + * + * @return SentrySpan + */ +- (instancetype)initWithTracer:(SentryTracer *)tracer + name:(NSString *)name + context:(SentrySpanContext *)context; + +/** + * Init a SentrySpan with given name and context. + * + * @param name The name of the span. + * @param context This span context information. + * + * @return SentrySpan + */ +- (instancetype)initWithName:(NSString *)name context:(SentrySpanContext *)context; + +/** + * Starts a child span. + * + * @param name Child span name. + * @param operation Short code identifying the type of operation the span is measuring. + * + * @return SentrySpan + */ +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + NS_SWIFT_NAME(startChild(name:operation:)); + +/** + * Starts a child span. + * + * @param name Child span name. + * @param operation Defines the child span operation. + * @param description Define the child span description. + * + * @return SentrySpan + */ +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description + NS_SWIFT_NAME(startChild(name:operation:description:)); + +/** + * Sets an extra. + */ + +- (void)setDataValue:(nullable id)value forKey:(NSString *)key NS_SWIFT_NAME(setExtra(value:key:)); + +/** + * Finishes the span by setting the end time. + */ +- (void)finish; + +/** + * Finishes the span by setting the end time and span status. + * + * @param status The status of this span + * */ +- (void)finishWithStatus:(SentrySpanStatus)status NS_SWIFT_NAME(finish(status:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryTracer.h b/Sources/Sentry/include/SentryTracer.h new file mode 100644 index 00000000000..69760c7f0cb --- /dev/null +++ b/Sources/Sentry/include/SentryTracer.h @@ -0,0 +1,110 @@ +#import "SentrySpanProtocol.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SentryHub, SentryTransactionContext; + +@interface SentryTracer : NSObject + +/** + *Span name. + */ +@property (nonatomic, copy) NSString *name; + +/** + * The context information of the span. + */ +@property (nonatomic, readonly) SentrySpanContext *context; + +/** + * The timestamp of which the span ended. + */ +@property (nullable, nonatomic, strong) NSDate *timestamp; + +/** + * The start time of the span. + */ +@property (nullable, nonatomic, strong) NSDate *startTimestamp; + +/** + * An arbitrary mapping of additional metadata of the span. + */ +@property (nullable, readonly) NSDictionary *data; + +/** + * Whether the span is finished. + */ +@property (readonly) BOOL isFinished; + +/** + * Init a SentryTransaction with given transaction context and hub and set other fields by default + * + * @param transactionContext Transaction context + * @param hub A hub to bind this transaction + * + * @return SentryTransaction + */ +- (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext + hub:(nullable SentryHub *)hub; + +/** + * Starts a child span. + * + * @param operation Short code identifying the type of operation the span is measuring. + * + * @return SentrySpan + */ +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + NS_SWIFT_NAME(startChild(name:operation:)); + +/** + * Starts a child span. + * + * @param operation Defines the child span operation. + * @param description Define the child span description. + * + * @return SentrySpan + */ +- (id)startChildWithName:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description + NS_SWIFT_NAME(startChild(name:operation:description:)); + +/** + * Starts a child span. + * + * @param parentId The child span parent id. + * @param name Defines the child span name + * @param operation The child span operation. + * @param description The child span description. + * + * @return SentrySpan + */ +- (id)startChildWithParentId:(SentrySpanId *)parentId + name:(NSString *)name + operation:(NSString *)operation + description:(nullable NSString *)description + NS_SWIFT_NAME(startChild(parentId:name:operation:description:)); + +/** + * Sets an extra. + */ +- (void)setDataValue:(nullable id)value forKey:(NSString *)key NS_SWIFT_NAME(setExtra(value:key:)); + +/** + * Finishes the transaction by setting the end time and capturing the transaction with binded hub. + */ +- (void)finish; + +/** + * Finishes the span by setting the end time and span status. + * + * @param status The status of this span + * */ +- (void)finishWithStatus:(SentrySpanStatus)status NS_SWIFT_NAME(finish(status:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryTransaction+Private.h b/Sources/Sentry/include/SentryTransaction+Private.h deleted file mode 100644 index 84fa76af498..00000000000 --- a/Sources/Sentry/include/SentryTransaction+Private.h +++ /dev/null @@ -1,28 +0,0 @@ -#import "SentryTransaction.h" - -@class SentrySpanId, SentrySpan; - -NS_ASSUME_NONNULL_BEGIN - -/** - * SentryTransaction SDK internal methods. - * This should not be in the public API. - */ -@interface SentryTransaction (Private) - -/** - * Starts a child span. - * - * @param parentId The child span parent id. - * @param operation The child span operation. - * @param description The child span description. - * - * @return SentrySpan - */ -- (SentrySpan *)startChildWithParentId:(SentrySpanId *)parentId - operation:(NSString *)operation - description:(nullable NSString *)description; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryTransaction.h b/Sources/Sentry/include/SentryTransaction.h new file mode 100644 index 00000000000..eec59af67f3 --- /dev/null +++ b/Sources/Sentry/include/SentryTransaction.h @@ -0,0 +1,13 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(Transaction) +@interface SentryTransaction : SentryEvent +SENTRY_NO_INIT + +- (instancetype)initWithTrace:(id)trace childs:(NSArray> *)childs; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 9be6cb6a6c1..a903e9b78db 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -157,17 +157,6 @@ class SentryClientTest: XCTestCase { } } } - - func testCaptureTransaction() { - let transaction = Transaction(name: "Some Transaction", operation: "Some Operation") - let eventId = fixture.getSut().capture(transaction: transaction) - - eventId.assertIsNotEmpty() - - assertLastSentEvent { actual in - XCTAssertEqual(transaction.transaction, actual.transaction) - } - } func testCaptureEventWithException() { let event = Event() diff --git a/Tests/SentryTests/SentrySpanTests.swift b/Tests/SentryTests/SentrySpanTests.swift index b0fcf96379f..289c98db06c 100644 --- a/Tests/SentryTests/SentrySpanTests.swift +++ b/Tests/SentryTests/SentrySpanTests.swift @@ -1,18 +1,30 @@ import XCTest class SentrySpanTests: XCTestCase { - private class Fixture { let someTransaction = "Some Transaction" let someOperation = "Some Operation" let someDescription = "Some Description" let extraKey = "extra_key" let extraValue = "extra_value" + let spanName = "Span Name" + let options: Options + + init() { + options = Options() + options.dsn = TestConstants.dsnAsString + options.environment = "test" + } func getSut() -> Span { - let transaction = Transaction(name: someTransaction, operation: someOperation) - return Span(transaction: transaction, operation: someOperation, trace: SentryId(), parentId: transaction.spanId) + return getSut(client: TestClient(options: options)!) + } + + func getSut(client: Client) -> Span { + let hub = SentryHub(client: client, andScope: nil, andCrashAdapter: TestSentryCrashWrapper()) + return hub.startTransaction(name: someTransaction, operation: someOperation) } + } private var fixture: Fixture! @@ -32,13 +44,21 @@ class SentrySpanTests: XCTestCase { } func testFinish() { - let span = fixture.getSut() + let client = TestClient(options: fixture.options)! + let span = fixture.getSut(client: client) span.finish() XCTAssertEqual(span.startTimestamp, TestData.timestamp) XCTAssertEqual(span.timestamp, TestData.timestamp) XCTAssertTrue(span.isFinished) + + let lastEvent = client.captureEventWithScopeArguments[0].event + XCTAssertEqual(lastEvent.transaction, fixture.someTransaction) + XCTAssertEqual(lastEvent.timestamp, TestData.timestamp) + XCTAssertEqual(lastEvent.startTimestamp, TestData.timestamp) + XCTAssertEqual(lastEvent.type, SentryEnvelopeItemTypeTransaction) + } func testFinishWithStatus() { @@ -47,27 +67,43 @@ class SentrySpanTests: XCTestCase { XCTAssertEqual(span.startTimestamp, TestData.timestamp) XCTAssertEqual(span.timestamp, TestData.timestamp) - XCTAssertEqual(span.status, .ok) + XCTAssertEqual(span.context.status, .ok) XCTAssertTrue(span.isFinished) } - func testStartChildWithOperation() { + func testFinishWithChild() { + let client = TestClient(options: fixture.options)! + let span = fixture.getSut(client: client) + let childSpan = span.startChild(name: fixture.spanName, operation: fixture.someOperation) + + span.finish() + let lastEvent = client.captureEventWithScopeArguments[0].event + let serializedData = lastEvent.serialize() + + let spans = serializedData["spans"] as! [Any] + let serializedChild = spans[0] as! [String: Any] + + XCTAssertEqual(serializedChild["span_id"] as? String, childSpan.context.spanId.sentrySpanIdString) + XCTAssertEqual(serializedChild["parent_span_id"] as? String, span.context.spanId.sentrySpanIdString) + } + + func testStartChildWithNameOperation() { let span = fixture.getSut() - let childSpan = span.startChild(operation: fixture.someOperation) - XCTAssertEqual(childSpan.parentSpanId, span.spanId) - XCTAssertEqual(childSpan.operation, fixture.someOperation) - XCTAssertNil(childSpan.spanDescription) + let childSpan = span.startChild(name: fixture.spanName, operation: fixture.someOperation) + XCTAssertEqual(childSpan.context.parentSpanId, span.context.spanId) + XCTAssertEqual(childSpan.context.operation, fixture.someOperation) + XCTAssertNil(childSpan.context.spanDescription) } - func testStartChildWithOperationAndDescription() { + func testStartChildWithNameOperationAndDescription() { let span = fixture.getSut() - let childSpan = span.startChild(operation: fixture.someOperation, description: fixture.someDescription) + let childSpan = span.startChild(name: fixture.spanName, operation: fixture.someOperation, description: fixture.someDescription) - XCTAssertEqual(childSpan.parentSpanId, span.spanId) - XCTAssertEqual(childSpan.operation, fixture.someOperation) - XCTAssertEqual(childSpan.spanDescription, fixture.someDescription) + XCTAssertEqual(childSpan.context.parentSpanId, span.context.spanId) + XCTAssertEqual(childSpan.context.operation, fixture.someOperation) + XCTAssertEqual(childSpan.context.spanDescription, fixture.someDescription) } func testSetExtras() { @@ -75,8 +111,8 @@ class SentrySpanTests: XCTestCase { span.setExtra(value: fixture.extraValue, key: fixture.extraKey) - XCTAssertEqual(span.extras!.count, 1) - XCTAssertEqual(span.extras![fixture.extraKey] as! String, fixture.extraValue) + XCTAssertEqual(span.data!.count, 1) + XCTAssertEqual(span.data![fixture.extraKey] as! String, fixture.extraValue) } func testSerialization() { @@ -84,10 +120,14 @@ class SentrySpanTests: XCTestCase { span.setExtra(value: fixture.extraValue, key: fixture.extraKey) span.finish() - + let serialization = span.serialize() + XCTAssertEqual(serialization["span_id"] as? String, span.context.spanId.sentrySpanIdString) + XCTAssertEqual(serialization["trace_id"] as? String, span.context.traceId.sentryIdString) XCTAssertEqual(serialization["timestamp"] as? String, TestData.timestampAs8601String) XCTAssertEqual(serialization["start_timestamp"] as? String, TestData.timestampAs8601String) + XCTAssertEqual(serialization["type"] as? String, SpanContext.type) + XCTAssertEqual(serialization["sampled"] as? String, "false") XCTAssertNotNil(serialization["data"]) XCTAssertEqual((serialization["data"] as! Dictionary)[fixture.extraKey], fixture.extraValue) } @@ -121,6 +161,6 @@ class SentrySpanTests: XCTestCase { queue.activate() group.wait() - XCTAssertEqual(span.extras!.count, outerLoop * innerLoop) + XCTAssertEqual(span.data!.count, outerLoop * innerLoop) } } diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 2d05f723a75..9f6cbf23253 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -64,7 +64,6 @@ #import "SentrySystemEventsBreadcrumbs.h" #import "SentryTestIntegration.h" #import "SentryThreadInspector.h" -#import "SentryTransaction+Private.h" #import "SentryTransport.h" #import "SentryTransportFactory.h" #import "SentryUserFeedback.h" diff --git a/Tests/SentryTests/SentryTransaction+Private.h b/Tests/SentryTests/SentryTransaction+Private.h deleted file mode 100644 index 31c37ed135e..00000000000 --- a/Tests/SentryTests/SentryTransaction+Private.h +++ /dev/null @@ -1,11 +0,0 @@ -#import "SentryTransaction.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface SentryTransaction (Private) - -@property (nonatomic) NSMutableArray *spans; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/SentryTransactionTests.swift b/Tests/SentryTests/SentryTransactionTests.swift deleted file mode 100644 index e2a469295a8..00000000000 --- a/Tests/SentryTests/SentryTransactionTests.swift +++ /dev/null @@ -1,157 +0,0 @@ -import XCTest - -class SentryTransactionTest: XCTestCase { - let someTransactionName = "Some Transaction" - let someOperation = "Some Operation" - - func testInitWithName() { - let transaction = Transaction(name: someTransactionName, operation: someOperation) - - XCTAssertNotNil(transaction.startTimestamp) - XCTAssertNil(transaction.timestamp) - XCTAssertEqual(transaction.transaction, someTransactionName) - } - - func testInitWithTransactionContext() { - let someOperation = "Some Operation" - let someSpanDescription = "Some Span Description" - - let context = TransactionContext(name: someTransactionName, operation: someOperation, trace: SentryId(), spanId: SpanId(), parentSpanId: SpanId(), parentSampled: true) - context.operation = someOperation - context.status = .ok - context.sampled = true - context.spanDescription = someSpanDescription - - let transaction = Transaction(transactionContext: context, hub: nil) - XCTAssertNotNil(transaction.startTimestamp) - XCTAssertNil(transaction.timestamp) - XCTAssertEqual(transaction.transaction, someTransactionName) - XCTAssertEqual(transaction.traceId, context.traceId) - XCTAssertEqual(transaction.spanId, context.spanId) - XCTAssertEqual(transaction.operation, someOperation) - XCTAssertEqual(transaction.status, SentrySpanStatus.ok) - XCTAssertTrue(transaction.isSampled) - XCTAssertEqual(transaction.spanDescription, someSpanDescription) - } - - func testIndirectManipulationOfContext() { - let someOperation = "Some Operation" - let spanDescription = "Span Description" - - let context = TransactionContext(name: someTransactionName, operation: someOperation) - - let transaction = Transaction(transactionContext: context, hub: nil) - transaction.spanDescription = spanDescription - transaction.operation = someOperation - transaction.status = .ok - - XCTAssertEqual(context.spanDescription, spanDescription) - XCTAssertEqual(context.operation, someOperation) - XCTAssertEqual(context.status, SentrySpanStatus.ok) - } - - func testInitWithNameAndContext() { - let context = SpanContext(operation: someOperation) - context.operation = someOperation - context.status = .ok - - let transaction = Transaction(name: someTransactionName, spanContext: context, hub: nil) - XCTAssertNotNil(transaction.startTimestamp) - XCTAssertNil(transaction.timestamp) - XCTAssertEqual(transaction.transaction, someTransactionName) - XCTAssertEqual(transaction.traceId, context.traceId) - XCTAssertEqual(transaction.spanId, context.spanId) - XCTAssertEqual(transaction.operation, someOperation) - XCTAssertEqual(transaction.status, SentrySpanStatus.ok) - } - - func testFinishCapturesTransaction() { - let testDataProvider = TestCurrentDateProvider() - CurrentDate.setCurrentDateProvider(testDataProvider) - - let fileManager = try! SentryFileManager(dsn: TestConstants.dsn, andCurrentDateProvider: TestCurrentDateProvider()) - let transport = TestTransport() - let client = TestClient(options: Options(), andTransport: transport, andFileManager: fileManager) - let hub = SentryHub(client: client, andScope: nil, andCrashAdapter: TestSentryCrashWrapper()) - - let transaction = Transaction(name: someTransactionName, spanContext: SpanContext(operation: someOperation), hub: hub) - transaction.finish() - - XCTAssertEqual(transaction.startTimestamp, testDataProvider.date()) - XCTAssertEqual(transaction.timestamp, testDataProvider.date()) - XCTAssertTrue(transaction.timestamp! >= transaction.startTimestamp!) - XCTAssertTrue(client.captureEventWithScopeArguments.last!.event === transaction) - } - - func testSerializationWithoutContext() { - let testDataProvider = TestCurrentDateProvider() - CurrentDate.setCurrentDateProvider(testDataProvider) - testDataProvider.setDate(date: TestData.timestamp) - - let transaction = Transaction(name: someTransactionName, operation: someOperation) - - let serialization = transaction.serialize() - XCTAssertNotNil(serialization) - XCTAssertEqual(serialization["type"] as? String, "transaction") - XCTAssertEqual(serialization["event_id"] as? String, transaction.eventId.sentryIdString) - XCTAssertNotNil(serialization["start_timestamp"] as? String, TestData.timestampAs8601String) - XCTAssertEqual(serialization["timestamp"] as? String, TestData.timestampAs8601String) - XCTAssertEqual(serialization["transaction"] as? String, someTransactionName) - XCTAssertNotNil(serialization["contexts"]) - XCTAssertNotNil((serialization["contexts"] as! Dictionary)["trace"]) - XCTAssertNotNil(serialization["spans"]) - } - - func testSerializationWithContext() { - let testDataProvider = TestCurrentDateProvider() - CurrentDate.setCurrentDateProvider(testDataProvider) - testDataProvider.setDate(date: TestData.timestamp) - - let transaction = Transaction(name: someTransactionName, operation: someOperation) - transaction.context = [String: [String: Any]]() - - let serialization = transaction.serialize() - XCTAssertNotNil(serialization) - XCTAssertEqual(serialization["type"] as? String, "transaction") - XCTAssertEqual(serialization["event_id"] as? String, transaction.eventId.sentryIdString) - XCTAssertNotNil(serialization["start_timestamp"] as? String, TestData.timestampAs8601String) - XCTAssertEqual(serialization["timestamp"] as? String, TestData.timestampAs8601String) - XCTAssertEqual(serialization["transaction"] as? String, someTransactionName) - XCTAssertNotNil(serialization["contexts"]) - XCTAssertNotNil((serialization["contexts"] as! Dictionary)["trace"]) - XCTAssertNotNil(serialization["spans"]) - } - - func testAdditionOfChild() { - let transaction = Transaction(name: someTransactionName, operation: someOperation) - let span = transaction.startChild(operation: someOperation) - - XCTAssertEqual(transaction.spans.count, 1) - XCTAssertEqual(span.operation, someOperation) - XCTAssertEqual(span.parentSpanId, transaction.spanId) - XCTAssertNotNil(span.startTimestamp) - XCTAssertNil(span.spanDescription) - XCTAssertNil(span.timestamp) - } - - func testAddChildWithOperationAndDescription() { - let transaction = Transaction(name: someTransactionName, operation: someOperation) - let someDescription = "Some Description" - let span = transaction.startChild(operation: someOperation, description: someDescription) - - XCTAssertEqual(span.operation, someOperation) - XCTAssertEqual(span.spanDescription, someDescription) - XCTAssertEqual(span.parentSpanId, transaction.spanId) - XCTAssertNotNil(span.startTimestamp) - XCTAssertNil(span.timestamp) - } - - func testSerializeWithSpan() { - let transaction = Transaction(name: someTransactionName, operation: someOperation) - transaction.startChild(operation: someOperation) - - let serialization = transaction.serialize() - let spansSerialized = serialization["spans"] as! [Any] - XCTAssertEqual(spansSerialized.count, 1) - } -} From 088280c3a9850286c4a5cfc06440789c8ec813b0 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 2 Mar 2021 18:57:53 -0300 Subject: [PATCH 09/14] feat: Add transaction sampling properties to SentryOptions (#961) * Transaction sampling * Update CHANGELOG.md * Format code * Apply suggestions from code review Co-authored-by: Philipp Hofmann * Testing and fixes * Using NSNumber for sampler return * Update CHANGELOG.md Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> * Comment Update * Using NSNumber for tracesSampleRate Co-authored-by: Sentry Github Bot Co-authored-by: Philipp Hofmann Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> --- CHANGELOG.md | 1 + Sentry.xcodeproj/project.pbxproj | 9 +++++ Sources/Sentry/Public/Sentry.h | 1 + Sources/Sentry/Public/SentryDefines.h | 13 ++++++- Sources/Sentry/Public/SentryOptions.h | 16 +++++++- Sources/Sentry/Public/SentrySamplingContext.h | 31 +++++++++++++++ Sources/Sentry/SentryOptions.m | 11 ++++++ Sources/Sentry/SentrySamplingContext.m | 15 +++++++ Tests/SentryTests/SentryOptionsTest.m | 39 +++++++++++++++++++ 9 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 Sources/Sentry/Public/SentrySamplingContext.h create mode 100644 Sources/Sentry/SentrySamplingContext.m diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a3110db19..947754f986f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## unreleased +- feat: Add transaction sampling properties to SentryOptions #961 - feat: Add SentrySpan #932 - feat: Expand SentrySpanContext through SentryTransaction #919 - feat: Performance Monitoring API diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 8a8792fa8d8..3841549f083 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -400,6 +400,8 @@ 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; 861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */; }; 861265FA2404EC1500C4AFDE /* NSArray+SentrySanitize.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */; }; + 8E133FA225E72DEF00ABD0BF /* SentrySamplingContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E133FA025E72DEF00ABD0BF /* SentrySamplingContext.m */; }; + 8E133FA625E72EB400ABD0BF /* SentrySamplingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E133FA525E72EB400ABD0BF /* SentrySamplingContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8E4E7C6D25DAAAFE006AB9E2 /* SentryTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C6B25DAAAFE006AB9E2 /* SentryTransaction.h */; }; 8E4E7C6E25DAAAFE006AB9E2 /* SentrySpan.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */; }; 8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E4E7C7325DAAB49006AB9E2 /* SentrySpanProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -866,6 +868,9 @@ 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashIntegration.m; sourceTree = ""; }; 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSArray+SentrySanitize.h"; path = "include/NSArray+SentrySanitize.h"; sourceTree = ""; }; 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+SentrySanitize.m"; sourceTree = ""; }; + 8E133FA025E72DEF00ABD0BF /* SentrySamplingContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySamplingContext.m; sourceTree = ""; }; + 8E133FA525E72EB400ABD0BF /* SentrySamplingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySamplingContext.h; path = Public/SentrySamplingContext.h; sourceTree = ""; }; + 8E70B0D125CB67F3002B3155 /* SentryTransaction+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryTransaction+Private.h"; sourceTree = ""; }; 8E4E7C6B25DAAAFE006AB9E2 /* SentryTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryTransaction.h; path = include/SentryTransaction.h; sourceTree = ""; }; 8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySpan.h; path = include/SentrySpan.h; sourceTree = ""; }; 8E4E7C7325DAAB49006AB9E2 /* SentrySpanProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanProtocol.h; path = Public/SentrySpanProtocol.h; sourceTree = ""; }; @@ -1673,6 +1678,8 @@ 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */, 8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */, 8EC4CF4725C38CAF0093DEE9 /* SentrySpanStatus.h */, + 8E133FA525E72EB400ABD0BF /* SentrySamplingContext.h */, + 8E133FA025E72DEF00ABD0BF /* SentrySamplingContext.m */, 8E4E7C7B25DAB287006AB9E2 /* SentryTracer.h */, 8E4E7C8125DAB2A5006AB9E2 /* SentryTracer.m */, ); @@ -1686,6 +1693,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 8E133FA625E72EB400ABD0BF /* SentrySamplingContext.h in Headers */, 8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */, 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */, 8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */, @@ -1978,6 +1986,7 @@ 7BE3C7692445C1A800A38442 /* SentryCurrentDate.m in Sources */, 63EED6C02237923600E02400 /* SentryOptions.m in Sources */, 63AA769E1EB9C57A00D153DE /* SentryError.m in Sources */, + 8E133FA225E72DEF00ABD0BF /* SentrySamplingContext.m in Sources */, 63FE708D20DA4C1000CDBAE8 /* SentryCrashReportFilterBasic.m in Sources */, 63FE718120DA4C1100CDBAE8 /* SentryCrashDoctor.m in Sources */, 63FE713720DA4C1100CDBAE8 /* SentryCrashCPU_x86_64.c in Sources */, diff --git a/Sources/Sentry/Public/Sentry.h b/Sources/Sentry/Public/Sentry.h index c4376798d8f..3554fff2736 100644 --- a/Sources/Sentry/Public/Sentry.h +++ b/Sources/Sentry/Public/Sentry.h @@ -27,6 +27,7 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[]; #import "SentryNSError.h" #import "SentryOptions.h" #import "SentrySDK.h" +#import "SentrySamplingContext.h" #import "SentryScope.h" #import "SentrySdkInfo.h" #import "SentrySerializable.h" diff --git a/Sources/Sentry/Public/SentryDefines.h b/Sources/Sentry/Public/SentryDefines.h index b6e1affe373..747e2d5c9da 100644 --- a/Sources/Sentry/Public/SentryDefines.h +++ b/Sources/Sentry/Public/SentryDefines.h @@ -22,7 +22,7 @@ -(instancetype)init NS_UNAVAILABLE; \ +(instancetype) new NS_UNAVAILABLE; -@class SentryEvent, SentryBreadcrumb; +@class SentryEvent, SentryBreadcrumb, SentrySamplingContext; /** * Block used for returning after a request finished @@ -61,6 +61,17 @@ typedef void (^SentryOnCrashedLastRunCallback)(SentryEvent *_Nonnull event); */ typedef BOOL (^SentryShouldQueueEvent)( NSHTTPURLResponse *_Nullable response, NSError *_Nullable error); + +/** + * Function pointer for a sampler callback. + * + * @param samplingContext context of the sampling. + * + * @return A sample rate between 0.0 and 1.0. + */ +typedef NSNumber *_Nullable (^SentryTracesSamplerCallback)( + SentrySamplingContext *_Nonnull samplingContext); + /** * Loglevel */ diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index a6ba9ebc8d5..95f2aa4348f 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -93,7 +93,7 @@ NS_SWIFT_NAME(Options) /** * Array of integrations to install. */ -@property (nonatomic, copy) NSArray *_Nullable integrations; +@property (nullable, nonatomic, copy) NSArray *integrations; /** * Array of default integrations. Will be used if integrations are nil @@ -148,6 +148,20 @@ NS_SWIFT_NAME(Options) */ @property (nonatomic, assign) BOOL sendDefaultPii; +/** + * Indicates the percentage of the tracing data that is collected. + * Setting this to 0 or NIL discards all trace data, 1.0 collects all trace data, + * 0.01 collects 1% of all trace data. + */ +@property (nullable, nonatomic, strong) NSNumber *tracesSampleRate; + +/** + * A callback to a user defined traces sampler function. + * Returning 0 or NIL discards all trace data, 1.0 collects all trace data, + * 0.01 collects 1% of all trace data. + */ +@property (nullable, nonatomic) SentryTracesSamplerCallback tracesSampler; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Public/SentrySamplingContext.h b/Sources/Sentry/Public/SentrySamplingContext.h new file mode 100644 index 00000000000..d800160e40a --- /dev/null +++ b/Sources/Sentry/Public/SentrySamplingContext.h @@ -0,0 +1,31 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SentryTransactionContext; + +NS_SWIFT_NAME(SamplingContext) +@interface SentrySamplingContext : NSObject + +/** + * Transaction context. + */ +@property (nonatomic, readonly) SentryTransactionContext *transactionContext; + +/** + * Custom data used for sampling. + */ +@property (nonatomic, readonly) NSDictionary *customSamplingContext; + +/** + * Init a SentryTransactionSamplingContext. + * + * @param transactionContext The context of the transaction being sampled. + * @param customSamplingContext Custom data used for sampling. + */ +- (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext + customSamplingContext:(NSDictionary *)customSamplingContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 165fe37d510..4c79b492a39 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -30,6 +30,7 @@ - (instancetype)init self.attachStacktrace = YES; self.maxAttachmentSize = 20 * 1024 * 1024; self.sendDefaultPii = NO; + self.tracesSampleRate = 0; _sdkInfo = [[SentrySdkInfo alloc] initWithName:SentryMeta.sdkName andVersion:SentryMeta.versionString]; @@ -163,6 +164,16 @@ - (void)validateOptions:(NSDictionary *)options if (nil != options[@"sendDefaultPii"]) { self.sendDefaultPii = [options[@"sendDefaultPii"] boolValue]; } + + NSNumber *tracesSampleRate = options[@"tracesSampleRate"]; + if (nil != tracesSampleRate && [tracesSampleRate floatValue] >= 0 && + [tracesSampleRate floatValue] <= 1.0) { + self.sampleRate = tracesSampleRate; + } + + if (nil != options[@"tracesSampler"]) { + self.tracesSampler = options[@"tracesSampler"]; + } } @end diff --git a/Sources/Sentry/SentrySamplingContext.m b/Sources/Sentry/SentrySamplingContext.m new file mode 100644 index 00000000000..5ab35573964 --- /dev/null +++ b/Sources/Sentry/SentrySamplingContext.m @@ -0,0 +1,15 @@ +#import "SentrySamplingContext.h" + +@implementation SentrySamplingContext + +- (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext + customSamplingContext:(NSDictionary *)customSamplingContext +{ + if (self = [super init]) { + _transactionContext = transactionContext; + _customSamplingContext = customSamplingContext; + } + return self; +} + +@end diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 3b3f668aadb..43ec7fea407 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -415,6 +415,45 @@ - (void)testDefaultSendDefaultPii XCTAssertFalse(options.sendDefaultPii); } +- (void)testTracesSampleRate +{ + SentryOptions *options = [self getValidOptions:@{ @"tracesSampleRate" : @0.1 }]; + + XCTAssertEqual(options.tracesSampleRate, @0.1); +} + +- (void)testDefaultTracesSampleRate +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertEqual(options.tracesSampleRate, @0); +} + +- (double)tracesSamplerCallback:(NSDictionary *)context +{ + return 0.1; +} + +- (void)testTracesSampler +{ + SentryTracesSamplerCallback sampler = ^(SentrySamplingContext *context) { + XCTAssertNotNil(context); + return @1.0; + }; + + SentryOptions *options = [self getValidOptions:@{ @"tracesSampler" : sampler }]; + + SentrySamplingContext *context = [[SentrySamplingContext alloc] init]; + XCTAssertEqual(options.tracesSampler(context), @1.0); +} + +- (void)testDefaultTracesSampler +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.tracesSampler); +} + - (SentryOptions *)getValidOptions:(NSDictionary *)dict { NSError *error = nil; From 4a90eb54e13a8a8a76fe762d66648a7bb4f968c9 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Mon, 8 Mar 2021 10:32:19 -0300 Subject: [PATCH 10/14] feat: Transaction in Sample App (#971) * Some UI Sample * Format code * Apply suggestions from code review Co-authored-by: Philipp Hofmann * removing button to download image * Adding status and fixing operation value * Fixed Tests Co-authored-by: Sentry Github Bot Co-authored-by: Philipp Hofmann --- .../iOS-Swift.xcodeproj/project.pbxproj | 8 ++- .../iOS-Swift/Base.lproj/Main.storyboard | 47 ++++++++++++++++-- .../iOS-Swift/TraceTestViewController.swift | 49 +++++++++++++++++++ .../iOS-Swift/iOS-Swift/ViewController.swift | 2 +- Sources/Sentry/SentryOptions.m | 2 +- Tests/SentryTests/SentryOptionsTest.m | 4 +- 6 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 Samples/iOS-Swift/iOS-Swift/TraceTestViewController.swift diff --git a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj index 5b54879a6ce..fc178cc2786 100644 --- a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj +++ b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 637AFDB6243B02770034958B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 637AFDB4243B02770034958B /* LaunchScreen.storyboard */; }; 7B3427F825876A5200056519 /* Tongariro.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7B3427F725876A5200056519 /* Tongariro.jpg */; }; 7B4AC44025D16AA00070736A /* RandomErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4AC43F25D16AA00070736A /* RandomErrors.swift */; }; + 8E8C57AF25EF16E6001CEEFA /* TraceTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8C57AE25EF16E6001CEEFA /* TraceTestViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -71,6 +72,7 @@ 63F93AA9245AC91600A500DB /* iOS-Swift.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "iOS-Swift.entitlements"; sourceTree = ""; }; 7B3427F725876A5200056519 /* Tongariro.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Tongariro.jpg; sourceTree = ""; }; 7B4AC43F25D16AA00070736A /* RandomErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomErrors.swift; sourceTree = ""; }; + 8E8C57AE25EF16E6001CEEFA /* TraceTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceTestViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -133,6 +135,7 @@ 7B3427F725876A5200056519 /* Tongariro.jpg */, 637AFDB4243B02770034958B /* LaunchScreen.storyboard */, 637AFDB7243B02770034958B /* Info.plist */, + 8E8C57AE25EF16E6001CEEFA /* TraceTestViewController.swift */, ); path = "iOS-Swift"; sourceTree = ""; @@ -236,6 +239,7 @@ files = ( 637AFDAE243B02760034958B /* ViewController.swift in Sources */, 637AFDAA243B02760034958B /* AppDelegate.swift in Sources */, + 8E8C57AF25EF16E6001CEEFA /* TraceTestViewController.swift in Sources */, 637AFDAC243B02760034958B /* SceneDelegate.swift in Sources */, 7B4AC44025D16AA00070736A /* RandomErrors.swift in Sources */, ); @@ -400,7 +404,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.0.0-alpha.0; + MARKETING_VERSION = "7.0.0-alpha.0"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = NO; @@ -423,7 +427,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.0.0-alpha.0; + MARKETING_VERSION = "7.0.0-alpha.0"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.sentry.sample.iOS-Swift"; diff --git a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard index d82ee15c668..01b004ed2c5 100644 --- a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard +++ b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard @@ -24,7 +24,7 @@ - + - +