Skip to content
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

Feat: native crash reporting #2887

Merged
merged 38 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3e11be3
feat: sentry-native init & scopes
vaind Nov 22, 2023
a559102
wip: native sdk crash integration tests
vaind Nov 23, 2023
8549b2a
wip: native crash integration test
vaind Nov 23, 2023
293c69c
wip: native stack crash integration test
vaind Nov 23, 2023
3031b3e
linux memset
vaind Nov 23, 2023
ac0c743
Apply suggestions from code review
vaind Nov 24, 2023
1276fdb
chore: update changelog
vaind Nov 24, 2023
e475fe2
fix linux build
vaind Nov 24, 2023
e922153
build issues
vaind Nov 24, 2023
3342217
cleanup
vaind Nov 24, 2023
dbf3828
fix integration tests
vaind Nov 24, 2023
1e849fb
refactor: add Options.PostInitCallbacks
vaind Nov 26, 2023
0aa1c31
add curl as a native dependency on linux/macos
vaind Nov 26, 2023
9e401a2
fix: integration test
vaind Nov 26, 2023
4452f78
chore: update contributing
vaind Nov 26, 2023
6b3cc15
update runtime tests
vaind Nov 27, 2023
6f03698
fix windows test
vaind Nov 27, 2023
ab4aa27
add libcurl on linux
vaind Nov 27, 2023
93587d1
only sync user context if changed
vaind Nov 27, 2023
17f9630
extra serialization
vaind Nov 27, 2023
b0d9911
set cache directory path
vaind Nov 27, 2023
7789249
native log forwarding
vaind Nov 28, 2023
25d69b1
load debug images only once
vaind Nov 28, 2023
15a505f
Merge branch 'main' into feat/native-crash-reporting
vaind Nov 28, 2023
626a6bc
fix non-windows linking
vaind Nov 28, 2023
b59ba77
use System.OperatingSystem.IsWindows()
vaind Nov 28, 2023
0047925
add a native crash sample
vaind Nov 28, 2023
00a0118
Merge branch 'main' into feat/native-crash-reporting
vaind Nov 28, 2023
7cb0f19
chore: update solution filters
vaind Nov 28, 2023
f4e55ba
fixup changelog
vaind Nov 28, 2023
c92ddf8
update sentry.sln
vaind Nov 28, 2023
6ff56e2
disable intial scope sync
vaind Nov 28, 2023
24cbbb9
Apply suggestions from code review
vaind Nov 28, 2023
52afd57
Update src/Sentry/Internal/ContextWriter.cs
vaind Nov 28, 2023
caeaacb
Update integration-test/runtime.Tests.ps1
vaind Nov 28, 2023
30687be
Update src/Sentry/Scope.cs
vaind Nov 28, 2023
2645d0f
Revert "Update src/Sentry/Scope.cs"
vaind Nov 28, 2023
bf0bd8a
Revert "Apply suggestions from code review"
vaind Nov 28, 2023
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
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ jobs:
key: sentry-native-${{ runner.os }}-${{ hashFiles('scripts/build-sentry-native.ps1') }}-${{ hashFiles('.git/modules/modules/sentry-native/HEAD') }}
enableCrossOsArchive: true

- name: Install build dependencies
if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'Linux'
run: |
sudo apt update
sudo apt install libcurl4-openssl-dev

- run: scripts/build-sentry-native.ps1
if: steps.cache.outputs.cache-hit != 'true'
shell: pwsh
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ test_output/
test/**/*.apk
/tools/
*.log
.sentry-native
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

- Native crash reporting on NativeAOT published apps (Windows, Linux, macOS). ([#2887](https://github.com/getsentry/sentry-dotnet/pull/2887))

## 4.0.0-beta.2

### Fixes
Expand Down
62 changes: 48 additions & 14 deletions integration-test/runtime.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ using Sentry.Protocol.Envelopes;
// Initialize the Sentry SDK. (It is not necessary to dispose it.)
SentrySdk.Init(options =>
{
options.Dsn = "http://key@127.0.0.1:9999/123";
options.Dsn = args[0];
options.Debug = true;
options.Transport = new FakeTransport();
});

throw new ApplicationException("Something happened!");
if (args.Length > 1 && !string.IsNullOrEmpty(args[1]))
{
#pragma warning disable CS0618
var crashType = (CrashType)Enum.Parse(typeof(CrashType), args[1]);
SentrySdk.CauseCrash(crashType);
#pragma warning restore CS0618
vaind marked this conversation as resolved.
Show resolved Hide resolved
}

internal class FakeTransport : ITransport
{
Expand All @@ -34,13 +40,6 @@ internal class FakeTransport : ITransport
}
"@ | Out-File $path/Program.cs

# Publish once, then run the executable in actual tests.
dotnet publish console-app -c Release --nologo --framework $framework | ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -ne 0)
{
throw "Failed to publish the test app project."
}

function getConsoleAppPath()
{
if ($IsMacOS)
Expand All @@ -58,13 +57,29 @@ internal class FakeTransport : ITransport
}
}

function runConsoleApp([bool]$IsAOT = $true)
function runConsoleApp([bool]$IsAOT = $true, [string]$CrashType = 'Managed', [string]$Dsn = "http://key@127.0.0.1:9999/123")
{
$executable = $IsAOT ? { & (getConsoleAppPath) } : { dotnet run --project $path -c Release --framework $framework }
if ($IsAOT)
{
$executable = getConsoleAppPath
If (!(Test-Path $executable))
{
dotnet publish console-app -c Release --nologo --framework $framework | ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -ne 0)
{
throw "Failed to publish the test app project."
}
}
}
else
{
$executable = "dotnet run --project $path -c Release --framework $framework"
}
$executable += " $Dsn $CrashType"
Write-Host "::group::Executing $executable"
try
{
$executable.Invoke() | ForEach-Object {
Invoke-Expression $executable | ForEach-Object {
Write-Host " $_"
$_
}
Expand Down Expand Up @@ -101,6 +116,25 @@ internal class FakeTransport : ITransport
It "'dotnet run' produces an app that's recognized as JIT by Sentry" {
runConsoleApp $false | Should -AnyElementMatch 'This looks like a standard JIT/AOT application build.'
}

It "Produces the expected exception (Managed, AOT=<_>)" -ForEach @($true, $false) {
runConsoleApp $_ 'Managed' | Should -AnyElementMatch '{"type":"System.ApplicationException","value":"This exception was caused deliberately by SentrySdk.CauseCrash\(CrashType.Managed\)."'
}

It "Produces the expected exception (Native)" {
# The first run triggers a native error. This error is captured by sentry-native and stored stored for the next run.
runConsoleApp $true 'Native' | Should -AnyElementMatch 'Triggering a deliberate exception'

# On the next run, we use a mock Sentry HTTP server to receive the native crash.
$result = Invoke-SentryServer {
Param([string]$url)
runConsoleApp $true '' ($url.Replace('http://', 'http://key@') + '/123')
}
$result.HasErrors() | Should -BeFalse
$result.ScriptOutput | Should -AnyElementMatch "Native SDK reported: 'crashedLastRun': 'True'"
# TODO the dummy server doesn't seem to receive & print envelopes at the moment...
# $result.ServerStdOut | Should -AnyElementMatch ''
}
}

# This ensures we don't have a regression for https://github.com/getsentry/sentry-dotnet/issues/2825
Expand All @@ -116,11 +150,11 @@ Describe 'Console app regression (missing System.Reflection.Metadata)' {

function runConsoleApp()
{
$executable = { dotnet run --project $path -c Release }
$executable = "dotnet run --project $path -c Release"
Write-Host "::group::Executing $executable"
try
{
$executable.Invoke() | ForEach-Object {
Invoke-Expression $executable | ForEach-Object {
Write-Host " $_"
$_
}
Expand Down
5 changes: 2 additions & 3 deletions scripts/build-sentry-native.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ try

if ($Clean)
{
rm -rf $buildDir
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $buildDir
}

cmake `
Expand All @@ -49,8 +49,7 @@ try
-D CMAKE_BUILD_TYPE=RelWithDebInfo `
-D SENTRY_SDK_NAME=sentry.native.dotnet `
-D SENTRY_BUILD_SHARED_LIBS=0 `
-D SENTRY_BACKEND=none `
-D SENTRY_TRANSPORT=none `
-D SENTRY_BACKEND=inproc `
$additionalArgs

cmake `
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry/CrashType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public enum CrashType
JavaBackgroundThread,
#endif

#if __MOBILE__
#if __MOBILE__ || NET8_0_OR_GREATER
/// <summary>
/// A native operation that will crash the application will be performed by a C library.
/// </summary>
Expand Down
78 changes: 78 additions & 0 deletions src/Sentry/Internal/ContextWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace Sentry.Internal;

/// <summary>
/// Allows synchronizing Context from .NET to native layers.
/// We're providing a single method that the implementations should override.
/// They can choose to either have the single method directly in native using p/invoke,
/// or use a more fine-grained interface, whatever is best for the platform.
/// </summary>
/// <remarks>
/// WriteScope() is called in a new Task (background thread from a pool).
vaind marked this conversation as resolved.
Show resolved Hide resolved
/// </remarks>
internal abstract class ContextWriter
{
public void Write(Scope scope)
{
WriteScope(
scope.Contexts.App.StartTime?.ToString("o"),
scope.Contexts.App.BuildType,
scope.Contexts.OperatingSystem.RawDescription,
scope.Contexts.Device.ProcessorCount,
scope.Contexts.Device.CpuDescription,
scope.Contexts.Device.Timezone?.Id,
scope.Contexts.Device.SupportsVibration,
scope.Contexts.Device.Name,
scope.Contexts.Device.Simulator,
scope.Contexts.Device.DeviceUniqueIdentifier,
scope.Contexts.Device.DeviceType,
scope.Contexts.Device.Model,
scope.Contexts.Device.MemorySize,
scope.Contexts.Gpu.Id,
scope.Contexts.Gpu.Name,
scope.Contexts.Gpu.VendorName,
scope.Contexts.Gpu.MemorySize,
scope.Contexts.Gpu.NpotSupport,
scope.Contexts.Gpu.Version,
scope.Contexts.Gpu.ApiType,
scope.Contexts.Gpu.MaxTextureSize,
scope.Contexts.Gpu.SupportsDrawCallInstancing,
scope.Contexts.Gpu.SupportsRayTracing,
scope.Contexts.Gpu.SupportsComputeShaders,
scope.Contexts.Gpu.SupportsGeometryShaders,
scope.Contexts.Gpu.VendorId,
scope.Contexts.Gpu.MultiThreadedRendering,
scope.Contexts.Gpu.GraphicsShaderLevel
);
}

protected abstract void WriteScope(
string? AppStartTime,
string? AppBuildType,
string? OperatingSystemRawDescription,
int? DeviceProcessorCount,
string? DeviceCpuDescription,
string? DeviceTimezone,
bool? DeviceSupportsVibration,
string? DeviceName,
bool? DeviceSimulator,
string? DeviceDeviceUniqueIdentifier,
string? DeviceDeviceType,
string? DeviceModel,
long? DeviceMemorySize,
int? GpuId,
string? GpuName,
string? GpuVendorName,
int? GpuMemorySize,
string? GpuNpotSupport,
string? GpuVersion,
string? GpuApiType,
int? GpuMaxTextureSize,
bool? GpuSupportsDrawCallInstancing,
bool? GpuSupportsRayTracing,
bool? GpuSupportsComputeShaders,
bool? GpuSupportsGeometryShaders,
string? GpuVendorId,
bool? GpuMultiThreadedRendering,
string? GpuGraphicsShaderLevel
);
}
88 changes: 88 additions & 0 deletions src/Sentry/Internal/ScopeObserver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Sentry.Extensibility;

namespace Sentry.Internal;

/// <summary>
/// Scope Observer wrapper for the common behaviour accross platforms.
/// </summary>
internal abstract class ScopeObserver : Sentry.IScopeObserver
{
private readonly SentryOptions _options;
private readonly string _name;

public ScopeObserver(
string name, SentryOptions options)
{
_name = name;
_options = options;
}

public void AddBreadcrumb(Breadcrumb breadcrumb)
{
_options.DiagnosticLogger?.Log(SentryLevel.Debug,
"{0} Scope Sync - Adding breadcrumb m:\"{1}\" l:\"{2}\"", null, _name,
breadcrumb.Message, breadcrumb.Level);
AddBreadcrumbImpl(breadcrumb);
}

public abstract void AddBreadcrumbImpl(Breadcrumb breadcrumb);

public void SetExtra(string key, object? value)
{
// TODO json serialization
// var serialized = value is null ? null : SafeSerializer.SerializeSafely(value);
// if (value is not null && serialized is null)
// {
// _options.DiagnosticLogger?.Log(SentryLevel.Warning,
// "{0} Scope Sync - SetExtra k:\"{1}\" v:\"{2}\" - value was serialized as null",
// null, _name, key, value);
// }
// else
// {
// _options.DiagnosticLogger?.Log(SentryLevel.Debug,
// "{0} Scope Sync - Setting Extra k:\"{1}\" v:\"{2}\"", null, _name, key, value);
// }
// SetExtraImpl(key, serialized);
}

public abstract void SetExtraImpl(string key, string? value);

public void SetTag(string key, string value)
{
_options.DiagnosticLogger?.Log(SentryLevel.Debug,
"{0} Scope Sync - Setting Tag k:\"{1}\" v:\"{2}\"", null, _name, key, value);
SetTagImpl(key, value);
}

public abstract void SetTagImpl(string key, string value);

public void UnsetTag(string key)
{
_options.DiagnosticLogger?.Log(
SentryLevel.Debug, "{0} Scope Sync - Unsetting Tag k:\"{1}\"", null, _name, key);
UnsetTagImpl(key);
}

public abstract void UnsetTagImpl(string key);

public void SetUser(User? user)
{
if (user is null)
{
_options.DiagnosticLogger?.Log(
SentryLevel.Debug, "{0} Scope Sync - Unsetting User", null, _name);
UnsetUserImpl();
}
else
{
_options.DiagnosticLogger?.Log(SentryLevel.Debug,
"{0} Scope Sync - Setting User i:\"{1}\" n:\"{2}\"", null, _name, user.Id,
user.Username);
SetUserImpl(user);
}
}

public abstract void SetUserImpl(User user);

public abstract void UnsetUserImpl();
}
4 changes: 2 additions & 2 deletions src/Sentry/Platforms/Android/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private static void InitSentryAndroidSdk(SentryOptions options)
}

// These options we have behind feature flags
if (options is {IsPerformanceMonitoringEnabled: true, Android.EnableAndroidSdkTracing: true})
if (options is { IsPerformanceMonitoringEnabled: true, Android.EnableAndroidSdkTracing: true })
{
o.EnableTracing = (JavaBoolean?)options.EnableTracing;
o.TracesSampleRate = (JavaDouble?)options.TracesSampleRate;
Expand Down Expand Up @@ -175,7 +175,7 @@ private static void InitSentryAndroidSdk(SentryOptions options)
});

// Now initialize the Android SDK (with a logger only if we're debugging)
if (options.Debug && options.DiagnosticLogger is {} logger)
if (options.Debug && options.DiagnosticLogger is { } logger)
{
var androidLogger = new AndroidDiagnosticLogger(logger);
SentryAndroid.Init(AppContext, androidLogger, configuration);
Expand Down
Loading
Loading