349 lines
13 KiB
C++
349 lines
13 KiB
C++
// 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<USentryEvent>();
|
|
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<USentrySamplingContext>();
|
|
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<ISentryBreadcrumb> breadcrumb)
|
|
{
|
|
TSharedPtr<SentryBreadcrumbApple> breadcrumbIOS = StaticCastSharedPtr<SentryBreadcrumbApple>(breadcrumb);
|
|
|
|
[SENTRY_APPLE_CLASS(SentrySDK) addBreadcrumb:breadcrumbIOS->GetNativeObject()];
|
|
}
|
|
|
|
void SentrySubsystemApple::AddBreadcrumbWithParams(const FString& Message, const FString& Category, const FString& Type, const TMap<FString, FString>& Data, ESentryLevel Level)
|
|
{
|
|
TSharedPtr<SentryBreadcrumbApple> 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<ISentryId> 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<ISentryId> 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<ISentryId> SentrySubsystemApple::CaptureEvent(TSharedPtr<ISentryEvent> event)
|
|
{
|
|
TSharedPtr<SentryEventApple> eventIOS = StaticCastSharedPtr<SentryEventApple>(event);
|
|
|
|
SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject()];
|
|
return MakeShareable(new SentryIdApple(id));
|
|
}
|
|
|
|
TSharedPtr<ISentryId> SentrySubsystemApple::CaptureEventWithScope(TSharedPtr<ISentryEvent> event, const FSentryScopeDelegate& onConfigureScope)
|
|
{
|
|
TSharedPtr<SentryEventApple> eventIOS = StaticCastSharedPtr<SentryEventApple>(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<ISentryId> 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<ISentryId> 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<ISentryId> 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<ISentryUserFeedback> userFeedback)
|
|
{
|
|
TSharedPtr<SentryUserFeedbackApple> userFeedbackIOS = StaticCastSharedPtr<SentryUserFeedbackApple>(userFeedback);
|
|
|
|
[SENTRY_APPLE_CLASS(SentrySDK) captureUserFeedback:userFeedbackIOS->GetNativeObject()];
|
|
}
|
|
|
|
void SentrySubsystemApple::SetUser(TSharedPtr<ISentryUser> user)
|
|
{
|
|
TSharedPtr<SentryUserApple> userIOS = StaticCastSharedPtr<SentryUserApple>(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<FString, FString>& 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<ISentryTransaction> SentrySubsystemApple::StartTransaction(const FString& name, const FString& operation)
|
|
{
|
|
id<SentrySpan> transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithName:name.GetNSString() operation:operation.GetNSString()];
|
|
|
|
return MakeShareable(new SentryTransactionApple(transaction));
|
|
}
|
|
|
|
TSharedPtr<ISentryTransaction> SentrySubsystemApple::StartTransactionWithContext(TSharedPtr<ISentryTransactionContext> context)
|
|
{
|
|
TSharedPtr<SentryTransactionContextApple> transactionContextIOS = StaticCastSharedPtr<SentryTransactionContextApple>(context);
|
|
|
|
id<SentrySpan> transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithContext:transactionContextIOS->GetNativeObject()];
|
|
|
|
return MakeShareable(new SentryTransactionApple(transaction));
|
|
}
|
|
|
|
TSharedPtr<ISentryTransaction> SentrySubsystemApple::StartTransactionWithContextAndTimestamp(TSharedPtr<ISentryTransactionContext> context, int64 timestamp)
|
|
{
|
|
UE_LOG(LogSentrySdk, Log, TEXT("Setting transaction timestamp explicitly not supported on Mac/iOS."));
|
|
return StartTransactionWithContext(context);
|
|
}
|
|
|
|
TSharedPtr<ISentryTransaction> SentrySubsystemApple::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options)
|
|
{
|
|
TSharedPtr<SentryTransactionContextApple> transactionContextIOS = StaticCastSharedPtr<SentryTransactionContextApple>(context);
|
|
|
|
id<SentrySpan> transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithContext:transactionContextIOS->GetNativeObject()
|
|
customSamplingContext:SentryConvertorsApple::StringMapToNative(options)];
|
|
|
|
return MakeShareable(new SentryTransactionApple(transaction));
|
|
}
|
|
|
|
TSharedPtr<ISentryTransactionContext> SentrySubsystemApple::ContinueTrace(const FString& sentryTrace, const TArray<FString>& baggageHeaders)
|
|
{
|
|
TArray<FString> 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:@"<unlabeled transaction>" 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));
|
|
}
|