Skip to content

Commit

Permalink
Merge 65e2095 into 522f8da
Browse files Browse the repository at this point in the history
  • Loading branch information
philipphofmann authored Nov 5, 2024
2 parents 522f8da + 65e2095 commit 08981b2
Show file tree
Hide file tree
Showing 41 changed files with 708 additions and 68 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Too many navigation breadcrumbs for Session Replay (#4480)
- Time-of-check time-of-use filesystem race condition (#4473)
- Capture all touches with session replay (#4477)
- Transactions for crashes (#4504): Finish the transaction bound to the scope when the app crashes. This __experimental__ feature is disabled by default. You can enable it via the option `enableTracingWhenCrashing`.

### Improvements

Expand Down
1 change: 1 addition & 0 deletions Samples/iOS-Swift/iOS-Swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
options.enableSwizzling = !args.contains("--disable-swizzling")
options.enableCrashHandler = !args.contains("--disable-crash-handler")
options.enableTracing = !args.contains("--disable-tracing")
options.enableTracingWhenCrashing = true

// because we run CPU for 15 seconds at full throttle, we trigger ANR issues being sent. disable such during benchmarks.
options.enableAppHangTracking = !isBenchmarking && !args.contains("--disable-anr-tracking")
Expand Down
9 changes: 8 additions & 1 deletion Samples/iOS-Swift/iOS-Swift/ErrorsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ class ErrorsViewController: UIViewController {
}

@IBAction func crash(_ sender: UIButton) {
SentrySDK.crash()
let transaction = SentrySDK.startTransaction(name: "Crashing Transaction", operation: "ui.load", bindToScope: true)

transaction.startChild(operation: "operation explode")

DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
transaction.startChild(operation: "operation crash")
SentrySDK.crash()
}
}

// swiftlint:disable force_unwrapping
Expand Down
5 changes: 5 additions & 0 deletions SentryTestUtils/TestClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ public class TestClient: SentryClient {
return SentryId()
}

public var saveCrashTransactionInvocations = Invocations<(event: Event, scope: Scope)>()
public override func saveCrashTransaction(transaction: Transaction, scope: Scope) {
saveCrashTransactionInvocations.record((transaction, scope))
}

public var captureUserFeedbackInvocations = Invocations<UserFeedback>()
public override func capture(userFeedback: UserFeedback) {
captureUserFeedbackInvocations.record(userFeedback)
Expand Down
7 changes: 6 additions & 1 deletion SentryTestUtils/TestTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import _SentryPrivate
import Foundation

public class TestTransport: NSObject, Transport {

public var sentEnvelopes = Invocations<SentryEnvelope>()
public func send(envelope: SentryEnvelope) {
sentEnvelopes.record(envelope)
}

public var storedEnvelopes = Invocations<SentryEnvelope>()
public func store(_ envelope: SentryEnvelope) {
storedEnvelopes.record(envelope)
}

public var recordLostEvents = Invocations<(category: SentryDataCategory, reason: SentryDiscardReason)>()
public func recordLostEvent(_ category: SentryDataCategory, reason: SentryDiscardReason) {
recordLostEvents.record((category, reason))
Expand Down
5 changes: 5 additions & 0 deletions SentryTestUtils/TestTransportAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public class TestTransportAdapter: SentryTransportAdapter {
public override func send(event: Event, traceContext: TraceContext?, attachments: [Attachment], additionalEnvelopeItems: [SentryEnvelopeItem]) {
sendEventWithTraceStateInvocations.record((event, traceContext, attachments, additionalEnvelopeItems))
}

public var storeEventInvocations = Invocations<(event: Event, traceContext: TraceContext?)>()
public override func store(_ event: Event, traceContext: TraceContext?) {
storeEventInvocations.record((event, traceContext))
}

public var userFeedbackInvocations = Invocations<UserFeedback>()
public override func send(userFeedback: UserFeedback) {
Expand Down
11 changes: 11 additions & 0 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,17 @@ NS_SWIFT_NAME(Options)
*/
@property (nonatomic, assign) BOOL enablePerformanceV2;

/**
* @warning This is an experimental feature and may still have bugs.
*
* When enabled, the SDK finishes the ongoing transaction bound to the scope and links them to the
* crash event when your app crashes. The SDK skips adding profiles to increase the chance of
* keeping the transaction.
*
* @note The default is @c NO .
*/
@property (nonatomic, assign) BOOL enableTracingWhenCrashing;

/**
* A block that configures the initial scope when starting the SDK.
* @discussion The block receives a suggested default scope. You can either
Expand Down
16 changes: 16 additions & 0 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,22 @@ - (SentryId *)captureCrashEvent:(SentryEvent *)event
return [self sendEvent:preparedEvent withSession:session withScope:scope];
}

- (void)saveCrashTransaction:(SentryTransaction *)transaction withScope:(SentryScope *)scope
{
SentryEvent *preparedEvent = [self prepareEvent:transaction
withScope:scope
alwaysAttachStacktrace:NO
isCrashEvent:NO];

if (preparedEvent == nil) {
return;
}

SentryTraceContext *traceContext = [self getTraceStateWithEvent:transaction withScope:scope];

[self.transportAdapter storeEvent:preparedEvent traceContext:traceContext];
}

- (SentryId *)captureEvent:(SentryEvent *)event
{
return [self captureEvent:event withScope:[[SentryScope alloc] init]];
Expand Down
23 changes: 23 additions & 0 deletions Sources/Sentry/SentryCrashIntegration.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "SentryCrashIntegration.h"
#import "SentryCrashInstallationReporter.h"

#import "SentryCrashC.h"
#include "SentryCrashMonitor_Signal.h"
#import "SentryCrashWrapper.h"
#import "SentryDispatchQueueWrapper.h"
Expand All @@ -11,6 +12,7 @@
#import "SentrySDK+Private.h"
#import "SentryScope+Private.h"
#import "SentrySessionCrashedHandler.h"
#import "SentryTracer.h"
#import "SentryWatchdogTerminationLogic.h"
#import <SentryAppStateManager.h>
#import <SentryClient+Private.h>
Expand All @@ -34,6 +36,16 @@
static NSString *const DEVICE_KEY = @"device";
static NSString *const LOCALE_KEY = @"locale";

void
sentry_finishAndSaveTransaction(void)
{
id<SentrySpan> span = SentrySDK.currentHub.scope.span;
if (span != nil && [span isKindOfClass:[SentryTracer class]]) {
SentryTracer *tracer = (SentryTracer *)span;
[tracer finishForCrash];
}
}

@interface SentryCrashIntegration ()

@property (nonatomic, weak) SentryOptions *options;
Expand Down Expand Up @@ -110,6 +122,10 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options

[self configureScope];

if (options.enableTracingWhenCrashing) {
[self configureTracingWhenCrashing];
}

return YES;
}

Expand Down Expand Up @@ -193,6 +209,8 @@ - (void)uninstall
installationToken = 0;
}

sentrycrash_setSaveTransaction(NULL);

[NSNotificationCenter.defaultCenter removeObserver:self
name:NSCurrentLocaleDidChangeNotification
object:nil];
Expand Down Expand Up @@ -243,4 +261,9 @@ - (void)currentLocaleDidChange
}];
}

- (void)configureTracingWhenCrashing
{
sentrycrash_setSaveTransaction(&sentry_finishAndSaveTransaction);
}

@end
10 changes: 9 additions & 1 deletion Sources/Sentry/SentryCrashReportConverter.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,15 @@ - (SentryEvent *_Nullable)convertReportToEvent

event.dist = self.userContext[@"dist"];
event.environment = self.userContext[@"environment"];
event.context = self.userContext[@"context"];

NSMutableDictionary *mutableContext =
[[NSMutableDictionary alloc] initWithDictionary:self.userContext[@"context"]];
if (self.userContext[@"traceContext"]) {
mutableContext[@"trace"] = self.userContext[@"traceContext"];
}

event.context = mutableContext;

event.extra = self.userContext[@"extra"];
event.tags = self.userContext[@"tags"];
// event.level we do not set the level here since this always resulted
Expand Down
6 changes: 6 additions & 0 deletions Sources/Sentry/SentryCrashScopeObserver.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ - (void)setContext:(nullable NSDictionary<NSString *, id> *)context
syncToSentryCrash:^(const void *bytes) { sentrycrash_scopesync_setContext(bytes); }];
}

- (void)setTraceContext:(nullable NSDictionary<NSString *, id> *)traceContext
{
[self syncScope:traceContext
syncToSentryCrash:^(const void *bytes) { sentrycrash_scopesync_setTraceContext(bytes); }];
}

- (void)setExtras:(nullable NSDictionary<NSString *, id> *)extras
{
[self syncScope:extras
Expand Down
5 changes: 5 additions & 0 deletions Sources/Sentry/SentryHttpTransport.m
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ - (void)sendEnvelope:(SentryEnvelope *)envelope
}];
}

- (void)storeEnvelope:(SentryEnvelope *)envelope
{
[self.fileManager storeEnvelope:envelope];
}

- (void)recordLostEvent:(SentryDataCategory)category reason:(SentryDiscardReason)reason
{
[self recordLostEvent:category reason:reason quantity:1];
Expand Down
15 changes: 15 additions & 0 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,21 @@ - (void)captureTransaction:(SentryTransaction *)transaction
}];
}

- (void)saveCrashTransaction:(SentryTransaction *)transaction
{
SentrySampleDecision decision = transaction.trace.sampled;

if (decision != kSentrySampleDecisionYes) {
// No need to update client reports when we're crashing cause they get lost anyways.
return;
}

SentryClient *client = _client;
if (client != nil) {
[client saveCrashTransaction:transaction withScope:self.scope];
}
}

- (SentryId *)captureEvent:(SentryEvent *)event
{
return [self captureEvent:event withScope:self.scope];
Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/SentryOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ - (instancetype)init
self.sendDefaultPii = NO;
self.enableAutoPerformanceTracing = YES;
self.enablePerformanceV2 = NO;
self.enableTracingWhenCrashing = NO;
self.enableCaptureFailedRequests = YES;
self.environment = kSentryDefaultEnvironment;
self.enableTimeToFullDisplayTracing = NO;
Expand Down Expand Up @@ -413,6 +414,9 @@ - (BOOL)validateOptions:(NSDictionary<NSString *, id> *)options
[self setBool:options[@"enablePerformanceV2"]
block:^(BOOL value) { self->_enablePerformanceV2 = value; }];

[self setBool:options[@"enableTracingWhenCrashing"]
block:^(BOOL value) { self->_enableTracingWhenCrashing = value; }];

[self setBool:options[@"enableCaptureFailedRequests"]
block:^(BOOL value) { self->_enableCaptureFailedRequests = value; }];

Expand Down
40 changes: 31 additions & 9 deletions Sources/Sentry/SentryScope.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ - (instancetype)initWithScope:(SentryScope *)scope
[_fingerprintArray addObjectsFromArray:[scope fingerprints]];
[_attachmentArray addObjectsFromArray:[scope attachments]];

self.propagationContext = [[SentryPropagationContext alloc] init];
self.propagationContext = scope.propagationContext;
self.maxBreadcrumbs = scope.maxBreadcrumbs;
self.userObject = scope.userObject.copy;
self.distString = scope.distString;
Expand Down Expand Up @@ -143,6 +143,10 @@ - (void)setSpan:(nullable id<SentrySpan>)span
{
@synchronized(_spanLock) {
_span = span;

for (id<SentryScopeObserver> observer in self.observers) {
[observer setTraceContext:[self buildTraceContext:span]];
}
}
}

Expand Down Expand Up @@ -453,9 +457,18 @@ - (void)clearAttachments
if (self.extras.count > 0) {
[serializedData setValue:[self extras] forKey:@"extra"];
}
if (self.context.count > 0) {
[serializedData setValue:[self context] forKey:@"context"];

NSDictionary *traceContext = nil;
@synchronized(_spanLock) {
traceContext = [self buildTraceContext:_span];
}
serializedData[@"traceContext"] = traceContext;

NSDictionary *context = [self context];
if (context.count > 0) {
[serializedData setValue:context forKey:@"context"];
}

[serializedData setValue:[self.userObject serialize] forKey:@"user"];
[serializedData setValue:self.distString forKey:@"dist"];
[serializedData setValue:self.environmentString forKey:@"environment"];
Expand Down Expand Up @@ -571,8 +584,9 @@ - (SentryEvent *__nullable)applyToEvent:(SentryEvent *)event
return event;
}

id<SentrySpan> span;

if (self.span != nil) {
id<SentrySpan> span;
@synchronized(_spanLock) {
span = self.span;
}
Expand All @@ -583,14 +597,10 @@ - (SentryEvent *__nullable)applyToEvent:(SentryEvent *)event
[span isKindOfClass:[SentryTracer class]]) {
event.transaction = [[(SentryTracer *)span transactionContext] name];
}
newContext[@"trace"] = [span serialize];
}
}

if (newContext[@"trace"] == nil) {
// If this is an error event we need to add the distributed trace context.
newContext[@"trace"] = [self.propagationContext traceContextForEvent];
}
newContext[@"trace"] = [self buildTraceContext:span];

event.context = newContext;
return event;
Expand All @@ -601,6 +611,18 @@ - (void)addObserver:(id<SentryScopeObserver>)observer
[self.observers addObject:observer];
}

/**
* Make sure to call this inside @c synchronized(_spanLock) caus this method isn't thread safe.
*/
- (NSDictionary *)buildTraceContext:(nullable id<SentrySpan>)span
{
if (span != nil) {
return [span serialize];
} else {
return [self.propagationContext traceContextForEvent];
}
}

@end

NS_ASSUME_NONNULL_END
6 changes: 6 additions & 0 deletions Sources/Sentry/SentryScopeSyncC.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ sentrycrash_scopesync_setContext(const char *const jsonEncodedCString)
setField(jsonEncodedCString, &scope.context);
}

void
sentrycrash_scopesync_setTraceContext(const char *const jsonEncodedCString)
{
setField(jsonEncodedCString, &scope.traceContext);
}

void
sentrycrash_scopesync_setEnvironment(const char *const jsonEncodedCString)
{
Expand Down
5 changes: 5 additions & 0 deletions Sources/Sentry/SentrySpotlightTransport.m
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ - (void)sendEnvelope:(SentryEnvelope *)envelope
}];
}

- (void)storeEnvelope:(SentryEnvelope *)envelope
{
[self sendEnvelope:envelope];
}

- (SentryFlushResult)flush:(NSTimeInterval)timeout
{
// Empty on purpose
Expand Down
Loading

0 comments on commit 08981b2

Please sign in to comment.