// Copyright (c) 2022 Sentry. All Rights Reserved. #include "SentrySubsystemApple.h" #include "SentryBreadcrumbApple.h" #include "SentryEventApple.h" #include "SentryScopeApple.h" #include "SentryUserApple.h" #include "SentryUserFeedbackApple.h" #include "SentryTransactionApple.h" #include "SentryTransactionContextApple.h" #include "SentrySamplingContextApple.h" #include "SentryIdApple.h" #include "SentryEvent.h" #include "SentrySettings.h" #include "SentryBeforeSendHandler.h" #include "SentryDefines.h" #include "SentrySamplingContext.h" #include "SentryTraceSampler.h" #include "Infrastructure/SentryConvertorsApple.h" #include "Convenience/SentryInclude.h" #include "Convenience/SentryMacro.h" #include "GenericPlatform/GenericPlatformOutputDevices.h" #include "HAL/FileManager.h" #include "UObject/GarbageCollection.h" #include "Utils/SentryLogUtils.h" void SentrySubsystemApple::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler) { [SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) setSdkName:@"sentry.cocoa.unreal"]; dispatch_group_t sentryDispatchGroup = dispatch_group_create(); dispatch_group_enter(sentryDispatchGroup); dispatch_async(dispatch_get_main_queue(), ^{ [SENTRY_APPLE_CLASS(SentrySDK) startWithConfigureOptions:^(SentryOptions *options) { options.dsn = settings->Dsn.GetNSString(); options.environment = settings->Environment.GetNSString(); options.enableAutoSessionTracking = settings->EnableAutoSessionTracking; options.sessionTrackingIntervalMillis = settings->SessionTimeout; options.releaseName = settings->OverrideReleaseName ? settings->Release.GetNSString() : settings->GetFormattedReleaseName().GetNSString(); options.attachStacktrace = settings->AttachStacktrace; options.debug = settings->Debug; options.sampleRate = [NSNumber numberWithFloat:settings->SampleRate]; options.maxBreadcrumbs = settings->MaxBreadcrumbs; options.sendDefaultPii = settings->SendDefaultPii; #if SENTRY_UIKIT_AVAILABLE options.attachScreenshot = settings->AttachScreenshot; #endif options.initialScope = ^(SentryScope* scope) { if(settings->EnableAutoLogAttachment) { const FString logFilePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FGenericPlatformOutputDevices::GetAbsoluteLogFilename()); SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; [scope addAttachment:logAttachment]; } return scope; }; options.beforeSend = ^SentryEvent* (SentryEvent* event) { FGCScopeGuard GCScopeGuard; USentryEvent* EventToProcess = NewObject(); EventToProcess->InitWithNativeImpl(MakeShareable(new SentryEventApple(event))); return beforeSendHandler->HandleBeforeSend(EventToProcess, nullptr) ? event : nullptr; }; for (auto it = settings->InAppInclude.CreateConstIterator(); it; ++it) { [options addInAppInclude:it->GetNSString()]; } for (auto it = settings->InAppExclude.CreateConstIterator(); it; ++it) { [options addInAppExclude:it->GetNSString()]; } options.enableAppHangTracking = settings->EnableAppNotRespondingTracking; if(settings->EnableTracing && settings->SamplingType == ESentryTracesSamplingType::UniformSampleRate) { options.tracesSampleRate = [NSNumber numberWithFloat:settings->TracesSampleRate]; } if(settings->EnableTracing && settings->SamplingType == ESentryTracesSamplingType::TracesSampler) { options.tracesSampler = ^NSNumber* (SentrySamplingContext* samplingContext) { FGCScopeGuard GCScopeGuard; USentrySamplingContext* Context = NewObject(); Context->InitWithNativeImpl(MakeShareable(new SentrySamplingContextApple(samplingContext))); float samplingValue; return traceSampler->Sample(Context, samplingValue) ? [NSNumber numberWithFloat:samplingValue] : nil; }; } }]; dispatch_group_leave(sentryDispatchGroup); }); // Wait synchronously until sentry-cocoa initialization finished in main thread dispatch_group_wait(sentryDispatchGroup, DISPATCH_TIME_FOREVER); dispatch_release(sentryDispatchGroup); } void SentrySubsystemApple::Close() { [SENTRY_APPLE_CLASS(SentrySDK) close]; } bool SentrySubsystemApple::IsEnabled() { return [SENTRY_APPLE_CLASS(SentrySDK) isEnabled]; } ESentryCrashedLastRun SentrySubsystemApple::IsCrashedLastRun() { return [SENTRY_APPLE_CLASS(SentrySDK) crashedLastRun] ? ESentryCrashedLastRun::Crashed : ESentryCrashedLastRun::NotCrashed; } void SentrySubsystemApple::AddBreadcrumb(TSharedPtr breadcrumb) { TSharedPtr breadcrumbIOS = StaticCastSharedPtr(breadcrumb); [SENTRY_APPLE_CLASS(SentrySDK) addBreadcrumb:breadcrumbIOS->GetNativeObject()]; } void SentrySubsystemApple::AddBreadcrumbWithParams(const FString& Message, const FString& Category, const FString& Type, const TMap& Data, ESentryLevel Level) { TSharedPtr breadcrumbIOS = MakeShareable(new SentryBreadcrumbApple()); breadcrumbIOS->SetMessage(Message); breadcrumbIOS->SetCategory(Category); breadcrumbIOS->SetType(Type); breadcrumbIOS->SetData(Data); breadcrumbIOS->SetLevel(Level); [SENTRY_APPLE_CLASS(SentrySDK) addBreadcrumb:breadcrumbIOS->GetNativeObject()]; } void SentrySubsystemApple::ClearBreadcrumbs() { [SENTRY_APPLE_CLASS(SentrySDK) configureScope:^(SentryScope* scope) { [scope clearBreadcrumbs]; }]; } TSharedPtr SentrySubsystemApple::CaptureMessage(const FString& message, ESentryLevel level) { SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ [scope setLevel:SentryConvertorsApple::SentryLevelToNative(level)]; }]; return MakeShareable(new SentryIdApple(id)); } TSharedPtr SentrySubsystemApple::CaptureMessageWithScope(const FString& message, const FSentryScopeDelegate& onConfigureScope, ESentryLevel level) { SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ [scope setLevel:SentryConvertorsApple::SentryLevelToNative(level)]; onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; return MakeShareable(new SentryIdApple(id)); } TSharedPtr SentrySubsystemApple::CaptureEvent(TSharedPtr event) { TSharedPtr eventIOS = StaticCastSharedPtr(event); SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject()]; return MakeShareable(new SentryIdApple(id)); } TSharedPtr SentrySubsystemApple::CaptureEventWithScope(TSharedPtr event, const FSentryScopeDelegate& onConfigureScope) { TSharedPtr eventIOS = StaticCastSharedPtr(event); SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject() withScopeBlock:^(SentryScope* scope) { onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; return MakeShareable(new SentryIdApple(id)); } TSharedPtr SentrySubsystemApple::CaptureException(const FString& type, const FString& message, int32 framesToSkip) { auto StackFrames = FGenericPlatformStackWalk::GetStack(framesToSkip); SentryException *nativeException = [[SENTRY_APPLE_CLASS(SentryException) alloc] initWithValue:message.GetNSString() type:type.GetNSString()]; NSMutableArray *nativeExceptionArray = [NSMutableArray arrayWithCapacity:1]; [nativeExceptionArray addObject:nativeException]; SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS(SentryEvent) alloc] init]; exceptionEvent.exceptions = nativeExceptionArray; exceptionEvent.stacktrace = SentryConvertorsApple::CallstackToNative(StackFrames); SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent]; return MakeShareable(new SentryIdApple(id)); } TSharedPtr SentrySubsystemApple::CaptureAssertion(const FString& type, const FString& message) { #if PLATFORM_MAC int32 framesToSkip = 6; #elif PLATFORM_IOS int32 framesToSkip = 5; #endif SentryLogUtils::LogStackTrace(*message, ELogVerbosity::Error, framesToSkip); return CaptureException(type, message, framesToSkip); } TSharedPtr SentrySubsystemApple::CaptureEnsure(const FString& type, const FString& message) { int32 framesToSkip = 6; SentryLogUtils::LogStackTrace(*message, ELogVerbosity::Error, framesToSkip); return CaptureException(type, message, framesToSkip); } void SentrySubsystemApple::CaptureUserFeedback(TSharedPtr userFeedback) { TSharedPtr userFeedbackIOS = StaticCastSharedPtr(userFeedback); [SENTRY_APPLE_CLASS(SentrySDK) captureUserFeedback:userFeedbackIOS->GetNativeObject()]; } void SentrySubsystemApple::SetUser(TSharedPtr user) { TSharedPtr userIOS = StaticCastSharedPtr(user); [SENTRY_APPLE_CLASS(SentrySDK) setUser:userIOS->GetNativeObject()]; } void SentrySubsystemApple::RemoveUser() { [SENTRY_APPLE_CLASS(SentrySDK) setUser:nil]; } void SentrySubsystemApple::ConfigureScope(const FSentryScopeDelegate& onConfigureScope) { [SENTRY_APPLE_CLASS(SentrySDK) configureScope:^(SentryScope* scope) { onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; } void SentrySubsystemApple::SetContext(const FString& key, const TMap& values) { [SENTRY_APPLE_CLASS(SentrySDK) configureScope:^(SentryScope* scope) { [scope setContextValue:SentryConvertorsApple::StringMapToNative(values) forKey:key.GetNSString()]; }]; } void SentrySubsystemApple::SetTag(const FString& key, const FString& value) { [SENTRY_APPLE_CLASS(SentrySDK) configureScope:^(SentryScope* scope) { [scope setTagValue:value.GetNSString() forKey:key.GetNSString()]; }]; } void SentrySubsystemApple::RemoveTag(const FString& key) { [SENTRY_APPLE_CLASS(SentrySDK) configureScope:^(SentryScope* scope) { [scope removeTagForKey:key.GetNSString()]; }]; } void SentrySubsystemApple::SetLevel(ESentryLevel level) { [SENTRY_APPLE_CLASS(SentrySDK) configureScope:^(SentryScope* scope) { [scope setLevel:SentryConvertorsApple::SentryLevelToNative(level)]; }]; } void SentrySubsystemApple::StartSession() { [SENTRY_APPLE_CLASS(SentrySDK) startSession]; } void SentrySubsystemApple::EndSession() { [SENTRY_APPLE_CLASS(SentrySDK) endSession]; } TSharedPtr SentrySubsystemApple::StartTransaction(const FString& name, const FString& operation) { id transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithName:name.GetNSString() operation:operation.GetNSString()]; return MakeShareable(new SentryTransactionApple(transaction)); } TSharedPtr SentrySubsystemApple::StartTransactionWithContext(TSharedPtr context) { TSharedPtr transactionContextIOS = StaticCastSharedPtr(context); id transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithContext:transactionContextIOS->GetNativeObject()]; return MakeShareable(new SentryTransactionApple(transaction)); } TSharedPtr SentrySubsystemApple::StartTransactionWithContextAndTimestamp(TSharedPtr context, int64 timestamp) { UE_LOG(LogSentrySdk, Log, TEXT("Setting transaction timestamp explicitly not supported on Mac/iOS.")); return StartTransactionWithContext(context); } TSharedPtr SentrySubsystemApple::StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) { TSharedPtr transactionContextIOS = StaticCastSharedPtr(context); id transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithContext:transactionContextIOS->GetNativeObject() customSamplingContext:SentryConvertorsApple::StringMapToNative(options)]; return MakeShareable(new SentryTransactionApple(transaction)); } TSharedPtr SentrySubsystemApple::ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) { TArray traceParts; sentryTrace.ParseIntoArray(traceParts, TEXT("-")); if (traceParts.Num() < 2) { return nullptr; } SentrySampleDecision sampleDecision = kSentrySampleDecisionUndecided; if (traceParts.Num() == 3) { sampleDecision = traceParts[2].Equals(TEXT("1")) ? kSentrySampleDecisionYes : kSentrySampleDecisionNo; } // `SentryId` definition was moved to Swift so its name that can be recognized by UE should be taken from "Sentry-Swift.h" to successfully load class on Mac #if PLATFORM_MAC SentryId* traceId = [[SENTRY_APPLE_CLASS(_TtC6Sentry8SentryId) alloc] initWithUUIDString:traceParts[0].GetNSString()]; #elif PLATFORM_IOS SentryId* traceId = [[SENTRY_APPLE_CLASS(SentryId) alloc] initWithUUIDString:traceParts[0].GetNSString()]; #endif SentryTransactionContext* transactionContext = [[SENTRY_APPLE_CLASS(SentryTransactionContext) alloc] initWithName:@"" operation:@"default" traceId:traceId spanId:[[SENTRY_APPLE_CLASS(SentrySpanId) alloc] init] parentSpanId:[[SENTRY_APPLE_CLASS(SentrySpanId) alloc] initWithValue:traceParts[1].GetNSString()] parentSampled:sampleDecision]; // currently `sentry-cocoa` doesn't have API for `SentryTransactionContext` to set `baggageHeaders` return MakeShareable(new SentryTransactionContextApple(transactionContext)); }