Skip to content
Closed
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## Features

- Added `SentrySdk.CrashedLastRun()`. Users can now retrieve the `LastRunState` ([#4025](https://github.com/getsentry/sentry-dotnet/pull/4025))

### Fixes

- Using SentryOptions.Native.SuppressExcBadAccess and SentryOptions.Native.SuppressSignalAborts, users can now block duplicate errors from native due to dotnet NullReferenceExceptions - Defaults to false ([#3998](https://github.com/getsentry/sentry-dotnet/pull/3998))
Expand Down
6 changes: 6 additions & 0 deletions src/Sentry/Extensibility/DisabledHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ public SentryId CaptureCheckIn(
Action<SentryMonitorOptions>? configureMonitorOptions = null)
=> SentryId.Empty;

/// <summary>
/// No-Op
/// </summary>
public CrashedLastRun CrashedLastRun()
=> Sentry.CrashedLastRun.Unknown;

/// <summary>
/// No-Op.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Sentry/Extensibility/HubAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,12 @@ public SentryId CaptureCheckIn(
Action<SentryMonitorOptions>? monitorOptions = null)
=> SentrySdk.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope, monitorOptions);

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>.
/// </summary>
public CrashedLastRun CrashedLastRun()
=> SentrySdk.CrashedLastRun();

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Sentry/IHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,11 @@ TransactionContext ContinueTrace(
/// <param name="configureScope">The callback to configure the scope.</param>
/// <returns></returns>
public SentryId CaptureEvent(SentryEvent evt, SentryHint? hint, Action<Scope> configureScope);

/// <summary>
/// Retrieves the crash state of the previous application run.
/// This indicates whether the application terminated normally or crashed.
/// </summary>
/// <returns><see cref="CrashedLastRun"/> indicating the state of the previous run.</returns>
public CrashedLastRun CrashedLastRun();
}
20 changes: 20 additions & 0 deletions src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,26 @@ public SentryId CaptureCheckIn(
return SentryId.Empty;
}


public CrashedLastRun CrashedLastRun()
{
if (!IsEnabled)
{
return Sentry.CrashedLastRun.Unknown;
}

if (_options.CrashedLastRun is null)
{
_options.DiagnosticLogger?.LogDebug("The SDK does not have a 'CrashedLastRun' set. " +
"This might be due to a missing or disabled native integration.");
return Sentry.CrashedLastRun.Unknown;
}

return _options.CrashedLastRun.Invoke()
? Sentry.CrashedLastRun.Crashed
: Sentry.CrashedLastRun.DidNotCrash;
}

public async Task FlushAsync(TimeSpan timeout)
{
try
Expand Down
24 changes: 24 additions & 0 deletions src/Sentry/LastRunState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Sentry;

/// <summary>
/// Represents the crash state of the games's previous run.
/// Used to determine if the last execution terminated normally or crashed.
/// </summary>
public enum CrashedLastRun
{
/// <summary>
/// The LastRunState is unknown. This might be due to the SDK not being initialized, native crash support
/// missing, or being disabled.
/// </summary>
Unknown,

/// <summary>
/// The application did not crash during the last run.
/// </summary>
DidNotCrash,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In .NET (JIT-land) we might return DidNotCrash when in fact the app crashed natively. Since we don't capture native crashes when running on JIT on Windows/Mac/Linux. We do capture native crashes Native AOT, or JIT on Android, for example.


/// <summary>
/// The application crashed during the last run.
/// </summary>
Crashed
}
5 changes: 5 additions & 0 deletions src/Sentry/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,11 @@ public static void PauseSession()
public static void ResumeSession()
=> CurrentHub.ResumeSession();

/// <inheritdoc cref="IHub.CrashedLastRun"/>
[DebuggerStepThrough]
public static CrashedLastRun CrashedLastRun()
=> CurrentHub.CrashedLastRun();

/// <summary>
/// Deliberately crashes an application, which is useful for testing and demonstration purposes.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ namespace Sentry
ManagedBackgroundThread = 1,
Native = 2,
}
public enum CrashedLastRun
{
Unknown = 0,
DidNotCrash = 1,
Crashed = 2,
}
[System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")]
public class Debouncer
{
Expand Down Expand Up @@ -214,6 +220,7 @@ namespace Sentry
Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action<Sentry.Scope> configureScope);
Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null);
Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null);
Sentry.CrashedLastRun CrashedLastRun();
void EndSession(Sentry.SessionEndStatus status = 0);
Sentry.BaggageHeader? GetBaggage();
Sentry.ISpan? GetSpan();
Expand Down Expand Up @@ -841,6 +848,7 @@ namespace Sentry
public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public static Sentry.CrashedLastRun CrashedLastRun() { }
public static void EndSession(Sentry.SessionEndStatus status = 0) { }
public static void Flush() { }
public static void Flush(System.TimeSpan timeout) { }
Expand Down Expand Up @@ -1342,6 +1350,7 @@ namespace Sentry.Extensibility
public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.CrashedLastRun CrashedLastRun() { }
public void Dispose() { }
public void EndSession(Sentry.SessionEndStatus status = 0) { }
public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { }
Expand Down Expand Up @@ -1388,6 +1397,7 @@ namespace Sentry.Extensibility
public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.CrashedLastRun CrashedLastRun() { }
public void EndSession(Sentry.SessionEndStatus status = 0) { }
public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { }
public Sentry.BaggageHeader? GetBaggage() { }
Expand Down
10 changes: 10 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ namespace Sentry
ManagedBackgroundThread = 1,
Native = 2,
}
public enum CrashedLastRun
{
Unknown = 0,
DidNotCrash = 1,
Crashed = 2,
}
[System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")]
public class Debouncer
{
Expand Down Expand Up @@ -214,6 +220,7 @@ namespace Sentry
Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action<Sentry.Scope> configureScope);
Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null);
Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null);
Sentry.CrashedLastRun CrashedLastRun();
void EndSession(Sentry.SessionEndStatus status = 0);
Sentry.BaggageHeader? GetBaggage();
Sentry.ISpan? GetSpan();
Expand Down Expand Up @@ -841,6 +848,7 @@ namespace Sentry
public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public static Sentry.CrashedLastRun CrashedLastRun() { }
public static void EndSession(Sentry.SessionEndStatus status = 0) { }
public static void Flush() { }
public static void Flush(System.TimeSpan timeout) { }
Expand Down Expand Up @@ -1342,6 +1350,7 @@ namespace Sentry.Extensibility
public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.CrashedLastRun CrashedLastRun() { }
public void Dispose() { }
public void EndSession(Sentry.SessionEndStatus status = 0) { }
public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { }
Expand Down Expand Up @@ -1388,6 +1397,7 @@ namespace Sentry.Extensibility
public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.CrashedLastRun CrashedLastRun() { }
public void EndSession(Sentry.SessionEndStatus status = 0) { }
public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { }
public Sentry.BaggageHeader? GetBaggage() { }
Expand Down
10 changes: 10 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ namespace Sentry
Managed = 0,
ManagedBackgroundThread = 1,
}
public enum CrashedLastRun
{
Unknown = 0,
DidNotCrash = 1,
Crashed = 2,
}
[System.Flags]
public enum DeduplicateMode
{
Expand Down Expand Up @@ -202,6 +208,7 @@ namespace Sentry
Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.SentryHint? hint, System.Action<Sentry.Scope> configureScope);
Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null);
Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null);
Sentry.CrashedLastRun CrashedLastRun();
void EndSession(Sentry.SessionEndStatus status = 0);
Sentry.BaggageHeader? GetBaggage();
Sentry.ISpan? GetSpan();
Expand Down Expand Up @@ -822,6 +829,7 @@ namespace Sentry
public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public static Sentry.CrashedLastRun CrashedLastRun() { }
public static void EndSession(Sentry.SessionEndStatus status = 0) { }
public static void Flush() { }
public static void Flush(System.TimeSpan timeout) { }
Expand Down Expand Up @@ -1323,6 +1331,7 @@ namespace Sentry.Extensibility
public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.CrashedLastRun CrashedLastRun() { }
public void Dispose() { }
public void EndSession(Sentry.SessionEndStatus status = 0) { }
public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { }
Expand Down Expand Up @@ -1369,6 +1378,7 @@ namespace Sentry.Extensibility
public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func<Sentry.Scope, System.Threading.Tasks.Task> configureScope) { }
public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { }
public Sentry.CrashedLastRun CrashedLastRun() { }
public void EndSession(Sentry.SessionEndStatus status = 0) { }
public System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout) { }
public Sentry.BaggageHeader? GetBaggage() { }
Expand Down
67 changes: 67 additions & 0 deletions test/Sentry.Tests/HubTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,73 @@ public void CaptureTransaction_Client_Gets_Hint()
_fixture.Client.Received().CaptureTransaction(Arg.Any<SentryTransaction>(), Arg.Any<Scope>(), Arg.Any<SentryHint>());
}

[Fact]
public void GetLastRunState_WithoutInit_ReturnsUnknown()
{
// Make sure SDK is closed
SentrySdk.Close();

// Act
var result = SentrySdk.CrashedLastRun();

// Assert
Assert.Equal(CrashedLastRun.Unknown, result);
}

[Fact]
public void GetLastRunState_WhenCrashed_ReturnsCrashed()
{
// Arrange
var options = new SentryOptions
{
Dsn = ValidDsn,
CrashedLastRun = () => true // Mock crashed state
};

// Act
SentrySdk.Init(options);
var result = SentrySdk.CrashedLastRun();

// Assert
Assert.Equal(CrashedLastRun.Crashed, result);
}

[Fact]
public void GetLastRunState_WhenNotCrashed_ReturnsDidNotCrash()
{
// Arrange
var options = new SentryOptions()
{
Dsn = ValidDsn,
CrashedLastRun = () => false // Mock non-crashed state
};

// Act
SentrySdk.Init(options);
var result = SentrySdk.CrashedLastRun();

// Assert
Assert.Equal(CrashedLastRun.DidNotCrash, result);
}

[Fact]
public void GetLastRunState_WithNullDelegate_ReturnsUnknown()
{
// Arrange
var options = new SentryOptions
{
Dsn = ValidDsn,
CrashedLastRun = null // Explicitly set to null
};

// Act
SentrySdk.Init(options);
var result = SentrySdk.CrashedLastRun();

// Assert
Assert.Equal(CrashedLastRun.Unknown, result);
}

[SkippableTheory]
[InlineData(false)]
[InlineData(true)]
Expand Down
Loading