Skip to content

fix: Screenshot Capture #2240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 49 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7c90568
initial idea
bitsandfoxes Jul 10, 2025
98a7988
Format code
getsentry-bot Jul 10, 2025
a3a70a4
Merge branch 'main' into fix/screenshot-capture
bitsandfoxes Jul 15, 2025
f204618
Merge branch 'fix/screenshot-capture' of https://github.com/getsentry…
bitsandfoxes Jul 15, 2025
8f74147
capture and send screenshot
bitsandfoxes Jul 16, 2025
8f852e0
merged main
bitsandfoxes Jul 16, 2025
35a172d
bumped implementation in .NET
bitsandfoxes Jul 18, 2025
11548fe
updated to make it work with tests
bitsandfoxes Jul 18, 2025
91aa77f
Format code
getsentry-bot Jul 18, 2025
113e40e
moved screenshot capture logic back into the processor
bitsandfoxes Jul 18, 2025
271ef13
cleanup
bitsandfoxes Jul 18, 2025
c4c5a96
Updated CHANGELOG.md
bitsandfoxes Jul 18, 2025
7e183a1
added delay in tests to fix flake
bitsandfoxes Jul 21, 2025
41d69fc
updated the smoketester to handle the delayed screenshot capture
bitsandfoxes Jul 21, 2025
521298d
updated success/fail checks
bitsandfoxes Jul 21, 2025
8dfd665
more CI work
bitsandfoxes Jul 21, 2025
e2e83a8
bumped .NET SDK
bitsandfoxes Jul 21, 2025
072d2d3
.
bitsandfoxes Jul 21, 2025
176de44
cleaned up the smoketester
bitsandfoxes Jul 21, 2025
778e03d
.
bitsandfoxes Jul 21, 2025
34b03bd
increased smoke test run timeout
bitsandfoxes Jul 21, 2025
f4ed1fa
waitforseconds for smoketester instead
bitsandfoxes Jul 22, 2025
38d2738
debug logs
bitsandfoxes Jul 22, 2025
9015cd5
no coroutine for smoketesting no more
bitsandfoxes Jul 22, 2025
4e634d1
increase http request interception timeout
bitsandfoxes Jul 22, 2025
a9dfcb1
it needs to be a coroutine
bitsandfoxes Jul 22, 2025
e05a579
if waitfor does not work what about yield null?
bitsandfoxes Jul 22, 2025
26be76b
force opengl on linux
bitsandfoxes Jul 22, 2025
1cd0c2c
keep focus
bitsandfoxes Jul 22, 2025
b8fdb41
smoke tester cleanup
bitsandfoxes Jul 22, 2025
2e24049
is it the focus thing?
bitsandfoxes Jul 22, 2025
8043f3c
one more time. does it really require the sentrymonobehaviour?
bitsandfoxes Jul 22, 2025
6c9b8c8
going with buildtime settings here
bitsandfoxes Jul 22, 2025
c0f38f0
final pass
bitsandfoxes Jul 22, 2025
73d3589
chore: Added Unity `6.1` to CI (#2130)
bitsandfoxes Jul 22, 2025
8d675e7
Update src/Sentry.Unity/ScreenshotEventProcessor.cs
bitsandfoxes Jul 24, 2025
3a704c1
Update src/Sentry.Unity/SentryUnitySdk.cs
bitsandfoxes Jul 24, 2025
172e8ae
Update src/Sentry.Unity/ScreenshotEventProcessor.cs
bitsandfoxes Jul 24, 2025
bb18109
Format code
getsentry-bot Jul 24, 2025
e0856f6
fixed broken commits & added editor check before adding integration
bitsandfoxes Jul 24, 2025
65516a1
fixed broken merge
bitsandfoxes Jul 24, 2025
a2fe576
Format code
getsentry-bot Jul 24, 2025
240d0ba
Merge branch 'main' into fix/screenshot-capture
bitsandfoxes Jul 24, 2025
7910b8c
merged main
bitsandfoxes Jul 24, 2025
060c3c5
Updated the processor to have virtual functions for testing
bitsandfoxes Jul 24, 2025
d3615e7
builder cleanup
bitsandfoxes Jul 24, 2025
e132867
Fixed name
bitsandfoxes Jul 24, 2025
12e4315
Handle empty byte array
bitsandfoxes Jul 24, 2025
6c7695a
Fix processname
bitsandfoxes Jul 24, 2025
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
18 changes: 9 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
uses: ./.github/workflows/smoke-test-create.yml
with:
unity-version: ${{ matrix.unity-version }}
Expand All @@ -101,7 +101,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
platform: ["WebGL", "Linux"]
include:
- platform: WebGL
Expand Down Expand Up @@ -205,7 +205,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
uses: ./.github/workflows/smoke-test-build-android.yml
with:
unity-version: ${{ matrix.unity-version }}
Expand All @@ -224,7 +224,7 @@ jobs:
matrix:
api-level: [30, 31, 34] # last updated January 2025
init-type: ["runtime", "buildtime"]
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]

smoke-test-build-ios:
name: Build iOS ${{ matrix.unity-version }} Smoke Test
Expand All @@ -234,7 +234,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
uses: ./.github/workflows/smoke-test-build-ios.yml
with:
unity-version: ${{ matrix.unity-version }}
Expand All @@ -247,7 +247,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
init-type: ["runtime", "buildtime"]
uses: ./.github/workflows/smoke-test-compile-ios.yml
with:
Expand All @@ -266,7 +266,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
# Check https://support.apple.com/en-us/HT201222 for the latest minor version for a given major one.
# https://developer.apple.com/support/app-store/ shows that of all iOS devices
# - `iOS 17`: 86 %
Expand All @@ -287,7 +287,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
platform: ["WebGL", "Linux"]
steps:
- name: Checkout
Expand Down Expand Up @@ -325,7 +325,7 @@ jobs:
strategy:
fail-fast: false
matrix:
unity-version: ["2020", "2022", "6000"]
unity-version: ["2020", "2022", "6000", "6100"]
# os: ["windows", "macos"]
os: ["windows"]
include:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
customize it by creating your own variant. The user feedback feature allows your users to provide feedback in form
of a written message that can optionally have a screenshot attached. Read more about it ([here](https://docs.sentry.io/product/user-feedback/)). ([#2220](https://github.com/getsentry/sentry-unity/pull/2220))

### Fixes

- The SDK now waits for 'End of Frame' before capturing a screenshot. This should address any blank or malformed
screenshots previously attached to events. The SDK now also only captures one screenshot for the first error event in
each individual frame. ([#2240](https://github.com/getsentry/sentry-unity/pull/2240))

### Dependencies

- Bump Java SDK from v8.14.0 to v8.17.0 ([#2218](https://github.com/getsentry/sentry-unity/pull/2218), [#2223](https://github.com/getsentry/sentry-unity/pull/2223), [#2238](https://github.com/getsentry/sentry-unity/pull/2238))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ MonoBehaviour:
<ReleaseOverride>k__BackingField:
<EnvironmentOverride>k__BackingField:
<AttachStacktrace>k__BackingField: 1
<AttachScreenshot>k__BackingField: 0
<AttachScreenshot>k__BackingField: 1
<ScreenshotQuality>k__BackingField: 1
<ScreenshotCompression>k__BackingField: 75
<AttachViewHierarchy>k__BackingField: 0
<AttachViewHierarchy>k__BackingField: 1
<MaxViewHierarchyRootObjects>k__BackingField: 100
<MaxViewHierarchyObjectChildCount>k__BackingField: 20
<MaxViewHierarchyDepth>k__BackingField: 10
Expand Down
3 changes: 3 additions & 0 deletions scripts/ci-env.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ switch ($name) {
"unity6000" {
return "6000.0.49f1"
}
"unity6100" {
return "6000.1.12f1"
}
Default {
throw "Unkown variable '$name'"
}
Expand Down
83 changes: 47 additions & 36 deletions src/Sentry.Unity/ScreenshotEventProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,64 +1,75 @@
using System;
using System.Collections;
using System.Threading;
using Sentry.Extensibility;
using Sentry.Unity.Integrations;
using Sentry.Internal;
using UnityEngine;

namespace Sentry.Unity;

public class ScreenshotEventProcessor : ISentryEventProcessorWithHint
public class ScreenshotEventProcessor : ISentryEventProcessor
{
private readonly SentryUnityOptions _options;
private readonly IApplication _application;
public ScreenshotEventProcessor(SentryUnityOptions sentryOptions) : this(sentryOptions, null) { }
private readonly ISentryMonoBehaviour _sentryMonoBehaviour;
private volatile int _isCapturingScreenshot;

internal ScreenshotEventProcessor(SentryUnityOptions sentryOptions, IApplication? application)
internal Func<SentryUnityOptions, byte[]> ScreenshotCaptureFunction = SentryScreenshot.Capture;
internal Action<SentryId, SentryAttachment> AttachmentCaptureFunction = (eventId, attachment) =>
(Sentry.SentrySdk.CurrentHub as Hub)?.CaptureAttachment(eventId, attachment);
internal Func<YieldInstruction> WaitForEndOfFrameFunction = () => new WaitForEndOfFrame();

public ScreenshotEventProcessor(SentryUnityOptions sentryOptions) : this(sentryOptions, SentryMonoBehaviour.Instance) { }

internal ScreenshotEventProcessor(SentryUnityOptions sentryOptions, ISentryMonoBehaviour sentryMonoBehaviour)
{
_options = sentryOptions;
_application = application ?? ApplicationAdapter.Instance;
_sentryMonoBehaviour = sentryMonoBehaviour;
}

public SentryEvent? Process(SentryEvent @event)
public SentryEvent Process(SentryEvent @event)
{
// Only ever capture one screenshot per frame
if (Interlocked.CompareExchange(ref _isCapturingScreenshot, 1, 0) == 0)
{
_sentryMonoBehaviour.StartCoroutine(CaptureScreenshotCoroutine(@event.EventId));
}

return @event;
}

public SentryEvent? Process(SentryEvent @event, SentryHint hint)
internal IEnumerator CaptureScreenshotCoroutine(SentryId eventId)
{
// save event id
// wait for end of frame
// check if last id is event it
// send screenshot
_options.LogDebug("Screenshot capture triggered. Waiting for End of Frame.");

// add workitem: screentshot for ID xxx
// sdk integration checking for work: if ID got sent, follow up with screenshot
// WaitForEndOfFrame does not work in headless mode so we're making it configurable for CI.
// See https://docs.unity3d.com/6000.1/Documentation/ScriptReference/WaitForEndOfFrame.html
yield return WaitForEndOfFrameFunction();

if (!MainThreadData.IsMainThread())
try
{
_options.DiagnosticLogger?.LogDebug("Screenshot capture skipped. Can't capture screenshots on other than the main thread.");
return @event;
}

if (_options.BeforeCaptureScreenshotInternal?.Invoke() is not false)
{
if (_application.IsEditor)
if (_options.BeforeCaptureScreenshotInternal?.Invoke() is false)
{
_options.DiagnosticLogger?.LogInfo("Screenshot capture skipped. Capturing screenshots it not supported in the Editor");
return @event;
yield break;
}

if (Screen.width == 0 || Screen.height == 0)
{
_options.DiagnosticLogger?.LogWarning("Can't capture screenshots on a screen with a resolution of '{0}x{1}'.", Screen.width, Screen.height);
}
else
{
hint.AddAttachment(SentryScreenshot.Capture(_options), "screenshot.jpg", contentType: "image/jpeg");
}
var screenshotBytes = ScreenshotCaptureFunction(_options);
var attachment = new SentryAttachment(
AttachmentType.Default,
new ByteAttachmentContent(screenshotBytes),
"screenshot.jpg",
"image/jpeg");

_options.LogDebug("Screenshot captured for event {0}", eventId);

AttachmentCaptureFunction(eventId, attachment);
}
else
catch (Exception e)
{
_options.DiagnosticLogger?.LogInfo("Screenshot capture skipped by BeforeAttachScreenshot callback.");
_options.LogError(e, "Failed to capture screenshot.");
}
finally
{
Interlocked.Exchange(ref _isCapturingScreenshot, 0);
}

return @event;
}
}
2 changes: 2 additions & 0 deletions src/Sentry.Unity/SentryMonoBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using Sentry.Unity.Integrations;
using UnityEngine;

Expand All @@ -7,6 +8,7 @@ namespace Sentry.Unity;
internal interface ISentryMonoBehaviour
{
event Action? ApplicationResuming;
public Coroutine StartCoroutine(IEnumerator routine);
}

/// <summary>
Expand Down
1 change: 0 additions & 1 deletion src/Sentry.Unity/SentrySdk.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.ComponentModel;
using Sentry.Extensibility;
using Sentry.Unity.NativeUtils;

namespace Sentry.Unity;

Expand Down
23 changes: 22 additions & 1 deletion src/Sentry.Unity/SentryUnitySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.Threading.Tasks;
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Unity.Integrations;
using UnityEngine;

Expand Down Expand Up @@ -126,6 +127,26 @@ public void CaptureFeedback(string message, string? email, string? name, bool ad
Sentry.SentrySdk.CurrentHub.CaptureFeedback(message, email, name, hint: hint);
}

internal void CaptureAttachment(SentryId eventId, SentryAttachment attachment)
{
try
{
if (Sentry.SentrySdk.CurrentHub is Hub hub)
{
hub.CaptureAttachment(eventId, attachment);
_options.LogDebug("Attachment captured for event {0}", eventId);
}
else
{
_options.LogError("Capturing attachment was not possible because the current hub is of type {0}.", Sentry.SentrySdk.CurrentHub?.GetType());
}
}
catch (Exception ex)
{
_options.DiagnosticLogger?.LogError(ex, "Failed to capture attachment for event {0}", eventId);
}
}

internal static void SetUpWindowsPlayerCaching(SentryUnitySdk unitySdk, SentryUnityOptions options)
{
// On Windows-Standalone, we disable cache dir in case multiple app instances run over the same path.
Expand Down Expand Up @@ -157,7 +178,7 @@ internal static void AddIntegrations(SentryUnityOptions options)
{
options.AddEventProcessor(new ViewHierarchyEventProcessor(options));
}
if (options.AttachScreenshot)
if (!ApplicationAdapter.Instance.IsEditor && options.AttachScreenshot)
{
options.AddEventProcessor(new ScreenshotEventProcessor(options));
}
Expand Down
10 changes: 2 additions & 8 deletions test/Scripts.Integration.Test/Editor/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
EditorUserBuildSettings.selectedBuildTargetGroup = group;
EditorUserBuildSettings.allowDebugging = false;
PlayerSettings.SetScriptingBackend(group, ScriptingImplementation.IL2CPP);
// Making sure that the app keeps on running in the background. Linux CI is very unhappy with coroutines otherwise.
PlayerSettings.runInBackground = true;

DisableUnityAudio();
DisableProgressiveLightMapper();
Expand All @@ -41,14 +43,6 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
PlayerSettings.SetManagedStrippingLevel(group, ManagedStrippingLevel.Low);
#endif


// This is a workaround for build issues with Unity 2022.3. and newer.
// https://discussions.unity.com/t/gradle-build-issues-for-android-api-sdk-35-in-unity-2022-3lts/1502187/10
#if UNITY_2022_3_OR_NEWER
Debug.Log("Builder: Setting Android target API level to 33");
PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevel33;
#endif

Comment on lines -44 to -51
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workaround that is no longer needed.

Debug.Log("Builder: Updating BuildPlayerOptions");
var buildPlayerOptions = new BuildPlayerOptions
{
Expand Down
11 changes: 11 additions & 0 deletions test/Scripts.Integration.Test/Scripts/OptionsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Sentry;
using Sentry.Unity;
using UnityEngine;
Expand Down Expand Up @@ -37,6 +38,16 @@ public override void Configure(SentryUnityOptions options)
return -2;
};

// Filtering the SmokeTester logs from the breadcrumbs here
options.AddBreadcrumbsForLogType = new Dictionary<LogType, bool>
{
{ LogType.Error, true},
{ LogType.Assert, true},
{ LogType.Warning, true},
{ LogType.Log, false}, // No breadcrumbs for Debug.Log
{ LogType.Exception, true},
};

// If an ANR triggers while the smoke test runs, the test would fail because we expect exact order of events.
options.DisableAnrIntegration();

Expand Down
Loading