Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions .github/workflows/test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ jobs:
env:
EDITOR_BINARY: ${{ inputs.unreal-version == '4.27' && 'UE4Editor' || 'UnrealEditor' }}
run: |
docker exec -w /workspace/checkout/sample unreal bash -c "
ls -al /workspace/checkout/sample/Plugins/sentry "
docker exec -w /workspace/checkout/sample unreal /home/ue4/UnrealEngine/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
-project=/workspace/checkout/sample/SentryPlayground.uproject \
-archivedirectory=/workspace/checkout/sample/Builds \
Expand All @@ -131,8 +129,6 @@ jobs:
-prereqss \
-package \
-archive
docker exec -w /workspace/checkout/sample unreal bash -c "
cp -r '/home/gh/Library/Logs/Unreal Engine/LocalBuildLogs' Saved/Logs "
docker exec -w /workspace/checkout/sample unreal /home/ue4/UnrealEngine/Engine/Binaries/Linux/"$EDITOR_BINARY" \
/workspace/checkout/sample/SentryPlayground.uproject \
-ReportExportPath=/workspace/checkout/sample/Saved/Automation \
Expand All @@ -147,17 +143,14 @@ jobs:
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
uses: actions/upload-artifact@v4
with:
name: UE ${{ inputs.unreal-version }} sample test report
name: UE ${{ inputs.unreal-version }} sample test report (Linux)
path: |
checkout/sample/Saved/Automation

- name: Collect sample build info
if: contains(fromJson('["success", "failure"]'), steps.run-tests.outcome)
- name: Upload sample build
if: ${{ success() && steps.run-tests.outcome == 'success' }}
uses: actions/upload-artifact@v4
with:
name: UE ${{ inputs.unreal-version }} sample build logs
path: |
checkout/sample/Saved/Logs
checkout/sample/Saved/Stats
checkout/sample/Saved/MaterialStats
checkout/sample/Saved/MaterialStatsDebug
name: UE ${{ inputs.unreal-version }} sample build (Linux)
path: checkout/sample/Builds/${{ inputs.unreal-version == '4.27' && 'LinuxNoEditor' || 'Linux' }}/
retention-days: 1
19 changes: 18 additions & 1 deletion .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ env:
jobs:
test:
name: Test
runs-on: windows-latest
runs-on: windows-2022

steps:
- name: Log in to GitHub package registry
Expand Down Expand Up @@ -79,3 +79,20 @@ jobs:
-NoPause `
-NoSplash `
-NullRHI

- name: Collect sample test info
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
uses: actions/upload-artifact@v4
with:
name: UE ${{ inputs.unreal-version }} sample test report (Windows)
path: |
checkout/sample/Saved/Automation

- name: Upload sample build
if: ${{ success() && steps.run-tests.outcome == 'success' }}
uses: actions/upload-artifact@v4
with:
name: UE ${{ inputs.unreal-version }} sample build (Windows)
path: checkout/sample/Builds/${{ inputs.unreal-version == '4.27' && 'WindowsNoEditor' || 'Windows' }}/
retention-days: 1

5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,9 @@ In order to run the demo level navigate to `Content Browser -> Content -> Maps`

## Modifying plugin content

All files that belong to the plugin are listed in the snapshot files:
All files that belong to the plugin are listed in the snapshot file:

- `/scripts/packaging/package-github.snapshot` (for the GitHub package)
- `/scripts/packaging/package-marketplace.snapshot` (for the Marketplace package)
- `/scripts/packaging/package.snapshot`

If you add, delete or move files within the `plugin-dev` directory these snapshot files must be updated to reflect the changes. To do that, run:

Expand Down
Binary file not shown.
Binary file not shown.
91 changes: 91 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<p align="center">
<a href="https://sentry.io" target="_blank" align="left">
<img src="https://raw.githubusercontent.com/getsentry/sentry-unity/main/.github/sentry-wordmark-dark-400x119.svg" width="280">
</a>
<br />
</p>
<p align="center">

Sentry Unreal Engine SDK Sample Project
===========

This sample project demonstrates the capabilities of the Sentry Unreal Engine SDK and provides a comprehensive testing environment for all SDK features.

## Getting started

### Prerequisites

- Unreal Engine 4.27 or newer (version configured in `SentryPlayground.uproject`)
- Platform support: Windows, macOS, Linux, Android, iOS, PlayStation 5, Xbox, Nintendo Switch
- Sentry account with a project and DSN

### Setup

- Clone or download the Sentry Unreal SDK repository
- Copy or symlink the Sentry plugin sources to `sample/Plugins/Sentry/`
- Open `SentryPlayground.uproject` in Unreal Engine
- Configure your Sentry DSN in `Config/DefaultEngine.ini` or through the project settings menu
- Play the `SentryDemo` level to begin testing

## Project structure overview

Here's a breakdown of the important sample project files and folders:

```pwsh
📁 sample
├── 📄 SentryPlayground.uproject # Engine version configuration, supports UE 4.27 and newer
├── 📁 Source/
│ └── 📁 SentryPlayground/
│ ├── 📄 SentryPlaygroundGameInstance.cpp/.h # Logic for running integration tests
│ ├── 📄 SentryPlaygroundUtils.cpp/.h # Utilities for triggering different types of crashes
│ ├── 📄 CppBeforeSendHandler.cpp/.h # Example C++ implementation of `beforeSend` hook handler
│ └── 📄 SentryGCCallback.cpp/.h # Utility for capturing events during garbage collection
├── 📁 Content/
│ ├── 📁 Maps/
│ │ └── 📄 SentryDemo.umap # Main demo level)
│ ├── 📁 UI/
│ │ └── 📄 W_SentryDemo.uasset # Demo UI widget for testing SDK features
│ └── 📁 Misc/
│ ├── 📄 BP_BeforeSendHandler.uasset # Example Blueprint implementation of `beforeSend` hook handler
│ ├── 📄 BP_BeforeBreadcrumbHandler.uasset # Example Blueprint implementation of `beforeBreadcrumb` hook handler
│ └── 📄 BP_TraceSampler.uasset # Example Blueprint implementation of traces sampling function
├── 📁 Config/
│ └── 📄 DefaultEngine.ini # Configuration file with Sentry plugin settings
└── 📁 Plugins/ # Location for Sentry SDK sources - copy or symlink here
```

## Demo Level

The demo level (`SentryDemo.umap`) in the project's Content folder presents a simple UI for sending test events to Sentry. The `W_SentryDemo` Blueprint implementation demonstrates how to call the plugin API and serves as a reference.

To run the demo level, navigate to `Content Browser -> Content -> Maps` and open the `SentryDemo` map. Click Play to launch the demo.

## Unit Tests

To run automation tests, several engine plugins are enabled (see `Settings -> Plugins -> Testing`). Navigate to `Windows -> Test Automation` menu and open the `Session Frontend` window. Switch to the `Automation` tab and select `Sentry` from the list of available tests. Click the `Start Tests` button to run the tests and check the results.

## Integration Tests

The `SentryPlaygroundGameInstance.cpp` file contains logic that parses command line input used to launch the sample game build and runs test actions accordingly. Here are example commands:

```pwsh
# Windows - Crash capture test
SentryPlayground.exe -nullrhi -unattended -log -crash-capture -dsn="your-dsn-here"

# Windows - Message capture test
SentryPlayground.exe -nullrhi -unattended -log -message-capture
```

To run integration tests, specify which test to run using the appropriate argument (e.g., `-crash-capture` or `-message-capture`). The game will close after the test is completed. Otherwise, the game will launch as usual and present the sample UI.

Optionally, you can override the DSN for integration tests by adding `-dsn="your-dsn-here"` to the command line. When provided, this DSN will be used instead of the one configured in the project settings.

## Example Content

The sample project contains example Blueprint implementations of various hook handlers under `Content -> Misc`:

- `BP_BeforeSendHandler` - Example Blueprint implementation of event filtering
- `BP_BeforeBreadcrumbHandler` - Custom breadcrumb processing logic
- `BP_TraceSampler` - Performance monitoring sampling configuration

These can be configured for the SDK to use in `Project Settings -> Plugins -> Sentry`.
2 changes: 2 additions & 0 deletions sample/Source/SentryPlayground/SentryPlayground.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
#include "Modules/ModuleManager.h"

IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, SentryPlayground, "SentryPlayground" );

DEFINE_LOG_CATEGORY(LogSentrySample);
2 changes: 2 additions & 0 deletions sample/Source/SentryPlayground/SentryPlayground.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@

#include "CoreMinimal.h"

DECLARE_LOG_CATEGORY_EXTERN(LogSentrySample, Verbose, All);

113 changes: 113 additions & 0 deletions sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,122 @@

#include "SentryPlaygroundGameInstance.h"

#include "SentryPlayground.h"
#include "SentrySubsystem.h"
#include "SentrySettings.h"
#include "SentryPlaygroundUtils.h"
#include "SentryUser.h"

#include "Misc/CommandLine.h"
#include "Engine/Engine.h"

void USentryPlaygroundGameInstance::Init()
{
Super::Init();

const TCHAR* CommandLine = FCommandLine::Get();

// Check for expected test parameters to decide between running integration tests
// or launching the sample app with UI for manual testing
if (FParse::Param(FCommandLine::Get(), TEXT("crash-capture")) ||
FParse::Param(FCommandLine::Get(), TEXT("message-capture")))
{
RunIntegrationTest(CommandLine);
}
}

void USentryPlaygroundGameInstance::RunIntegrationTest(const TCHAR* CommandLine)
{
UE_LOG(LogSentrySample, Log, TEXT("Running integration test for command: %s\n"), CommandLine);

USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem<USentrySubsystem>();
if (!SentrySubsystem)
{
CompleteTestWithResult(TEXT("sentry-error"), false, TEXT("Invalid Sentry subsystem"));
return;
}

SentrySubsystem->InitializeWithSettings(FConfigureSettingsNativeDelegate::CreateLambda([=](USentrySettings* Settings)
{
// Override options set in config file if needed
FString Dsn;
if (FParse::Value(CommandLine, TEXT("dsn="), Dsn))
{
Settings->Dsn = Dsn;
}
}));

if (!SentrySubsystem->IsEnabled())
{
CompleteTestWithResult(TEXT("sentry-error"), false, TEXT("Failed to initialize Sentry"));
return;
}

SentrySubsystem->AddBreadcrumbWithParams(
TEXT("Integration test started"), TEXT("Test"), TEXT("info"), TMap<FString, FSentryVariant>(), ESentryLevel::Info);

ConfigureTestContext();

SentrySubsystem->AddBreadcrumbWithParams(
TEXT("Context configuration finished"), TEXT("Test"), TEXT("info"), TMap<FString, FSentryVariant>(), ESentryLevel::Info);

if (FParse::Param(CommandLine, TEXT("crash-capture")))
{
RunCrashTest();
}
else if (FParse::Param(CommandLine, TEXT("message-capture")))
{
RunMessageTest();
}
}

void USentryPlaygroundGameInstance::RunCrashTest()
{
USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem<USentrySubsystem>();

// Because we don't get the real crash event ID, create a fake one and set it as a tag
// This tag is then used by integration test script in CI to fetch the event

FString EventId = FGuid::NewGuid().ToString(EGuidFormats::Digits);

UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *EventId);

SentrySubsystem->SetTag(TEXT("test.crash_id"), EventId);

USentryPlaygroundUtils::Terminate(ESentryAppTerminationType::NullPointer);
}

void USentryPlaygroundGameInstance::RunMessageTest()
{
USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem<USentrySubsystem>();

FString EventId = SentrySubsystem->CaptureMessage(TEXT("Integration test message"));

UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *EventId);

CompleteTestWithResult(TEXT("message-capture"), !EventId.IsEmpty(), TEXT("Test complete"));
}

void USentryPlaygroundGameInstance::ConfigureTestContext()
{
USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem<USentrySubsystem>();

USentryUser* User = NewObject<USentryUser>();
User->Initialize();
User->SetUsername(TEXT("TestUser"));
User->SetEmail(TEXT("user-mail@test.abc"));
User->SetId(TEXT("12345"));

SentrySubsystem->SetUser(User);

SentrySubsystem->SetTag(TEXT("test.suite"), TEXT("integration"));
}

void USentryPlaygroundGameInstance::CompleteTestWithResult(const FString& TestName, bool Result, const FString& Message)
{
UE_LOG(LogSentrySample, Log, TEXT("TEST_RESULT: {\"test\":\"%s\",\"success\":%s,\"message\":\"%s\"}\n"),
*TestName, Result ? TEXT("true") : TEXT("false"), *Message);

// Close app after test is completed
FGenericPlatformMisc::RequestExit(false);
}
12 changes: 12 additions & 0 deletions sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "SentryPlaygroundUtils.h"
#include "SentrySubsystem.h"
#include "SentryPlaygroundGameInstance.generated.h"

/**
Expand All @@ -14,5 +16,15 @@ class SENTRYPLAYGROUND_API USentryPlaygroundGameInstance : public UGameInstance
{
GENERATED_BODY()

public:
virtual void Init() override;

private:
void RunIntegrationTest(const TCHAR* CommandLine);
void RunCrashTest();
void RunMessageTest();

void ConfigureTestContext();

void CompleteTestWithResult(const FString& TestName, bool Result, const FString& Message);
};