diff --git a/.gitignore b/.gitignore
index d1f5078cb4..e58d146cd5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ docs/docfx.zip
*.received.*
mono_crash.*.json
test_output/
+test/**/*.apk
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd6822d151..c3862e7f95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## Unreleased
+
+### Features
+
+- Add support for Portable PDB format ([#2050](https://github.com/getsentry/sentry-dotnet/pull/2050))
+
## 3.24.1
### Fixes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 791af3c670..be8848bbf4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,9 @@
# Contributing
-We love receiving PRs from the community with features and fixed.
+We love receiving PRs from the community with features and fixed.
For big feature it's advised to raise an issue to discuss it first.
-## TLDR:
+## TLDR
* Install the .NET SDKs
* To quickly get up and running, you can just run `dotnet build`
@@ -68,7 +68,7 @@ Note that the MSBuild version should always be `17.0` but will change paths base
We'd love for users to update the SDK everytime and as soon as we make a new release. But in reality most users rarely update the SDK.
To help users see value in updating the SDK, we maintain a changelog file with entries split between two headings:
-1. `### Features`
+1. `### Features`
2. `### Fixes`
We add the heading in the first PR that's adding either a feature or fixes in the current release.
@@ -86,3 +86,15 @@ Below that, you'll add the heading 3 mentioned above. For example, if you're add
```
There's a GitHub action check to verify if an entry was added. If the entry isn't a user-facing change, you can skip the verification with `#skip-changelog` written to the PR description. The bot writes a comment in the PR with a suggestion entry to the changelog based on the PR title.
+
+## Verify tests
+
+Some tests use [Verify](https://github.com/VerifyTests/Verify) to check returned objects against snapshots that are part of the repo.
+In case you're making code changes that produce many (intended) changes in those snapshots, you can use [accept-verifier-changes.ps1](./scripts/accept-verifier-changes.ps1) like this:
+
+```shell-script
+dotnet test
+pwsh ./scripts/accept-verifier-changes.ps1
+```
+
+You may need to run this multiple times because `dotnet test` stops after a certain number of failures.
\ No newline at end of file
diff --git a/Sentry-CI-Build-Linux.slnf b/Sentry-CI-Build-Linux.slnf
index 59cfce806d..fecdbe694d 100644
--- a/Sentry-CI-Build-Linux.slnf
+++ b/Sentry-CI-Build-Linux.slnf
@@ -34,6 +34,7 @@
"src\\Sentry.NLog\\Sentry.NLog.csproj",
"src\\Sentry.Serilog\\Sentry.Serilog.csproj",
"src\\Sentry\\Sentry.csproj",
+ "test\\Sentry.Android.Tests\\Sentry.Android.Tests.csproj",
"test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj",
"test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj",
"test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj",
diff --git a/Sentry-CI-Build-Windows.slnf b/Sentry-CI-Build-Windows.slnf
index c9d482552f..ffa209f86e 100644
--- a/Sentry-CI-Build-Windows.slnf
+++ b/Sentry-CI-Build-Windows.slnf
@@ -33,6 +33,7 @@
"src\\Sentry.NLog\\Sentry.NLog.csproj",
"src\\Sentry.Serilog\\Sentry.Serilog.csproj",
"src\\Sentry\\Sentry.csproj",
+ "test\\Sentry.Android.Tests\\Sentry.Android.Tests.csproj",
"test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj",
"test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj",
"test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj",
diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf
index 1aac621a54..27fc5556e9 100644
--- a/Sentry-CI-Build-macOS.slnf
+++ b/Sentry-CI-Build-macOS.slnf
@@ -36,6 +36,7 @@
"src\\Sentry.NLog\\Sentry.NLog.csproj",
"src\\Sentry.Serilog\\Sentry.Serilog.csproj",
"src\\Sentry\\Sentry.csproj",
+ "test\\Sentry.Android.Tests\\Sentry.Android.Tests.csproj",
"test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj",
"test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj",
"test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj",
diff --git a/Sentry-CI-Test.slnf b/Sentry-CI-Test.slnf
index 0397bdb917..735524c45d 100644
--- a/Sentry-CI-Test.slnf
+++ b/Sentry-CI-Test.slnf
@@ -2,6 +2,7 @@
"solution": {
"path": "Sentry.sln",
"projects": [
+ "test\\Sentry.Android.Tests\\Sentry.Android.Tests.csproj",
"test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj",
"test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj",
"test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj",
diff --git a/Sentry.sln b/Sentry.sln
index 7c65c68c8f..f1ad245e96 100644
--- a/Sentry.sln
+++ b/Sentry.sln
@@ -166,6 +166,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Bindings.Android", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Bindings.Cocoa", "src\Sentry.Bindings.Cocoa\Sentry.Bindings.Cocoa.csproj", "{F533FD6D-1E10-4F06-BE3A-3B3C713A75A6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Android.Tests", "test\Sentry.Android.Tests\Sentry.Android.Tests.csproj", "{5E12E053-22AF-4184-8581-3FCFD225617D}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.TestUtils", "test\Sentry.AspNetCore.TestUtils\Sentry.AspNetCore.TestUtils.csproj", "{C96CB65D-3D2D-404E-85C0-69A3FC03B48F}"
EndProject
Global
@@ -399,6 +401,10 @@ Global
{F533FD6D-1E10-4F06-BE3A-3B3C713A75A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F533FD6D-1E10-4F06-BE3A-3B3C713A75A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F533FD6D-1E10-4F06-BE3A-3B3C713A75A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5E12E053-22AF-4184-8581-3FCFD225617D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5E12E053-22AF-4184-8581-3FCFD225617D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5E12E053-22AF-4184-8581-3FCFD225617D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5E12E053-22AF-4184-8581-3FCFD225617D}.Release|Any CPU.Build.0 = Release|Any CPU
{C96CB65D-3D2D-404E-85C0-69A3FC03B48F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C96CB65D-3D2D-404E-85C0-69A3FC03B48F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C96CB65D-3D2D-404E-85C0-69A3FC03B48F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -466,6 +472,7 @@ Global
{70066C6C-0A18-4322-A02A-9A0DFE59C02B} = {77454495-55EE-4B40-A089-71B9E8F82E89}
{9A23DA57-3F0B-4646-A37A-71A4CE7C94CB} = {AF6AF4C7-8AA2-4D59-8064-2D79560904EB}
{F533FD6D-1E10-4F06-BE3A-3B3C713A75A6} = {AF6AF4C7-8AA2-4D59-8064-2D79560904EB}
+ {5E12E053-22AF-4184-8581-3FCFD225617D} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
{C96CB65D-3D2D-404E-85C0-69A3FC03B48F} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/SentryMaui.slnf b/SentryMaui.slnf
index 4d7e7c9aa7..f671d8c3a5 100644
--- a/SentryMaui.slnf
+++ b/SentryMaui.slnf
@@ -12,6 +12,7 @@
"test\\MauiTestUtils\\DeviceTests.Runners.SourceGen\\TestUtils.DeviceTests.Runners.SourceGen.csproj",
"test\\MauiTestUtils\\DeviceTests.Runners\\TestUtils.DeviceTests.Runners.csproj",
"test\\MauiTestUtils\\DeviceTests\\TestUtils.DeviceTests.csproj",
+ "test\\Sentry.Android.Tests\\Sentry.Android.Tests.csproj",
"test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj",
"test\\Sentry.Maui.Device.TestApp\\Sentry.Maui.Device.TestApp.csproj",
"test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj",
diff --git a/SentryNoSamples.slnf b/SentryNoSamples.slnf
index 857b6791f6..41592da47a 100644
--- a/SentryNoSamples.slnf
+++ b/SentryNoSamples.slnf
@@ -15,6 +15,7 @@
"src\\Sentry.NLog\\Sentry.NLog.csproj",
"src\\Sentry.Serilog\\Sentry.Serilog.csproj",
"src\\Sentry\\Sentry.csproj",
+ "test\\Sentry.Android.Tests\\Sentry.Android.Tests.csproj",
"test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj",
"test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj",
"test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj",
diff --git a/scripts/accept-verifier-changes.ps1 b/scripts/accept-verifier-changes.ps1
new file mode 100644
index 0000000000..06a5a4b2af
--- /dev/null
+++ b/scripts/accept-verifier-changes.ps1
@@ -0,0 +1,23 @@
+# Similar to `dotnet verify accept` but doesn't create new runtime-specific ".verified" files if a common one exists.
+param([switch] $DryRun)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+$receivedFiles = Get-ChildItem . -Recurse -File -Include "*.received.txt"
+
+$receivedFiles | ForEach-Object {
+ $nameWithRuntime = ($_.BaseName.Split(".") | Select-Object -SkipLast 1) -join "."
+ $nameWithoutRuntime = ($_.BaseName.Split(".") | Select-Object -SkipLast 2) -join "."
+ $ext = ".verified.txt"
+
+ $targetFile = (Test-Path "$($_.Directory)/$nameWithoutRuntime$ext") `
+ ? "$($_.Directory)/$nameWithoutRuntime$ext" `
+ : "$($_.Directory)/$nameWithRuntime$ext"
+
+ Write-Host "Updating $($targetFile.Replace((Get-Item .).FullName, ''))"
+ if (-not ($DryRun))
+ {
+ Move-Item $_ $targetFile -Force
+ }
+}
diff --git a/scripts/update-test-apks.ps1 b/scripts/update-test-apks.ps1
new file mode 100644
index 0000000000..0423b00ebb
--- /dev/null
+++ b/scripts/update-test-apks.ps1
@@ -0,0 +1,52 @@
+param([switch] $IfNotExist)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+$repoRoot = "$PSScriptRoot/.."
+$apkDir = "$repoRoot/test/Sentry.Android.Tests/TestAPKs"
+
+New-Item -ItemType Directory -Force -Path $apkDir | Out-Null
+$apkDir = Resolve-Path $apkDir
+
+function BuildAndroidSample([bool] $UseAssemblyStore, [bool] $UseAssemblyCompression)
+{
+ $testAppDir = "$repoRoot/test/AndroidTestApp"
+ $outputApk = "$apkDir/android-Store=$UseAssemblyStore-Compressed=$UseAssemblyCompression.apk"
+
+ if ($IfNotExist -and (Test-Path $outputApk))
+ {
+ Write-Host "$outputApk already exists, skipping build"
+ return
+ }
+
+ Push-Location -Verbose $testAppDir
+ try
+ {
+ # Need to do a clean build otherwise some DLLs would end up being compressed even if it's disabled on this run.
+ dotnet build --configuration Release --no-incremental `
+ --property:AndroidUseAssemblyStore=$UseAssemblyStore `
+ --property:AndroidEnableAssemblyCompression=$UseAssemblyCompression `
+ | Out-Host
+ if ($LASTEXITCODE -ne 0)
+ {
+ exit $LASTEXITCODE
+ }
+ }
+ finally
+ {
+ Pop-Location
+ }
+
+ Move-Item "$testAppDir/bin/Release/*/*-signed.apk" $outputApk
+}
+
+if (!$IfNotExist)
+{
+ Remove-Item "$apkDir/*.apk"
+}
+
+BuildAndroidSample $true $true
+BuildAndroidSample $true $false
+BuildAndroidSample $false $true
+BuildAndroidSample $false $false
diff --git a/src/Sentry/DebugImage.cs b/src/Sentry/DebugImage.cs
index 56a0748fc4..00c633aa23 100644
--- a/src/Sentry/DebugImage.cs
+++ b/src/Sentry/DebugImage.cs
@@ -31,6 +31,11 @@ public sealed class DebugImage : IJsonSerializable
///
public string? DebugId { get; set; }
+ ///
+ /// Checksum of the companion debug file.
+ ///
+ public string? DebugChecksum { get; set; }
+
///
/// Path and name of the debug companion file.
///
@@ -47,6 +52,8 @@ public sealed class DebugImage : IJsonSerializable
///
public string? CodeFile { get; set; }
+ internal Guid? ModuleVersionId { get; set; }
+
///
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
@@ -56,6 +63,7 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteStringIfNotWhiteSpace("image_addr", ImageAddress);
writer.WriteNumberIfNotNull("image_size", ImageSize);
writer.WriteStringIfNotWhiteSpace("debug_id", DebugId);
+ writer.WriteStringIfNotWhiteSpace("debug_checksum", DebugChecksum);
writer.WriteStringIfNotWhiteSpace("debug_file", DebugFile);
writer.WriteStringIfNotWhiteSpace("code_id", CodeId);
writer.WriteStringIfNotWhiteSpace("code_file", CodeFile);
@@ -72,6 +80,7 @@ public static DebugImage FromJson(JsonElement json)
var imageAddress = json.GetPropertyOrNull("image_addr")?.GetString();
var imageSize = json.GetPropertyOrNull("image_size")?.GetInt64();
var debugId = json.GetPropertyOrNull("debug_id")?.GetString();
+ var debugChecksum = json.GetPropertyOrNull("debug_checksum")?.GetString();
var debugFile = json.GetPropertyOrNull("debug_file")?.GetString();
var codeId = json.GetPropertyOrNull("code_id")?.GetString();
var codeFile = json.GetPropertyOrNull("code_file")?.GetString();
@@ -82,6 +91,7 @@ public static DebugImage FromJson(JsonElement json)
ImageAddress = imageAddress,
ImageSize = imageSize,
DebugId = debugId,
+ DebugChecksum = debugChecksum,
DebugFile = debugFile,
CodeId = codeId,
CodeFile = codeFile,
diff --git a/src/Sentry/Extensibility/SentryStackTraceFactory.cs b/src/Sentry/Extensibility/SentryStackTraceFactory.cs
index 3cde66e3dd..62bdf6f1f7 100644
--- a/src/Sentry/Extensibility/SentryStackTraceFactory.cs
+++ b/src/Sentry/Extensibility/SentryStackTraceFactory.cs
@@ -1,29 +1,14 @@
-using Sentry.Internal.Extensions;
+using Sentry.Internal;
namespace Sentry.Extensibility;
///
/// Default factory to from an .
///
-public class SentryStackTraceFactory : ISentryStackTraceFactory
+public sealed class SentryStackTraceFactory : ISentryStackTraceFactory
{
private readonly SentryOptions _options;
- /*
- * NOTE: While we could improve these regexes, doing so might break exception grouping on the backend.
- * Specifically, RegexAsyncFunctionName would be better as: @"^(.*)\+<(\w*|<\w*>b__\d*)>d(?:__\d*)?$"
- * But we cannot make this change without consequences of ignored events coming back to life in Sentry.
- */
-
- private static readonly Regex RegexAsyncFunctionName = new(@"^(.*)\+<(\w*)>d__\d*$",
- RegexOptions.Compiled | RegexOptions.CultureInvariant);
-
- private static readonly Regex RegexAnonymousFunction = new(@"^<(\w*)>b__\w+$",
- RegexOptions.Compiled | RegexOptions.CultureInvariant);
-
- private static readonly Regex RegexAsyncReturn = new(@"^(.+`[0-9]+)\[\[",
- RegexOptions.Compiled | RegexOptions.CultureInvariant);
-
///
/// Creates an instance of .
///
@@ -34,289 +19,20 @@ public class SentryStackTraceFactory : ISentryStackTraceFactory
///
/// The exception to create the stacktrace from.
/// A Sentry stack trace.
- public virtual SentryStackTrace? Create(Exception? exception = null)
+ public SentryStackTrace? Create(Exception? exception = null)
{
- var isCurrentStackTrace = exception == null && _options.AttachStacktrace;
-
- if (exception == null && !isCurrentStackTrace)
+ if (exception == null && !_options.AttachStacktrace)
{
_options.LogDebug("No Exception and AttachStacktrace is off. No stack trace will be collected.");
return null;
}
+ var isCurrentStackTrace = exception == null && _options.AttachStacktrace;
_options.LogDebug("Creating SentryStackTrace. isCurrentStackTrace: {0}.", isCurrentStackTrace);
- return Create(CreateStackTrace(exception), isCurrentStackTrace);
- }
-
- ///
- /// Creates a s from the .
- ///
- /// The exception.
- /// A StackTrace.
- protected virtual StackTrace CreateStackTrace(Exception? exception) =>
- exception is null
- ? new StackTrace(true)
- : new StackTrace(exception, true);
-
- ///
- /// Creates a from the .
- ///
- /// The stack trace.
- /// Whether this is the current stack trace.
- /// SentryStackTrace
- internal SentryStackTrace? Create(StackTrace stackTrace, bool isCurrentStackTrace)
- {
- var frames = CreateFrames(stackTrace, isCurrentStackTrace)
- // Sentry expects the frames to be sent in reversed order
- .Reverse();
-
- var stacktrace = new SentryStackTrace();
-
- foreach (var frame in frames)
- {
- stacktrace.Frames.Add(frame);
- }
-
- return stacktrace.Frames.Count != 0
- ? stacktrace
- : null;
- }
-
- ///
- /// Creates an enumerator of from a .
- ///
- internal IEnumerable CreateFrames(StackTrace stackTrace, bool isCurrentStackTrace)
- {
- var frames = _options.StackTraceMode switch
- {
- StackTraceMode.Enhanced => EnhancedStackTrace.GetFrames(stackTrace).Select(p => p as StackFrame),
- _ => stackTrace.GetFrames()
- // error CS8619: Nullability of reference types in value of type 'StackFrame?[]' doesn't match target type 'IEnumerable'.
-#if NETCOREAPP3_0
- .Where(f => f is not null)
-#endif
- };
-
- // Not to throw on code that ignores nullability warnings.
- if (frames.IsNull())
- {
- _options.LogDebug("No stack frames found. AttachStacktrace: '{0}', isCurrentStackTrace: '{1}'",
- _options.AttachStacktrace, isCurrentStackTrace);
-
- yield break;
- }
-
- Debug.Assert(frames != null);
-
- var firstFrame = true;
- foreach (var stackFrame in frames)
- {
-
-#if !NET5_0_OR_GREATER
- if (stackFrame is null)
- {
- continue;
- }
-#endif
-
- // Remove the frames until the call for capture with the SDK
- if (firstFrame
- && isCurrentStackTrace
- && stackFrame.GetMethod() is { } method
- && method.DeclaringType?.AssemblyQualifiedName?.StartsWith("Sentry") == true)
- {
- continue;
- }
-
- firstFrame = false;
-
- yield return CreateFrame(stackFrame, isCurrentStackTrace);
- }
- }
-
- internal SentryStackFrame CreateFrame(StackFrame stackFrame) => InternalCreateFrame(stackFrame, true);
-
- ///
- /// Create a from a .
- ///
- protected virtual SentryStackFrame CreateFrame(StackFrame stackFrame, bool isCurrentStackTrace) => InternalCreateFrame(stackFrame, true);
-
- ///
- /// Default the implementation of CreateFrame.
- ///
- protected SentryStackFrame InternalCreateFrame(StackFrame stackFrame, bool demangle)
- {
- const string unknownRequiredField = "(unknown)";
- string? projectPath = null;
- var frame = new SentryStackFrame();
- if (GetMethod(stackFrame) is { } method)
- {
- frame.Module = method.DeclaringType?.FullName ?? unknownRequiredField;
- frame.Package = method.DeclaringType?.Assembly.FullName;
-
- if (_options.StackTraceMode == StackTraceMode.Enhanced &&
- stackFrame is EnhancedStackFrame enhancedStackFrame)
- {
- var stringBuilder = new StringBuilder();
- frame.Function = enhancedStackFrame.MethodInfo.Append(stringBuilder, false).ToString();
-
- if (enhancedStackFrame.MethodInfo.DeclaringType is { } declaringType)
- {
- stringBuilder.Clear();
- stringBuilder.AppendTypeDisplayName(declaringType);
- frame.Module = stringBuilder.ToString();
- }
- }
- else
- {
- frame.Function = method.Name;
- }
-
- // Originally we didn't skip methods from dynamic assemblies, so not to break compatibility:
- if (_options.StackTraceMode != StackTraceMode.Original && method.Module.Assembly.IsDynamic)
- {
- frame.InApp = false;
- }
-
- AttributeReader.TryGetProjectDirectory(method.Module.Assembly, out projectPath);
- }
-
- frame.ConfigureAppFrame(_options);
-
- var frameFileName = stackFrame.GetFileName();
- if (projectPath != null && frameFileName != null)
- {
- if (frameFileName.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase))
- {
- frameFileName = frameFileName.Substring(projectPath.Length);
- }
- }
-
- frame.FileName = frameFileName;
-
- // stackFrame.HasILOffset() throws NotImplemented on Mono 5.12
- var ilOffset = stackFrame.GetILOffset();
- if (ilOffset != StackFrame.OFFSET_UNKNOWN)
- {
- frame.InstructionOffset = ilOffset;
- }
-
- var lineNo = stackFrame.GetFileLineNumber();
- if (lineNo > 0)
- {
- frame.LineNumber = lineNo;
- }
-
- var colNo = stackFrame.GetFileColumnNumber();
- if (lineNo > 0)
- {
- frame.ColumnNumber = colNo;
- }
-
- if (demangle && _options.StackTraceMode != StackTraceMode.Enhanced)
- {
- DemangleAsyncFunctionName(frame);
- DemangleAnonymousFunction(frame);
- DemangleLambdaReturnType(frame);
- }
-
- if (_options.StackTraceMode == StackTraceMode.Enhanced)
- {
- // In Enhanced mode, Module (which in this case is the Namespace)
- // is already prepended to the function, after return type.
- // Removing here at the end because this is used to resolve InApp=true/false
- frame.Module = null;
- }
-
- return frame;
- }
-
- ///
- /// Get a from .
- ///
- /// The .
- protected virtual MethodBase? GetMethod(StackFrame stackFrame)
- => stackFrame.GetMethod();
-
- ///
- /// Clean up function and module names produced from `async` state machine calls.
- ///
- ///
- /// When the Microsoft cs.exe compiler compiles some modern C# features,
- /// such as async/await calls, it can create synthetic function names that
- /// do not match the function names in the original source code. Here we
- /// reverse some of these transformations, so that the function and module
- /// names that appears in the Sentry UI will match the function and module
- /// names in the original source-code.
- ///
- private static void DemangleAsyncFunctionName(SentryStackFrame frame)
- {
- if (frame.Module == null || frame.Function != "MoveNext")
- {
- return;
- }
-
- // Search for the function name in angle brackets followed by d__.
- //
- // Change:
- // RemotePrinterService+d__24 in MoveNext at line 457:13
- // to:
- // RemotePrinterService in UpdateNotification at line 457:13
-
- var match = RegexAsyncFunctionName.Match(frame.Module);
- if (match.Success && match.Groups.Count == 3)
- {
- frame.Module = match.Groups[1].Value;
- frame.Function = match.Groups[2].Value;
- }
- }
-
- ///
- /// Clean up function names for anonymous lambda calls.
- ///
- internal static void DemangleAnonymousFunction(SentryStackFrame frame)
- {
- if (frame.Function == null)
- {
- return;
- }
-
- // Search for the function name in angle brackets followed by b__.
- //
- // Change:
- // b__36
- // to:
- // BeginInvokeAsynchronousActionMethod { }
-
- var match = RegexAnonymousFunction.Match(frame.Function);
- if (match.Success && match.Groups.Count == 2)
- {
- frame.Function = match.Groups[1].Value + " { }";
- }
- }
-
- ///
- /// Remove return type from module in a Task with a Lambda with a return value.
- /// This was seen in Unity, see https://github.com/getsentry/sentry-unity/issues/845
- ///
- internal static void DemangleLambdaReturnType(SentryStackFrame frame)
- {
- if (frame.Module == null)
- {
- return;
- }
-
- // Change:
- // System.Threading.Tasks.Task`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] in InnerInvoke
- // or System.Collections.Generic.List`1[[UnityEngine.Events.PersistentCall, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]] in get_Item
- // to:
- // System.Threading.Tasks.Task`1 in InnerInvoke`
- // or System.Collections.Generic.List`1 in get_Item
- var match = RegexAsyncReturn.Match(frame.Module);
- if (match.Success && match.Groups.Count == 2)
- {
- frame.Module = match.Groups[1].Value;
- }
+ var stackTrace = exception is null ? new StackTrace(true) : new StackTrace(exception, true);
+ var result = DebugStackTrace.Create(_options, stackTrace, isCurrentStackTrace);
+ _options.LogDebug("Created {0} with {1} frames.", nameof(DebugStackTrace), result.Frames.Count);
+ return result.Frames.Count != 0 ? result : null;
}
}
diff --git a/src/Sentry/Internal/DebugStackTrace.cs b/src/Sentry/Internal/DebugStackTrace.cs
new file mode 100644
index 0000000000..5b7f4ab706
--- /dev/null
+++ b/src/Sentry/Internal/DebugStackTrace.cs
@@ -0,0 +1,494 @@
+using System.Reflection.PortableExecutable;
+using Sentry.Internal.Extensions;
+using Sentry.Extensibility;
+
+#if ANDROID
+using Sentry.Android;
+using Sentry.Android.AssemblyReader;
+#endif
+
+namespace Sentry.Internal;
+
+///
+/// Sentry Stacktrace with debug images.
+///
+internal class DebugStackTrace : SentryStackTrace
+{
+ private readonly SentryOptions _options;
+
+ // Debug images referenced by frames in this StackTrace
+ private readonly Dictionary _debugImageIndexByModule = new();
+ private const int DebugImageMissing = -1;
+ private bool _debugImagesMerged;
+
+ /*
+ * NOTE: While we could improve these regexes, doing so might break exception grouping on the backend.
+ * Specifically, RegexAsyncFunctionName would be better as: @"^(.*)\+<(\w*|<\w*>b__\d*)>d(?:__\d*)?$"
+ * But we cannot make this change without consequences of ignored events coming back to life in Sentry.
+ */
+
+ private static readonly Regex RegexAsyncFunctionName = new(@"^(.*)\+<(\w*)>d__\d*$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ private static readonly Regex RegexAnonymousFunction = new(@"^<(\w*)>b__\w+$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ private static readonly Regex RegexAsyncReturn = new(@"^(.+`[0-9]+)\[\[",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+#if ANDROID
+ private readonly Lazy _assemblyReader;
+#endif
+
+ internal DebugStackTrace(SentryOptions options)
+ {
+ _options = options;
+#if ANDROID
+ _assemblyReader = new Lazy(() => GetAndroidAssemblyReader(options));
+#endif
+ }
+
+ protected List DebugImages { get; } = new();
+
+ internal static DebugStackTrace Create(SentryOptions options, StackTrace stackTrace, bool isCurrentStackTrace)
+ {
+ var result = new DebugStackTrace(options);
+
+ var frames = result.CreateFrames(stackTrace, isCurrentStackTrace)
+ .Reverse(); // Sentry expects the frames to be sent in reversed order
+
+ foreach (var frame in frames)
+ {
+ result.Frames.Add(frame);
+ }
+
+ return result;
+ }
+
+ internal void MergeDebugImagesInto(SentryEvent @event)
+ {
+ // This operation may only be run once because it is destructive to the object state;
+ // Frame indexes may be changed as well as _debugImageIndexByModule becoming invalid.
+ if (_debugImagesMerged)
+ {
+ throw new InvalidOperationException("Cannot call MergeDebugImagesInto multiple times");
+ }
+ _debugImagesMerged = true;
+
+ _options.LogDebug("Merging {0} debug images from stacktrace.", DebugImages.Count);
+ if (DebugImages.Count == 0)
+ {
+ return;
+ }
+
+ @event.DebugImages ??= new();
+
+ if (@event.DebugImages.Count == 0)
+ {
+ // Default case when there's just a single stacktrace (i.e. no inner exceptions).
+ @event.DebugImages.AddRange(DebugImages);
+ return;
+ }
+
+ // Otherwise, we must merge new images into an existing list, which means their relative indexes change.
+ // Therefore, we must also update indices specified in frame.AddressMode for each affected frame.
+ // We could just append _debugImages to @event.DebugImages and shift all indexes but merging is simple too.
+ var originalCount = @event.DebugImages.Count;
+ Dictionary relocations = new();
+ for (var i = 0; i < DebugImages.Count; i++)
+ {
+ // First check if the image is already present on the event. Simple lookup should be faster than
+ // constructing a map first, assuming there are normally just a few debug images affected.
+ var found = false;
+ for (var j = 0; j < originalCount; j++)
+ {
+ if (DebugImages[i].ModuleVersionId == @event.DebugImages[j].ModuleVersionId)
+ {
+ if (i != j)
+ {
+ relocations.Add(DebugStackTrace.GetRelativeAddressMode(i), DebugStackTrace.GetRelativeAddressMode(j));
+ }
+ found = true;
+ }
+ }
+
+ if (!found)
+ {
+ relocations.Add(DebugStackTrace.GetRelativeAddressMode(i), DebugStackTrace.GetRelativeAddressMode(@event.DebugImages.Count));
+ @event.DebugImages.Add(DebugImages[i]);
+ }
+ }
+
+ foreach (var frame in Frames)
+ {
+ if (frame.AddressMode is not null && relocations.TryGetValue(frame.AddressMode, out var newAddressMode))
+ {
+ frame.AddressMode = newAddressMode;
+ }
+ }
+ }
+
+ ///
+ /// Creates an enumerator of from a .
+ ///
+ private IEnumerable CreateFrames(StackTrace stackTrace, bool isCurrentStackTrace)
+ {
+ var frames = _options.StackTraceMode switch
+ {
+ StackTraceMode.Enhanced => EnhancedStackTrace.GetFrames(stackTrace).Select(p => p as StackFrame),
+ _ => stackTrace.GetFrames()
+ // error CS8619: Nullability of reference types in value of type 'StackFrame?[]' doesn't match target type 'IEnumerable'.
+#if NETCOREAPP3_0
+ .Where(f => f is not null)
+#endif
+ };
+
+ // Not to throw on code that ignores nullability warnings.
+ if (frames.IsNull())
+ {
+ _options.LogDebug("No stack frames found. AttachStacktrace: '{0}', isCurrentStackTrace: '{1}'",
+ _options.AttachStacktrace, isCurrentStackTrace);
+
+ yield break;
+ }
+
+ Debug.Assert(frames != null);
+
+ var firstFrame = true;
+ foreach (var stackFrame in frames)
+ {
+#if !NET5_0_OR_GREATER
+ if (stackFrame is null)
+ {
+ continue;
+ }
+#endif
+
+ // Remove the frames until the call for capture with the SDK
+ if (firstFrame
+ && isCurrentStackTrace
+ && stackFrame.GetMethod() is { } method
+ && method.DeclaringType?.AssemblyQualifiedName?.StartsWith("Sentry") == true)
+ {
+ _options.LogDebug("Skipping initial stack frame '{0}'", method.Name);
+ continue;
+ }
+
+ firstFrame = false;
+
+ yield return CreateFrame(stackFrame);
+ }
+ }
+
+ ///
+ /// Create a from a .
+ ///
+ internal SentryStackFrame CreateFrame(StackFrame stackFrame) => InternalCreateFrame(stackFrame, true);
+
+ ///
+ /// Default the implementation of CreateFrame.
+ ///
+ private SentryStackFrame InternalCreateFrame(StackFrame stackFrame, bool demangle)
+ {
+ const string unknownRequiredField = "(unknown)";
+ string? projectPath = null;
+ var frame = new SentryStackFrame();
+ if (stackFrame.GetMethod() is { } method)
+ {
+ frame.Module = method.DeclaringType?.FullName ?? unknownRequiredField;
+ frame.Package = method.DeclaringType?.Assembly.FullName;
+
+ if (_options.StackTraceMode == StackTraceMode.Enhanced &&
+ stackFrame is EnhancedStackFrame enhancedStackFrame)
+ {
+ var stringBuilder = new StringBuilder();
+ frame.Function = enhancedStackFrame.MethodInfo.Append(stringBuilder, false).ToString();
+
+ if (enhancedStackFrame.MethodInfo.DeclaringType is { } declaringType)
+ {
+ stringBuilder.Clear();
+ stringBuilder.AppendTypeDisplayName(declaringType);
+ frame.Module = stringBuilder.ToString();
+ }
+ }
+ else
+ {
+ frame.Function = method.Name;
+ }
+
+ // Originally we didn't skip methods from dynamic assemblies, so not to break compatibility:
+ if (_options.StackTraceMode != StackTraceMode.Original && method.Module.Assembly.IsDynamic)
+ {
+ frame.InApp = false;
+ }
+
+ AttributeReader.TryGetProjectDirectory(method.Module.Assembly, out projectPath);
+
+ if (AddDebugImage(method.Module) is { } moduleIdx && moduleIdx != DebugImageMissing)
+ {
+ frame.AddressMode = DebugStackTrace.GetRelativeAddressMode(moduleIdx);
+
+ try
+ {
+ var token = method.MetadataToken;
+ // The top byte is the token type, the lower three bytes are the record id.
+ // See: https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms404456(v=vs.100)#metadata-token-structure
+ var tokenType = token & 0xff000000;
+ // See https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/cortokentype-enumeration
+ if (tokenType == 0x06000000) // CorTokenType.mdtMethodDef
+ {
+ var recordId = token & 0x00ffffff;
+ frame.FunctionId = $"0x{recordId:x}";
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ // method.MetadataToken may throw
+ // see https://learn.microsoft.com/en-us/dotnet/api/system.reflection.memberinfo.metadatatoken?view=net-6.0
+ _options.LogDebug("Could not get MetadataToken for stack frame {0} from {1}", frame.Function, method.Module.Name);
+ }
+ }
+ }
+
+ frame.ConfigureAppFrame(_options);
+
+ if (stackFrame.GetFileName() is { } frameFileName)
+ {
+ if (projectPath != null && frameFileName.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase))
+ {
+ frameFileName = frameFileName.Substring(projectPath.Length);
+ }
+ frame.FileName = frameFileName;
+ }
+
+ // stackFrame.HasILOffset() throws NotImplemented on Mono 5.12
+ var ilOffset = stackFrame.GetILOffset();
+ if (ilOffset != StackFrame.OFFSET_UNKNOWN)
+ {
+ frame.InstructionAddress = $"0x{ilOffset:x}";
+ }
+
+ var lineNo = stackFrame.GetFileLineNumber();
+ if (lineNo > 0)
+ {
+ frame.LineNumber = lineNo;
+ }
+
+ var colNo = stackFrame.GetFileColumnNumber();
+ if (lineNo > 0)
+ {
+ frame.ColumnNumber = colNo;
+ }
+
+ if (demangle && _options.StackTraceMode != StackTraceMode.Enhanced)
+ {
+ DemangleAsyncFunctionName(frame);
+ DemangleAnonymousFunction(frame);
+ DemangleLambdaReturnType(frame);
+ }
+
+ if (_options.StackTraceMode == StackTraceMode.Enhanced)
+ {
+ // In Enhanced mode, Module (which in this case is the Namespace)
+ // is already prepended to the function, after return type.
+ // Removing here at the end because this is used to resolve InApp=true/false
+ frame.Module = null;
+ }
+
+ return frame;
+ }
+
+ private static string GetRelativeAddressMode(int moduleIndex) => $"rel:{moduleIndex}";
+
+ ///
+ /// Clean up function and module names produced from `async` state machine calls.
+ ///
+ ///
+ /// When the Microsoft cs.exe compiler compiles some modern C# features,
+ /// such as async/await calls, it can create synthetic function names that
+ /// do not match the function names in the original source code. Here we
+ /// reverse some of these transformations, so that the function and module
+ /// names that appears in the Sentry UI will match the function and module
+ /// names in the original source-code.
+ ///
+ private static void DemangleAsyncFunctionName(SentryStackFrame frame)
+ {
+ if (frame.Module == null || frame.Function != "MoveNext")
+ {
+ return;
+ }
+
+ // Search for the function name in angle brackets followed by d__.
+ //
+ // Change:
+ // RemotePrinterService+d__24 in MoveNext at line 457:13
+ // to:
+ // RemotePrinterService in UpdateNotification at line 457:13
+
+ var match = RegexAsyncFunctionName.Match(frame.Module);
+ if (match is {Success: true, Groups.Count: 3})
+ {
+ frame.Module = match.Groups[1].Value;
+ frame.Function = match.Groups[2].Value;
+ }
+ }
+
+ ///
+ /// Clean up function names for anonymous lambda calls.
+ ///
+ internal static void DemangleAnonymousFunction(SentryStackFrame frame)
+ {
+ if (frame.Function == null)
+ {
+ return;
+ }
+
+ // Search for the function name in angle brackets followed by b__.
+ //
+ // Change:
+ // b__36
+ // to:
+ // BeginInvokeAsynchronousActionMethod { }
+
+ var match = RegexAnonymousFunction.Match(frame.Function);
+ if (match is {Success: true, Groups.Count: 2})
+ {
+ frame.Function = match.Groups[1].Value + " { }";
+ }
+ }
+
+ ///
+ /// Remove return type from module in a Task with a Lambda with a return value.
+ /// This was seen in Unity, see https://github.com/getsentry/sentry-unity/issues/845
+ ///
+ private static void DemangleLambdaReturnType(SentryStackFrame frame)
+ {
+ if (frame.Module == null)
+ {
+ return;
+ }
+
+ // Change:
+ // System.Threading.Tasks.Task`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] in InnerInvoke
+ // or System.Collections.Generic.List`1[[UnityEngine.Events.PersistentCall, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]] in get_Item
+ // to:
+ // System.Threading.Tasks.Task`1 in InnerInvoke`
+ // or System.Collections.Generic.List`1 in get_Item
+ var match = RegexAsyncReturn.Match(frame.Module);
+ if (match is {Success: true, Groups.Count: 2})
+ {
+ frame.Module = match.Groups[1].Value;
+ }
+ }
+
+#if ANDROID
+ private static IAndroidAssemblyReader? GetAndroidAssemblyReader(SentryOptions options)
+ {
+ var apkPath = Environment.CommandLine;
+ try
+ {
+ if (File.Exists(apkPath))
+ {
+ var supportedAbis = AndroidHelpers.GetSupportedAbis();
+ return AndroidAssemblyReaderFactory.Open(apkPath, supportedAbis, options.DiagnosticLogger);
+ }
+
+ options.LogWarning(
+ "Cannot create AssemblyReader: cannot read APK path from Environment.CommandLine={0}",
+ apkPath);
+ }
+ catch (Exception e)
+ {
+ options.LogError("Cannot create AssemblyReader: {0}", e);
+ }
+
+ return null;
+ }
+#endif
+
+ private PEReader? TryReadAssembly(string assemblyName)
+ {
+#if ANDROID
+ return _assemblyReader.Value?.TryReadAssembly(assemblyName);
+#else
+ return File.Exists(assemblyName) ? new PEReader(File.OpenRead(assemblyName)) : null;
+#endif
+ }
+
+ private int? AddDebugImage(Module module)
+ {
+ var id = module.ModuleVersionId;
+
+ if (_debugImageIndexByModule.TryGetValue(id, out var idx))
+ {
+ return idx;
+ }
+
+ var assemblyName = module.FullyQualifiedName;
+ using var peReader = TryReadAssembly(assemblyName);
+ if (peReader is null)
+ {
+ _options.LogDebug("Skipping DebugImage for module '{0}' because assembly wasn't found: '{1}'",
+ module.Name, assemblyName);
+ _debugImageIndexByModule.Add(id, DebugImageMissing); // don't try to resolve again
+ return null;
+ }
+
+ string? codeId = null;
+ var headers = peReader.PEHeaders;
+ if (headers.PEHeader is { } peHeader)
+ {
+ codeId = $"{headers.CoffHeader.TimeDateStamp:X8}{peHeader.SizeOfImage:x}";
+ }
+
+ string? debugId = null;
+ string? debugFile = null;
+ string? debugChecksum = null;
+
+ var debugDirs = peReader.ReadDebugDirectory();
+ foreach (var entry in debugDirs)
+ {
+ if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
+ {
+ var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
+ var checksumHex = checksum.Checksum.AsSpan().ToHexString();
+ debugChecksum = $"{checksum.AlgorithmName}:{checksumHex}";
+ }
+ if (!entry.IsPortableCodeView)
+ {
+ continue;
+ }
+ var codeView = peReader.ReadCodeViewDebugDirectoryData(entry);
+
+ // Together 16B of the Guid concatenated with 4B of the TimeDateStamp field of the entry form a PDB ID that
+ // should be used to match the PE/COFF image with the associated PDB (instead of Guid and Age).
+ // Matching PDB ID is stored in the #Pdb stream of the .pdb file.
+ // See https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md#codeview-debug-directory-entry-type-2
+ debugId = $"{codeView.Guid}-{entry.Stamp:x}";
+ debugFile = codeView.Path;
+ }
+
+ // well, we are out of luck :-(
+ if (debugId == null)
+ {
+ _options.LogWarning("Skipping DebugImage for module '{0}' because DebugId couldn't be determined", module.Name);
+ _debugImageIndexByModule.Add(id, DebugImageMissing); // don't try to resolve again
+ return null;
+ }
+
+ idx = DebugImages.Count;
+ DebugImages.Add(new DebugImage
+ {
+ Type = "pe_dotnet",
+ CodeId = codeId,
+ CodeFile = assemblyName,
+ DebugId = debugId,
+ DebugChecksum = debugChecksum,
+ DebugFile = debugFile,
+ ModuleVersionId = id,
+ });
+ _debugImageIndexByModule.Add(id, idx);
+
+ return idx;
+ }
+}
diff --git a/src/Sentry/Internal/Extensions/HashExtensions.cs b/src/Sentry/Internal/Extensions/HashExtensions.cs
index e76ce83f4f..dfaa149917 100644
--- a/src/Sentry/Internal/Extensions/HashExtensions.cs
+++ b/src/Sentry/Internal/Extensions/HashExtensions.cs
@@ -2,23 +2,11 @@ namespace Sentry.Internal.Extensions;
internal static class HashExtensions
{
- private static string GetHexString(this byte[] data)
- {
- var buffer = new StringBuilder();
-
- foreach (var t in data)
- {
- buffer.Append(t.ToString("X2"));
- }
-
- return buffer.ToString();
- }
-
public static string GetHashString(this string str)
{
var bytes = Encoding.UTF8.GetBytes(str);
using var sha = SHA1.Create();
var hash = sha.ComputeHash(bytes);
- return hash.GetHexString();
+ return hash.ToHexString();
}
}
diff --git a/src/Sentry/Internal/Extensions/MiscExtensions.cs b/src/Sentry/Internal/Extensions/MiscExtensions.cs
index 55615dd76a..97145ed0ae 100644
--- a/src/Sentry/Internal/Extensions/MiscExtensions.cs
+++ b/src/Sentry/Internal/Extensions/MiscExtensions.cs
@@ -9,8 +9,27 @@ internal static class MiscExtensions
? value
: null;
- public static string ToHexString(this long l) =>
- "0x" + l.ToString("x", CultureInfo.InvariantCulture);
+ public static string ToHexString(this long l) => "0x" + l.ToString("x", CultureInfo.InvariantCulture);
+
+ public static string ToHexString(this byte[] bytes) => new ReadOnlySpan(bytes).ToHexString();
+
+ public static string ToHexString(this Span bytes) => ((ReadOnlySpan)bytes).ToHexString();
+
+ public static string ToHexString(this ReadOnlySpan bytes)
+ {
+#if NET5_0_OR_GREATER
+ return Convert.ToHexString(bytes).ToLowerInvariant();
+#else
+ var buffer = new StringBuilder(bytes.Length * 2);
+
+ foreach (var t in bytes)
+ {
+ buffer.Append(t.ToString("x2", CultureInfo.InvariantCulture));
+ }
+
+ return buffer.ToString();
+#endif
+ }
private static readonly TimeSpan MaxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
diff --git a/src/Sentry/Internal/MainSentryEventProcessor.cs b/src/Sentry/Internal/MainSentryEventProcessor.cs
index 0e55b07e71..ce537f9a6f 100644
--- a/src/Sentry/Internal/MainSentryEventProcessor.cs
+++ b/src/Sentry/Internal/MainSentryEventProcessor.cs
@@ -85,7 +85,8 @@ public SentryEvent Process(SentryEvent @event)
if (@event.Exception == null)
{
- var stackTrace = SentryStackTraceFactoryAccessor().Create(@event.Exception);
+ // if there's no exception, get the current stack trace
+ var stackTrace = SentryStackTraceFactoryAccessor().Create();
if (stackTrace != null)
{
var currentThread = Thread.CurrentThread;
@@ -101,6 +102,23 @@ public SentryEvent Process(SentryEvent @event)
@event.SentryThreads = @event.SentryThreads?.Any() == true
? new List(@event.SentryThreads) { thread }
: new[] { thread }.AsEnumerable();
+
+ if (stackTrace is DebugStackTrace debugStackTrace)
+ {
+ debugStackTrace.MergeDebugImagesInto(@event);
+ }
+ }
+ }
+
+ // Add all the Debug Images that were referenced from stack traces to the Event.
+ if (@event.SentryExceptions is { } sentryExceptions)
+ {
+ foreach (var sentryException in sentryExceptions)
+ {
+ if (sentryException.Stacktrace is DebugStackTrace debugStackTrace)
+ {
+ debugStackTrace.MergeDebugImagesInto(@event);
+ }
}
}
diff --git a/src/Sentry/Internal/MonoSentryStackTraceFactory.cs b/src/Sentry/Internal/MonoSentryStackTraceFactory.cs
deleted file mode 100644
index 8d7ffe5aeb..0000000000
--- a/src/Sentry/Internal/MonoSentryStackTraceFactory.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using Sentry.Extensibility;
-
-namespace Sentry.Internal;
-
-///
-/// Mono factory to from an .
-///
-internal class MonoSentryStackTraceFactory : SentryStackTraceFactory
-{
- private readonly SentryOptions _options;
-
- ///
- /// Creates an instance of .
- ///
- public MonoSentryStackTraceFactory(SentryOptions options) : base(options) => _options = options;
-
- ///
- /// Creates a from the optional .
- ///
- /// The exception to create the stacktrace from.
- /// A Sentry stack trace.
- public override SentryStackTrace? Create(Exception? exception = null)
- {
- if (exception == null)
- {
- _options.LogDebug("No Exception to collect Mono stack trace.");
-
- // ReSharper disable once RedundantArgumentDefaultValue
- return base.Create(null);
- }
-
- List? frames = null;
- if (exception.StackTrace is { } stacktrace)
- {
- foreach (var line in stacktrace.Split('\n'))
- {
- if (StackFrameData.TryParse(line, out var frame))
- {
- frames ??= new List();
- frames.Add(frame);
- }
- }
- }
-
- if (frames == null)
- {
- _options.LogWarning("Couldn't resolve a Mono stacktrace, calling fallback");
- return base.Create(exception);
- }
-
- return new SentryStackTrace
- {
- // https://develop.sentry.dev/sdk/event-payloads/stacktrace/
- Frames = frames.Select(f => new SentryStackFrame
- {
- Module = f.TypeFullName,
- InstructionOffset = f.Offset != 0 ? f.Offset : null,
- Function = f.MethodSignature,
- LineNumber = GetLineNumber(f.Line),
- }).Reverse().ToArray()
- };
-
- static int? GetLineNumber(string line)
- {
- // Protocol is uint. Also, Mono AOT / IL2CPP / no pdb means no line number (0) which isn't useful.
- if (line is { } l && int.TryParse(l, out var parsedLine) && parsedLine >= 0)
- {
- return parsedLine;
- }
-
- return null;
- }
- }
-}
diff --git a/src/Sentry/Platforms/Android/AndroidHelpers.cs b/src/Sentry/Platforms/Android/AndroidHelpers.cs
new file mode 100644
index 0000000000..130525c823
--- /dev/null
+++ b/src/Sentry/Platforms/Android/AndroidHelpers.cs
@@ -0,0 +1,21 @@
+namespace Sentry.Android;
+
+internal static class AndroidHelpers
+{
+ public static string? GetCpuAbi() => GetSupportedAbis().FirstOrDefault();
+
+ public static IList GetSupportedAbis()
+ {
+ var result = AndroidBuild.SupportedAbis;
+ if (result != null)
+ {
+ return result;
+ }
+
+#pragma warning disable CS0618
+ var abi = AndroidBuild.CpuAbi;
+#pragma warning restore CS0618
+
+ return abi != null ? new[] {abi} : Array.Empty();
+ }
+}
diff --git a/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyDirectoryReader.cs b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyDirectoryReader.cs
new file mode 100644
index 0000000000..d7e69e1e59
--- /dev/null
+++ b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyDirectoryReader.cs
@@ -0,0 +1,54 @@
+using System.Reflection.PortableExecutable;
+using Sentry.Extensibility;
+
+namespace Sentry.Android.AssemblyReader;
+
+// The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file.
+internal sealed class AndroidAssemblyDirectoryReader : AndroidAssemblyReader, IAndroidAssemblyReader
+{
+ public AndroidAssemblyDirectoryReader(ZipArchive zip, IList supportedAbis, IDiagnosticLogger? logger)
+ : base(zip, supportedAbis, logger) { }
+
+ public PEReader? TryReadAssembly(string name)
+ {
+ var zipEntry = FindAssembly(name);
+ if (zipEntry is null)
+ {
+ Logger?.LogDebug("Couldn't find assembly {0} in the APK", name);
+ return null;
+ }
+
+ Logger?.LogDebug("Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName);
+
+ // We need a seekable stream for the PEReader (or even to check whether the DLL is compressed), so make a copy.
+ var memStream = new MemoryStream((int)zipEntry.Length);
+ using (var zipStream = zipEntry.Open())
+ {
+ zipStream.CopyTo(memStream);
+ memStream.Position = 0;
+ }
+ return CreatePEReader(name, memStream);
+ }
+
+ private ZipArchiveEntry? FindAssembly(string name)
+ {
+ var zipEntry = ZipArchive.GetEntry($"assemblies/{name}");
+
+ if (zipEntry is null)
+ {
+ foreach (var abi in SupportedAbis)
+ {
+ if (abi.Length > 0)
+ {
+ zipEntry = ZipArchive.GetEntry($"assemblies/{abi}/{name}");
+ if (zipEntry is not null)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return zipEntry;
+ }
+}
diff --git a/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyReader.cs b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyReader.cs
new file mode 100644
index 0000000000..ac92e42155
--- /dev/null
+++ b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyReader.cs
@@ -0,0 +1,89 @@
+using System.Reflection.PortableExecutable;
+using Sentry.Extensibility;
+using K4os.Compression.LZ4;
+
+namespace Sentry.Android.AssemblyReader;
+
+internal abstract class AndroidAssemblyReader : IDisposable
+{
+ protected IDiagnosticLogger? Logger { get; }
+ protected ZipArchive ZipArchive { get; }
+ protected IList SupportedAbis { get; }
+
+ protected AndroidAssemblyReader(ZipArchive zip, IList supportedAbis, IDiagnosticLogger? logger)
+ {
+ ZipArchive = zip;
+ Logger = logger;
+ SupportedAbis = supportedAbis;
+ }
+
+ public void Dispose()
+ {
+ ZipArchive.Dispose();
+ }
+
+ protected PEReader CreatePEReader(string assemblyName, MemoryStream inputStream)
+ {
+ var decompressedStream = TryDecompressLZ4(assemblyName, inputStream);
+
+ // Use the decompressed stream, or if null, i.e. it wasn't compressed, use the original.
+ return new PEReader(decompressedStream ?? inputStream);
+ }
+
+ ///
+ /// The DLL may be LZ4 compressed, see https://github.com/xamarin/xamarin-android/pull/4686
+ /// The format is:
+ /// [ 4 byte magic header ] (XALZ)
+ /// [ 4 byte header index ]
+ /// [ 4 byte uncompressed payload length ]
+ /// [rest: lz4 compressed payload]
+ ///
+ ///
+ private Stream? TryDecompressLZ4(string assemblyName, MemoryStream inputStream)
+ {
+ const uint compressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
+ const int payloadOffset = 12;
+ var reader = new BinaryReader(inputStream);
+ if (reader.ReadUInt32() != compressedDataMagic)
+ {
+ // Restore the input stream to the beginning if we're not decompressing.
+ inputStream.Position = 0;
+ return null;
+ }
+ reader.ReadUInt32(); // ignore descriptor index, we don't need it
+ var decompressedLength = reader.ReadInt32();
+ Debug.Assert(inputStream.Position == payloadOffset);
+ var inputLength = (int)(inputStream.Length - payloadOffset);
+
+ Logger?.LogDebug("Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength);
+
+ var outputStream = new MemoryStream(decompressedLength);
+
+ // We're writing to the underlying array manually, so we need to set the length.
+ outputStream.SetLength(decompressedLength);
+ var outputBuffer = outputStream.GetBuffer();
+
+ var inputBuffer = inputStream is MemorySlice slice ? slice.FullBuffer : inputStream.GetBuffer();
+ var offset = inputStream is MemorySlice memorySlice ? memorySlice.Offset + payloadOffset : payloadOffset;
+ var decoded = LZ4Codec.Decode(inputBuffer, offset, inputLength, outputBuffer, 0, decompressedLength);
+ if (decoded != decompressedLength)
+ {
+ throw new Exception($"Failed to decompress LZ4 data of assembly {assemblyName} - decoded {decoded} instead of expected {decompressedLength} bytes");
+ }
+ return outputStream;
+ }
+
+ // Allows consumer to access the underlying buffer even if the MemoryStream is created as a slice over another.
+ // Plain MemoryStream would throw "MemoryStream's internal buffer cannot be accessed."
+ protected class MemorySlice : MemoryStream
+ {
+ public readonly int Offset;
+ public readonly byte[] FullBuffer;
+
+ public MemorySlice(MemoryStream other, int offset, int size) : base(other.GetBuffer(), offset, size, writable: false)
+ {
+ Offset = offset;
+ FullBuffer = other.GetBuffer();
+ }
+ }
+}
diff --git a/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyReaderFactory.cs b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyReaderFactory.cs
new file mode 100644
index 0000000000..6a54cd1f90
--- /dev/null
+++ b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyReaderFactory.cs
@@ -0,0 +1,21 @@
+using Sentry.Extensibility;
+
+namespace Sentry.Android.AssemblyReader;
+
+internal static class AndroidAssemblyReaderFactory
+{
+ public static IAndroidAssemblyReader Open(string apkPath, IList supportedAbis, IDiagnosticLogger? logger)
+ {
+ logger?.LogDebug("Opening APK: {0}", apkPath);
+ var zipArchive = ZipFile.Open(apkPath, ZipArchiveMode.Read);
+
+ if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null)
+ {
+ logger?.LogDebug("APK uses AssemblyStore");
+ return new AndroidAssemblyStoreReader(zipArchive, supportedAbis, logger);
+ }
+
+ logger?.LogDebug("APK doesn't use AssemblyStore");
+ return new AndroidAssemblyDirectoryReader(zipArchive, supportedAbis, logger);
+ }
+}
diff --git a/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyStoreReader.cs b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyStoreReader.cs
new file mode 100644
index 0000000000..d25473e6b1
--- /dev/null
+++ b/src/Sentry/Platforms/Android/AssemblyReader/AndroidAssemblyStoreReader.cs
@@ -0,0 +1,513 @@
+using System.Reflection.PortableExecutable;
+using Sentry.Extensibility;
+
+namespace Sentry.Android.AssemblyReader;
+
+// See https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores
+internal sealed class AndroidAssemblyStoreReader : AndroidAssemblyReader, IAndroidAssemblyReader
+{
+ private readonly AssemblyStoreExplorer _explorer;
+
+ public AndroidAssemblyStoreReader(ZipArchive zip, IList supportedAbis, IDiagnosticLogger? logger)
+ : base(zip, supportedAbis, logger)
+ {
+ _explorer = new(zip, supportedAbis, logger);
+ }
+
+ public PEReader? TryReadAssembly(string name)
+ {
+ var assembly = TryFindAssembly(name);
+ if (assembly is null)
+ {
+ Logger?.LogDebug("Couldn't find assembly {0} in the APK AssemblyStore", name);
+ return null;
+ }
+
+ Logger?.LogDebug("Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch);
+
+ var stream = assembly.GetImage();
+ if (stream is null)
+ {
+ Logger?.LogDebug("Couldn't access assembly {0} image stream", name);
+ return null;
+ }
+
+ return CreatePEReader(name, stream);
+ }
+
+ private AssemblyStoreAssembly? TryFindAssembly(string name)
+ {
+ if (_explorer.AssembliesByName.TryGetValue(name, out var assembly))
+ {
+ return assembly;
+ }
+
+ if (name.EndsWith(".dll", ignoreCase: true, CultureInfo.InvariantCulture) ||
+ name.EndsWith(".exe", ignoreCase: true, CultureInfo.InvariantCulture))
+ {
+ if (_explorer.AssembliesByName.TryGetValue(name.Substring(0, name.Length - 4), out assembly))
+ {
+ return assembly;
+ }
+ }
+
+ return null;
+ }
+
+ // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs
+ // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE).
+ private class AssemblyStoreExplorer
+ {
+ private AssemblyStoreReader? _indexStore;
+ private readonly AssemblyStoreManifestReader _manifest;
+ private readonly IDiagnosticLogger? _logger;
+
+ public IDictionary AssembliesByName { get; } =
+ new SortedDictionary(StringComparer.OrdinalIgnoreCase);
+
+ public IDictionary AssembliesByHash32 { get; } =
+ new Dictionary();
+
+ public IDictionary AssembliesByHash64 { get; } =
+ new Dictionary();
+
+ public List Assemblies { get; } = new();
+
+ public IDictionary> Stores { get; } =
+ new SortedDictionary>();
+
+ public AssemblyStoreExplorer(ZipArchive zip, IList supportedAbis, IDiagnosticLogger? logger)
+ {
+ _logger = logger;
+ _manifest = new AssemblyStoreManifestReader(zip.GetEntry("assemblies/assemblies.manifest")!.Open());
+
+ TryAddStore(zip, null);
+ foreach (var abi in supportedAbis)
+ {
+ if (!string.IsNullOrEmpty(abi))
+ {
+ TryAddStore(zip, abi);
+ }
+ }
+
+ zip.Dispose();
+ ProcessStores();
+ }
+
+ private void ProcessStores()
+ {
+ if (Stores.Count == 0 || _indexStore == null)
+ {
+ return;
+ }
+
+ ProcessIndex(_indexStore.GlobalIndex32, "32", (he, assembly) =>
+ {
+ assembly.Hash32 = (uint)he.Hash;
+ assembly.RuntimeIndex = he.MappingIndex;
+
+ if (_manifest.EntriesByHash32.TryGetValue(assembly.Hash32, out var me))
+ {
+ assembly.Name = me.Name;
+ }
+
+ if (!AssembliesByHash32.ContainsKey(assembly.Hash32))
+ {
+ AssembliesByHash32.Add(assembly.Hash32, assembly);
+ }
+ });
+
+ ProcessIndex(_indexStore.GlobalIndex64, "64", (he, assembly) =>
+ {
+ assembly.Hash64 = he.Hash;
+ if (assembly.RuntimeIndex != he.MappingIndex)
+ {
+ _logger?.LogDebug(
+ $"assembly with hashes 0x{assembly.Hash32} and 0x{assembly.Hash64} has a different 32-bit runtime index ({assembly.RuntimeIndex}) than the 64-bit runtime index({he.MappingIndex})");
+ }
+
+ if (_manifest.EntriesByHash64.TryGetValue(assembly.Hash64, out var me))
+ {
+ if (string.IsNullOrEmpty(assembly.Name))
+ {
+ _logger?.LogDebug(
+ $"32-bit hash 0x{assembly.Hash32:x} did not match any assembly name in the manifest");
+ assembly.Name = me.Name;
+ if (string.IsNullOrEmpty(assembly.Name))
+ {
+ _logger?.LogDebug(
+ $"64-bit hash 0x{assembly.Hash64:x} did not match any assembly name in the manifest");
+ }
+ }
+ else if (!string.Equals(assembly.Name, me.Name, StringComparison.Ordinal))
+ {
+ _logger?.LogDebug(
+ $"32-bit hash 0x{assembly.Hash32:x} maps to assembly name '{assembly.Name}', however 64-bit hash 0x{assembly.Hash64:x} for the same entry matches assembly name '{me.Name}'");
+ }
+ }
+
+ if (!AssembliesByHash64.ContainsKey(assembly.Hash64))
+ {
+ AssembliesByHash64.Add(assembly.Hash64, assembly);
+ }
+ });
+
+ foreach (var kvp in Stores)
+ {
+ var list = kvp.Value;
+ if (list.Count < 2)
+ {
+ continue;
+ }
+
+ var template = list[0];
+ for (var i = 1; i < list.Count; i++)
+ {
+ var other = list[i];
+ if (!template.HasIdenticalContent(other))
+ {
+ throw new Exception(
+ $"Store ID {template.StoreId} for architecture {other.Arch} is not identical to other stores with the same ID");
+ }
+ }
+ }
+
+ void ProcessIndex(List index, string bitness,
+ Action assemblyHandler)
+ {
+ foreach (var he in index)
+ {
+ if (!Stores.TryGetValue(he.StoreId, out var storeList))
+ {
+ _logger?.LogDebug($"store with id {he.StoreId} not part of the set");
+ continue;
+ }
+
+ foreach (var store in storeList)
+ {
+ if (he.LocalStoreIndex >= (uint)store.Assemblies.Count)
+ {
+ _logger?.LogDebug(
+ $"{bitness}-bit index entry with hash 0x{he.Hash:x} has invalid store {store.StoreId} index {he.LocalStoreIndex} (maximum allowed is {store.Assemblies.Count})");
+ continue;
+ }
+
+ var assembly = store.Assemblies[(int)he.LocalStoreIndex];
+ assemblyHandler(he, assembly);
+
+ if (!AssembliesByName.ContainsKey(assembly.Name))
+ {
+ AssembliesByName.Add(assembly.Name, assembly);
+ }
+ }
+ }
+ }
+ }
+
+ private void TryAddStore(ZipArchive archive, string? abi)
+ {
+ var infix = abi is null ? "" : $".{abi}";
+ if (archive.GetEntry($"assemblies/assemblies{infix}.blob") is { } zipEntry)
+ {
+ var memStream = new MemoryStream((int)zipEntry.Length);
+ using (var zipStream = zipEntry.Open())
+ {
+ zipStream.CopyTo(memStream);
+ memStream.Position = 0;
+ }
+
+ AddStore(new AssemblyStoreReader(memStream, abi));
+ }
+ }
+
+ private void AddStore(AssemblyStoreReader reader)
+ {
+ if (reader.HasGlobalIndex)
+ {
+ _indexStore = reader;
+ }
+
+ if (!Stores.TryGetValue(reader.StoreId, out var storeList))
+ {
+ storeList = new List();
+ Stores.Add(reader.StoreId, storeList);
+ }
+
+ storeList.Add(reader);
+
+ Assemblies.AddRange(reader.Assemblies);
+ }
+ }
+
+ // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestReader.cs
+ // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE).
+ private class AssemblyStoreManifestReader
+ {
+ public List Entries { get; } = new();
+ public Dictionary EntriesByHash32 { get; } = new();
+ public Dictionary EntriesByHash64 { get; } = new();
+
+ public AssemblyStoreManifestReader(Stream manifest)
+ {
+ using var sr = new StreamReader(manifest, Encoding.UTF8, detectEncodingFromByteOrderMarks: false);
+ ReadManifest(sr);
+ }
+
+ private void ReadManifest(StreamReader reader)
+ {
+ // First line is ignored, it contains headers
+ reader.ReadLine();
+
+ // Each subsequent line consists of fields separated with any number of spaces (for the pleasure of a human being reading the manifest)
+ while (!reader.EndOfStream)
+ {
+ var fields = reader.ReadLine()?.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+ if (fields == null)
+ {
+ continue;
+ }
+
+ var entry = new AssemblyStoreManifestEntry(fields);
+ Entries.Add(entry);
+ if (entry.Hash32 != 0)
+ {
+ EntriesByHash32.Add(entry.Hash32, entry);
+ }
+
+ if (entry.Hash64 != 0)
+ {
+ EntriesByHash64.Add(entry.Hash64, entry);
+ }
+ }
+ }
+ }
+
+ // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestEntry.cs
+ // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE).
+ private class AssemblyStoreManifestEntry
+ {
+ // Fields are:
+ // Hash 32 | Hash 64 | Store ID | Store idx | Name
+ private const int NumberOfFields = 5;
+ private const int Hash32FieldIndex = 0;
+ private const int Hash64FieldIndex = 1;
+ private const int StoreIdFieldIndex = 2;
+ private const int StoreIndexFieldIndex = 3;
+ private const int NameFieldIndex = 4;
+
+ public uint Hash32 { get; }
+ public ulong Hash64 { get; }
+ public uint StoreId { get; }
+ public uint IndexInStore { get; }
+ public string Name { get; }
+
+ public AssemblyStoreManifestEntry(string[] fields)
+ {
+ if (fields.Length != NumberOfFields)
+ {
+ throw new ArgumentOutOfRangeException(nameof(fields), "Invalid number of fields");
+ }
+
+ Hash32 = GetUInt32(fields[Hash32FieldIndex]);
+ Hash64 = GetUInt64(fields[Hash64FieldIndex]);
+ StoreId = GetUInt32(fields[StoreIdFieldIndex]);
+ IndexInStore = GetUInt32(fields[StoreIndexFieldIndex]);
+ Name = fields[NameFieldIndex].Trim();
+ }
+
+ private static uint GetUInt32(string value)
+ {
+ if (uint.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash))
+ {
+ return hash;
+ }
+
+ return 0;
+ }
+
+ private static ulong GetUInt64(string value)
+ {
+ if (ulong.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash))
+ {
+ return hash;
+ }
+
+ return 0;
+ }
+
+ private static string PrepHexValue(string value)
+ {
+ if (value.StartsWith("0x", StringComparison.Ordinal))
+ {
+ return value.Substring(2);
+ }
+
+ return value;
+ }
+ }
+
+ // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreHashEntry.cs
+ // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE).
+ private class AssemblyStoreHashEntry
+ {
+ public bool Is32Bit { get; }
+ public ulong Hash { get; }
+ public uint MappingIndex { get; }
+ public uint LocalStoreIndex { get; }
+ public uint StoreId { get; }
+
+ internal AssemblyStoreHashEntry(BinaryReader reader, bool is32Bit)
+ {
+ Is32Bit = is32Bit;
+ Hash = reader.ReadUInt64();
+ MappingIndex = reader.ReadUInt32();
+ LocalStoreIndex = reader.ReadUInt32();
+ StoreId = reader.ReadUInt32();
+ }
+ }
+
+ // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreReader.cs
+ // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE).
+ private class AssemblyStoreReader
+ {
+ // These two constants must be identical to the native ones in src/monodroid/jni/xamarin-app.hh
+ private const uint ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian
+ private const uint ASSEMBLY_STORE_FORMAT_VERSION = 1; // The highest format version this reader understands
+
+ private readonly MemoryStream _storeData;
+
+ public uint Version { get; private set; }
+ public uint LocalEntryCount { get; private set; }
+ public uint GlobalEntryCount { get; private set; }
+ public uint StoreId { get; private set; }
+ public List Assemblies { get; }
+ public List GlobalIndex32 { get; } = new();
+ public List GlobalIndex64 { get; } = new();
+ public string Arch { get; }
+
+ public bool HasGlobalIndex => StoreId == 0;
+
+ public AssemblyStoreReader(MemoryStream store, string? arch = null)
+ {
+ Arch = arch ?? string.Empty;
+ _storeData = store;
+ using var reader = new BinaryReader(store, Encoding.UTF8, leaveOpen: true);
+ ReadHeader(reader);
+
+ Assemblies = new List();
+ ReadLocalEntries(reader, Assemblies);
+ if (HasGlobalIndex)
+ {
+ ReadGlobalIndex(reader, GlobalIndex32, GlobalIndex64);
+ }
+ }
+
+ internal MemoryStream? GetAssemblyImageSlice(AssemblyStoreAssembly assembly) =>
+ GetDataSlice(assembly.DataOffset, assembly.DataSize);
+
+ internal MemoryStream? GetAssemblyDebugDataSlice(AssemblyStoreAssembly assembly) =>
+ assembly.DebugDataOffset == 0 ? null : GetDataSlice(assembly.DebugDataOffset, assembly.DebugDataSize);
+
+ internal MemoryStream? GetAssemblyConfigSlice(AssemblyStoreAssembly assembly) =>
+ assembly.ConfigDataOffset == 0 ? null : GetDataSlice(assembly.ConfigDataOffset, assembly.ConfigDataSize);
+
+ private MemoryStream? GetDataSlice(uint offset, uint size) =>
+ size == 0 ? null : new MemorySlice(_storeData, (int)offset, (int)size);
+
+ public bool HasIdenticalContent(AssemblyStoreReader other)
+ {
+ return
+ other.Version == Version &&
+ other.LocalEntryCount == LocalEntryCount &&
+ other.GlobalEntryCount == GlobalEntryCount &&
+ other.StoreId == StoreId &&
+ other.Assemblies.Count == Assemblies.Count &&
+ other.GlobalIndex32.Count == GlobalIndex32.Count &&
+ other.GlobalIndex64.Count == GlobalIndex64.Count;
+ }
+
+ private void ReadHeader(BinaryReader reader)
+ {
+ if (reader.ReadUInt32() != ASSEMBLY_STORE_MAGIC)
+ {
+ throw new InvalidOperationException("Invalid header magic number");
+ }
+
+ Version = reader.ReadUInt32();
+ if (Version == 0)
+ {
+ throw new InvalidOperationException("Invalid version number: 0");
+ }
+
+ if (Version > ASSEMBLY_STORE_FORMAT_VERSION)
+ {
+ throw new InvalidOperationException(
+ $"Store format version {Version} is higher than the one understood by this reader, {ASSEMBLY_STORE_FORMAT_VERSION}");
+ }
+
+ LocalEntryCount = reader.ReadUInt32();
+ GlobalEntryCount = reader.ReadUInt32();
+ StoreId = reader.ReadUInt32();
+ }
+
+ private void ReadLocalEntries(BinaryReader reader, List assemblies)
+ {
+ for (uint i = 0; i < LocalEntryCount; i++)
+ {
+ assemblies.Add(new AssemblyStoreAssembly(reader, this));
+ }
+ }
+
+ private void ReadGlobalIndex(BinaryReader reader, List index32,
+ List index64)
+ {
+ ReadIndex(true, index32);
+ ReadIndex(false, index64);
+
+ void ReadIndex(bool is32Bit, List index)
+ {
+ for (uint i = 0; i < GlobalEntryCount; i++)
+ {
+ index.Add(new AssemblyStoreHashEntry(reader, is32Bit));
+ }
+ }
+ }
+ }
+
+ // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreAssembly.cs
+ // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE).
+ private class AssemblyStoreAssembly
+ {
+ public uint DataOffset { get; }
+ public uint DataSize { get; }
+ public uint DebugDataOffset { get; }
+ public uint DebugDataSize { get; }
+ public uint ConfigDataOffset { get; }
+ public uint ConfigDataSize { get; }
+
+ public uint Hash32 { get; set; }
+ public ulong Hash64 { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public uint RuntimeIndex { get; set; }
+
+ public AssemblyStoreReader Store { get; }
+
+ internal AssemblyStoreAssembly(BinaryReader reader, AssemblyStoreReader store)
+ {
+ Store = store;
+
+ DataOffset = reader.ReadUInt32();
+ DataSize = reader.ReadUInt32();
+ DebugDataOffset = reader.ReadUInt32();
+ DebugDataSize = reader.ReadUInt32();
+ ConfigDataOffset = reader.ReadUInt32();
+ ConfigDataSize = reader.ReadUInt32();
+ }
+
+ public MemoryStream? GetImage() => Store.GetAssemblyImageSlice(this);
+
+ public MemoryStream? GetDebugData() => Store.GetAssemblyDebugDataSlice(this);
+
+ public MemoryStream? GetAssemblyConfig() => Store.GetAssemblyConfigSlice(this);
+ }
+}
diff --git a/src/Sentry/Platforms/Android/AssemblyReader/IAndroidAssemblyReader.cs b/src/Sentry/Platforms/Android/AssemblyReader/IAndroidAssemblyReader.cs
new file mode 100644
index 0000000000..874de9b422
--- /dev/null
+++ b/src/Sentry/Platforms/Android/AssemblyReader/IAndroidAssemblyReader.cs
@@ -0,0 +1,8 @@
+using System.Reflection.PortableExecutable;
+
+namespace Sentry.Android.AssemblyReader;
+
+internal interface IAndroidAssemblyReader : IDisposable
+{
+ PEReader? TryReadAssembly(string name);
+}
diff --git a/src/Sentry/Platforms/Android/Extensions/DeviceExtensions.cs b/src/Sentry/Platforms/Android/Extensions/DeviceExtensions.cs
index 25bf9b9d84..b608480191 100644
--- a/src/Sentry/Platforms/Android/Extensions/DeviceExtensions.cs
+++ b/src/Sentry/Platforms/Android/Extensions/DeviceExtensions.cs
@@ -9,17 +9,7 @@ public static void ApplyFromAndroidRuntime(this Device device)
device.Manufacturer ??= AndroidBuild.Manufacturer;
device.Brand ??= AndroidBuild.Brand;
device.Model ??= AndroidBuild.Model;
-
- if (AndroidBuild.SupportedAbis is { } abis)
- {
- device.Architecture ??= abis[0];
- }
- else
- {
-#pragma warning disable CS0618 // Type or member is obsolete
- device.Architecture ??= AndroidBuild.CpuAbi;
-#pragma warning restore CS0618 // Type or member is obsolete
- }
+ device.Architecture ??= AndroidHelpers.GetCpuAbi();
}
public static void ApplyFromSentryAndroidSdk(this Device device, JavaSdk.Protocol.Device d)
diff --git a/src/Sentry/Platforms/Android/Sentry.Android.props b/src/Sentry/Platforms/Android/Sentry.Android.props
index 484d446d44..3b7d430f06 100644
--- a/src/Sentry/Platforms/Android/Sentry.Android.props
+++ b/src/Sentry/Platforms/Android/Sentry.Android.props
@@ -2,6 +2,7 @@
+
diff --git a/src/Sentry/Properties/AssemblyInfo.cs b/src/Sentry/Properties/AssemblyInfo.cs
index ea786af7e2..971cbd7de9 100644
--- a/src/Sentry/Properties/AssemblyInfo.cs
+++ b/src/Sentry/Properties/AssemblyInfo.cs
@@ -8,6 +8,7 @@
[assembly: InternalsVisibleTo("Sentry.NLog, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.NLog.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.Serilog.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
+[assembly: InternalsVisibleTo("Sentry.Android.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.AspNet, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.AspNet.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.AspNetCore.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
diff --git a/src/Sentry/SentryStackFrame.cs b/src/Sentry/SentryStackFrame.cs
index 1c6766cd97..a04df6e451 100644
--- a/src/Sentry/SentryStackFrame.cs
+++ b/src/Sentry/SentryStackFrame.cs
@@ -138,6 +138,14 @@ public sealed class SentryStackFrame : IJsonSerializable
///
public string? AddressMode { get; set; }
+ ///
+ /// The optional Function Id.
+ /// This is derived from the `MetadataToken`, and should be the record id
+ /// of a `MethodDef`.
+ /// This should be a string with a hexadecimal number that includes a 0x prefix.
+ ///
+ public string? FunctionId { get; set; }
+
///
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
@@ -162,6 +170,7 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteStringIfNotWhiteSpace("instruction_addr", InstructionAddress);
writer.WriteNumberIfNotNull("instruction_offset", InstructionOffset);
writer.WriteStringIfNotWhiteSpace("addr_mode", AddressMode);
+ writer.WriteStringIfNotWhiteSpace("function_id", FunctionId);
writer.WriteEndObject();
}
@@ -213,6 +222,7 @@ public static SentryStackFrame FromJson(JsonElement json)
var instructionAddress = json.GetPropertyOrNull("instruction_addr")?.GetString();
var instructionOffset = json.GetPropertyOrNull("instruction_offset")?.GetInt64();
var addressMode = json.GetPropertyOrNull("addr_mode")?.GetString();
+ var functionId = json.GetPropertyOrNull("function_id")?.GetString();
return new SentryStackFrame
{
@@ -235,6 +245,7 @@ public static SentryStackFrame FromJson(JsonElement json)
InstructionAddress = instructionAddress,
InstructionOffset = instructionOffset,
AddressMode = addressMode,
+ FunctionId = functionId,
};
}
}
diff --git a/src/Sentry/SentryStackTrace.cs b/src/Sentry/SentryStackTrace.cs
index e4d9215ba4..0951a08e7b 100644
--- a/src/Sentry/SentryStackTrace.cs
+++ b/src/Sentry/SentryStackTrace.cs
@@ -11,7 +11,7 @@ namespace Sentry;
/// Frames should be sorted from oldest to newest.
///
///
-public sealed class SentryStackTrace : IJsonSerializable
+public class SentryStackTrace : IJsonSerializable
{
internal IList? InternalFrames { get; private set; }
diff --git a/test/AndroidTestApp/AndroidManifest.xml b/test/AndroidTestApp/AndroidManifest.xml
new file mode 100644
index 0000000000..3fead98ca0
--- /dev/null
+++ b/test/AndroidTestApp/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/AndroidTestApp/AndroidTestApp.csproj b/test/AndroidTestApp/AndroidTestApp.csproj
new file mode 100644
index 0000000000..1ff3c71975
--- /dev/null
+++ b/test/AndroidTestApp/AndroidTestApp.csproj
@@ -0,0 +1,12 @@
+
+
+ net7.0-android
+ 21
+ Exe
+ enable
+ enable
+ com.companyname.AndroidTestApp
+ 1
+ 1.0
+
+
diff --git a/test/AndroidTestApp/Directory.Build.props b/test/AndroidTestApp/Directory.Build.props
new file mode 100644
index 0000000000..bbb7413d29
--- /dev/null
+++ b/test/AndroidTestApp/Directory.Build.props
@@ -0,0 +1,2 @@
+
+
diff --git a/test/AndroidTestApp/Directory.Build.targets b/test/AndroidTestApp/Directory.Build.targets
new file mode 100644
index 0000000000..bbb7413d29
--- /dev/null
+++ b/test/AndroidTestApp/Directory.Build.targets
@@ -0,0 +1,2 @@
+
+
diff --git a/test/AndroidTestApp/MainActivity.cs b/test/AndroidTestApp/MainActivity.cs
new file mode 100644
index 0000000000..4d26918933
--- /dev/null
+++ b/test/AndroidTestApp/MainActivity.cs
@@ -0,0 +1,13 @@
+namespace AndroidTestApp;
+
+[Activity(Label = "@string/app_name", MainLauncher = true)]
+public class MainActivity : Activity
+{
+ protected override void OnCreate(Bundle? savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ // Set our view from the "main" layout resource
+ SetContentView(Resource.Layout.activity_main);
+ }
+}
\ No newline at end of file
diff --git a/test/AndroidTestApp/Readme.md b/test/AndroidTestApp/Readme.md
new file mode 100644
index 0000000000..289b49cab7
--- /dev/null
+++ b/test/AndroidTestApp/Readme.md
@@ -0,0 +1 @@
+This is a minimal Android app created by `dotnet new android`. It is used solely for testing by the `AndroidAssemblyReaderTests`.
diff --git a/test/AndroidTestApp/Resources/layout/activity_main.xml b/test/AndroidTestApp/Resources/layout/activity_main.xml
new file mode 100644
index 0000000000..f94985291b
--- /dev/null
+++ b/test/AndroidTestApp/Resources/layout/activity_main.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/AndroidTestApp/Resources/mipmap-anydpi-v26/appicon.xml b/test/AndroidTestApp/Resources/mipmap-anydpi-v26/appicon.xml
new file mode 100644
index 0000000000..7751f69514
--- /dev/null
+++ b/test/AndroidTestApp/Resources/mipmap-anydpi-v26/appicon.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/AndroidTestApp/Resources/mipmap-anydpi-v26/appicon_round.xml b/test/AndroidTestApp/Resources/mipmap-anydpi-v26/appicon_round.xml
new file mode 100644
index 0000000000..7751f69514
--- /dev/null
+++ b/test/AndroidTestApp/Resources/mipmap-anydpi-v26/appicon_round.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/AndroidTestApp/Resources/mipmap-hdpi/appicon.png b/test/AndroidTestApp/Resources/mipmap-hdpi/appicon.png
new file mode 100644
index 0000000000..f97217f4aa
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-hdpi/appicon.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-hdpi/appicon_background.png b/test/AndroidTestApp/Resources/mipmap-hdpi/appicon_background.png
new file mode 100644
index 0000000000..513e69d841
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-hdpi/appicon_background.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-hdpi/appicon_foreground.png b/test/AndroidTestApp/Resources/mipmap-hdpi/appicon_foreground.png
new file mode 100644
index 0000000000..99d3a291bc
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-hdpi/appicon_foreground.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-mdpi/appicon.png b/test/AndroidTestApp/Resources/mipmap-mdpi/appicon.png
new file mode 100644
index 0000000000..76ceb98d48
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-mdpi/appicon.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-mdpi/appicon_background.png b/test/AndroidTestApp/Resources/mipmap-mdpi/appicon_background.png
new file mode 100644
index 0000000000..9e2d1e4d8d
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-mdpi/appicon_background.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-mdpi/appicon_foreground.png b/test/AndroidTestApp/Resources/mipmap-mdpi/appicon_foreground.png
new file mode 100644
index 0000000000..a28d342c1c
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-mdpi/appicon_foreground.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon.png b/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon.png
new file mode 100644
index 0000000000..83f089c37b
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon_background.png b/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon_background.png
new file mode 100644
index 0000000000..658be3fb6a
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon_background.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon_foreground.png b/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon_foreground.png
new file mode 100644
index 0000000000..70a542ac03
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xhdpi/appicon_foreground.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon.png b/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon.png
new file mode 100644
index 0000000000..988160a190
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon_background.png b/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon_background.png
new file mode 100644
index 0000000000..9171c3e404
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon_background.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon_foreground.png b/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon_foreground.png
new file mode 100644
index 0000000000..cb63bfb9f8
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xxhdpi/appicon_foreground.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon.png b/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon.png
new file mode 100644
index 0000000000..b65da5a735
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon_background.png b/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon_background.png
new file mode 100644
index 0000000000..1232d8c8d2
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon_background.png differ
diff --git a/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon_foreground.png b/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon_foreground.png
new file mode 100644
index 0000000000..9f9c9e6d93
Binary files /dev/null and b/test/AndroidTestApp/Resources/mipmap-xxxhdpi/appicon_foreground.png differ
diff --git a/test/AndroidTestApp/Resources/values/ic_launcher_background.xml b/test/AndroidTestApp/Resources/values/ic_launcher_background.xml
new file mode 100644
index 0000000000..6ec24e6413
--- /dev/null
+++ b/test/AndroidTestApp/Resources/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #2C3E50
+
\ No newline at end of file
diff --git a/test/AndroidTestApp/Resources/values/strings.xml b/test/AndroidTestApp/Resources/values/strings.xml
new file mode 100644
index 0000000000..5daa9b1b4a
--- /dev/null
+++ b/test/AndroidTestApp/Resources/values/strings.xml
@@ -0,0 +1,4 @@
+
+ AndroidTestApp
+ Hello, Android!
+
diff --git a/test/Sentry.Android.Tests/AndroidAssemblyReaderTests.cs b/test/Sentry.Android.Tests/AndroidAssemblyReaderTests.cs
new file mode 100644
index 0000000000..26de3fd73f
--- /dev/null
+++ b/test/Sentry.Android.Tests/AndroidAssemblyReaderTests.cs
@@ -0,0 +1,94 @@
+using Sentry.Android.AssemblyReader;
+
+namespace Sentry.Android.Tests;
+
+public class AndroidAssemblyReaderTests
+{
+ private readonly ITestOutputHelper _output;
+ private readonly IDiagnosticLogger _logger;
+
+ public AndroidAssemblyReaderTests(ITestOutputHelper output)
+ {
+ _output = output;
+ _logger = new TestOutputDiagnosticLogger(output);
+ }
+
+ private IAndroidAssemblyReader GetSut(bool isAssemblyStore, bool isCompressed)
+ {
+#if ANDROID
+ // On Android, this gets the current app APK.
+ var apkPath = Environment.CommandLine;
+ var supportedAbis = Sentry.Android.AndroidHelpers.GetSupportedAbis();
+#else
+ var apkPath = Path.Combine(
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
+ "..", "..", "..", "TestAPKs",
+ $"android-Store={isAssemblyStore}-Compressed={isCompressed}.apk");
+
+ var supportedAbis = new List { "x86_64" };
+#endif
+ _output.WriteLine($"Checking if APK exists: {apkPath}");
+ File.Exists(apkPath).Should().BeTrue();
+
+ return AndroidAssemblyReaderFactory.Open(apkPath, supportedAbis, _logger);
+ }
+
+ [SkippableTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CreatesCorrectReader(bool isAssemblyStore)
+ {
+#if ANDROID
+ Skip.If(true, "It's unknown whether the current Android app APK is an assembly store or not.");
+#endif
+ using var sut = GetSut(isAssemblyStore, isCompressed: true);
+ if (isAssemblyStore)
+ {
+ Assert.IsType(sut);
+ }
+ else
+ {
+ Assert.IsType(sut);
+ }
+ }
+
+ [SkippableTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void ReturnsNullIfAssemblyDoesntExist(bool isAssemblyStore)
+ {
+ using var sut = GetSut(isAssemblyStore, isCompressed: true);
+ Assert.Null(sut.TryReadAssembly("NonExistent.dll"));
+ }
+
+ [SkippableTheory]
+ [InlineData(false, true, "Mono.Android.dll")]
+ [InlineData(false, false, "Mono.Android.dll")]
+ [InlineData(false, true, "System.Runtime.dll")]
+ [InlineData(false, false, "System.Runtime.dll")]
+ [InlineData(true, true, "Mono.Android.dll")]
+ [InlineData(true, false, "Mono.Android.dll")]
+ [InlineData(true, true, "System.Runtime.dll")]
+ [InlineData(true, false, "System.Runtime.dll")]
+ public void ReadsAssembly(bool isAssemblyStore, bool isCompressed, string assemblyName)
+ {
+#if ANDROID
+ // No need to run all combinations - we only test the current APK which is (likely) compressed assembly store.
+ Skip.If(!isAssemblyStore);
+ Skip.If(!isCompressed);
+#endif
+ using var sut = GetSut(isAssemblyStore, isCompressed);
+
+ var peReader = sut.TryReadAssembly(assemblyName);
+ Assert.NotNull(peReader);
+ Assert.True(peReader.HasMetadata);
+
+ var headers = peReader.PEHeaders;
+ Assert.True(headers.IsDll);
+ headers.MetadataSize.Should().BeGreaterThan(0);
+ Assert.NotNull(headers.PEHeader);
+ headers.PEHeader.SizeOfImage.Should().BeGreaterThan(0);
+ var debugDirs = peReader.ReadDebugDirectory();
+ debugDirs.Length.Should().BeGreaterThan(0);
+ }
+}
diff --git a/test/Sentry.Android.Tests/Sentry.Android.Tests.csproj b/test/Sentry.Android.Tests/Sentry.Android.Tests.csproj
new file mode 100644
index 0000000000..8217860e05
--- /dev/null
+++ b/test/Sentry.Android.Tests/Sentry.Android.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net7.0;net6.0;net7.0-android
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Sentry.AspNetCore.Tests/ModuleInit.cs b/test/Sentry.AspNetCore.Tests/ModuleInit.cs
index cc68680029..97205a6634 100644
--- a/test/Sentry.AspNetCore.Tests/ModuleInit.cs
+++ b/test/Sentry.AspNetCore.Tests/ModuleInit.cs
@@ -4,9 +4,6 @@ namespace Sentry.AspNetCore.Tests;
public static class ModuleInit
{
[ModuleInitializer]
- public static void Init()
- {
- VerifyHttp.Enable();
- }
+ public static void Init() => VerifyHttp.Enable();
}
#endif
diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet6_0.verified.txt b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet6_0.verified.txt
index 35e5c53e66..ec6cab3b35 100644
--- a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet6_0.verified.txt
+++ b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet6_0.verified.txt
@@ -14,6 +14,47 @@ WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
},
Logger: Microsoft.EntityFrameworkCore.Database.Command,
Platform: csharp,
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.Extensions.Logging.pdb",
+ "code_id": "____________",
+ "code_file": ".../Microsoft.Extensions.Logging.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.Extensions.Logging.Abstractions.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Microsoft.Extensions.Logging.Abstractions.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.EntityFrameworkCore.pdb",
+ "code_id": "______________",
+ "code_file": ".../Microsoft.EntityFrameworkCore.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.EntityFrameworkCore.Relational.pdb",
+ "code_id": "______________",
+ "code_file": ".../Microsoft.EntityFrameworkCore.Relational.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_file": ".../System.Private.CoreLib.pdb",
+ "code_id": "______________",
+ "code_file": ".../System.Private.CoreLib.dll",
+ }
+ ],
Level: error,
Request: {},
Contexts: {},
diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet7_0.verified.txt b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet7_0.verified.txt
index 35e5c53e66..0ce1adf3a1 100644
--- a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet7_0.verified.txt
+++ b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.Logging.DotNet7_0.verified.txt
@@ -14,6 +14,48 @@ WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
},
Logger: Microsoft.EntityFrameworkCore.Database.Command,
Platform: csharp,
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.Extensions.Logging.pdb",
+ "code_id": "____________",
+ "code_file": ".../Microsoft.Extensions.Logging.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.Extensions.Logging.Abstractions.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Microsoft.Extensions.Logging.Abstractions.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.EntityFrameworkCore.pdb",
+ "code_id": "______________",
+ "code_file": ".../Microsoft.EntityFrameworkCore.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Microsoft.EntityFrameworkCore.Relational.pdb",
+ "code_id": "______________",
+ "code_file": ".../Microsoft.EntityFrameworkCore.Relational.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../System.Private.CoreLib.pdb",
+ "code_id": "______________",
+ "code_file": ".../System.Private.CoreLib.dll",
+ }
+ ],
Level: error,
Request: {},
Contexts: {},
diff --git a/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj b/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj
index 26d0c54e6a..621c73eebf 100644
--- a/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj
+++ b/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj
@@ -47,14 +47,14 @@
-
+
+
diff --git a/test/Sentry.Maui.Device.TestApp/Startup.cs b/test/Sentry.Maui.Device.TestApp/Startup.cs
index 5f20f7cfd3..d9ccb6f478 100644
--- a/test/Sentry.Maui.Device.TestApp/Startup.cs
+++ b/test/Sentry.Maui.Device.TestApp/Startup.cs
@@ -14,7 +14,11 @@ public static MauiApp CreateMauiApp()
{
typeof(Sentry.Tests.SentrySdkTests).Assembly,
typeof(Sentry.Extensions.Logging.Tests.LogLevelExtensionsTests).Assembly,
- typeof(Sentry.Maui.Tests.SentryMauiOptionsTests).Assembly
+ typeof(Sentry.Maui.Tests.SentryMauiOptionsTests).Assembly,
+
+#if ANDROID
+ typeof(Sentry.Android.Tests.AndroidAssemblyReaderTests).Assembly,
+#endif
}
})
.UseHeadlessRunner(new HeadlessRunnerOptions
diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.Core3_1.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Core3_1.verified.txt
new file mode 100644
index 0000000000..f88f63cdf7
--- /dev/null
+++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Core3_1.verified.txt
@@ -0,0 +1,84 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message
+ },
+ Message: {
+ Message: message = {arg},
+ Formatted: message = "arg value"
+ },
+ Logger: Sentry.NLog.Tests.IntegrationTests,
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 47,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.NLog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {}
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.NLog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.NLog.Tests.dll",
+ }
+ ],
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Id: myId,
+ Username: ,
+ Email: ,
+ IpAddress: ,
+ Segment: ,
+ Other: {
+ mood: joyous
+ }
+ },
+ Environment: production,
+ Extra: {
+ arg: arg value
+ },
+ Tags: {
+ logger: Sentry.NLog.Tests.IntegrationTests
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet6_0.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet6_0.verified.txt
new file mode 100644
index 0000000000..f88f63cdf7
--- /dev/null
+++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet6_0.verified.txt
@@ -0,0 +1,84 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message
+ },
+ Message: {
+ Message: message = {arg},
+ Formatted: message = "arg value"
+ },
+ Logger: Sentry.NLog.Tests.IntegrationTests,
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 47,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.NLog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {}
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.NLog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.NLog.Tests.dll",
+ }
+ ],
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Id: myId,
+ Username: ,
+ Email: ,
+ IpAddress: ,
+ Segment: ,
+ Other: {
+ mood: joyous
+ }
+ },
+ Environment: production,
+ Extra: {
+ arg: arg value
+ },
+ Tags: {
+ logger: Sentry.NLog.Tests.IntegrationTests
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet7_0.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet7_0.verified.txt
new file mode 100644
index 0000000000..f88f63cdf7
--- /dev/null
+++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet7_0.verified.txt
@@ -0,0 +1,84 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message
+ },
+ Message: {
+ Message: message = {arg},
+ Formatted: message = "arg value"
+ },
+ Logger: Sentry.NLog.Tests.IntegrationTests,
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 47,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.NLog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {}
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.NLog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.NLog.Tests.dll",
+ }
+ ],
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Id: myId,
+ Username: ,
+ Email: ,
+ IpAddress: ,
+ Segment: ,
+ Other: {
+ mood: joyous
+ }
+ },
+ Environment: production,
+ Extra: {
+ arg: arg value
+ },
+ Tags: {
+ logger: Sentry.NLog.Tests.IntegrationTests
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Mono4_0.verified.txt
similarity index 62%
rename from test/Sentry.NLog.Tests/IntegrationTests.Simple.verified.txt
rename to test/Sentry.NLog.Tests/IntegrationTests.Simple.Mono4_0.verified.txt
index 1d5ecf5a7a..9f5828ca87 100644
--- a/test/Sentry.NLog.Tests/IntegrationTests.Simple.verified.txt
+++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Mono4_0.verified.txt
@@ -1,4 +1,4 @@
-[
+[
{
Header: {
event_id: Guid_1,
@@ -30,15 +30,29 @@
Value: Exception message,
Stacktrace: {
Frames: [
- {
- Function: Task IntegrationTests.Simple(),
- InApp: false
+ {,
+ "function": "Task IntegrationTests.Simple()",
+ "in_app": false,
+ "package": "Sentry.NLog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
}
]
},
Mechanism: {}
}
],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.NLog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.NLog.Tests.dll",
+ }
+ ],
Level: error,
Request: {},
Contexts: {},
diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt
new file mode 100644
index 0000000000..f88f63cdf7
--- /dev/null
+++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt
@@ -0,0 +1,84 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message
+ },
+ Message: {
+ Message: message = {arg},
+ Formatted: message = "arg value"
+ },
+ Logger: Sentry.NLog.Tests.IntegrationTests,
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 47,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.NLog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {}
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.NLog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.NLog.Tests.dll",
+ }
+ ],
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Id: myId,
+ Username: ,
+ Email: ,
+ IpAddress: ,
+ Segment: ,
+ Other: {
+ mood: joyous
+ }
+ },
+ Environment: production,
+ Extra: {
+ arg: arg value
+ },
+ Tags: {
+ logger: Sentry.NLog.Tests.IntegrationTests
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.NLog.Tests/IntegrationTests.verify.cs b/test/Sentry.NLog.Tests/IntegrationTests.verify.cs
index ee16093db5..1fb57eb86f 100644
--- a/test/Sentry.NLog.Tests/IntegrationTests.verify.cs
+++ b/test/Sentry.NLog.Tests/IntegrationTests.verify.cs
@@ -55,6 +55,7 @@ public Task Simple()
LogManager.Flush();
return Verify(transport.Envelopes)
+ .UniqueForRuntimeAndVersion()
.IgnoreStandardSentryMembers();
}
diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Core3_1.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Core3_1.verified.txt
new file mode 100644
index 0000000000..1ed4c52299
--- /dev/null
+++ b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Core3_1.verified.txt
@@ -0,0 +1,224 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Debug message stored as breadcrumb.,
+ Formatted: [42] Debug message stored as breadcrumb.
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Message with a different MyTaskId,
+ Formatted: [65] Message with a different MyTaskId
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 65
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Some event that includes the previous breadcrumbs,
+ Formatted: [42] Some event that includes the previous breadcrumbs
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_4,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message,
+ Data: {
+ details: Do work always throws.
+ }
+ },
+ Message: {
+ Message: Error: with exception,
+ Formatted: [42] Error: with exception
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 46,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.Serilog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {},
+ Data: {
+ details: Do work always throws.
+ }
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Serilog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Serilog.Tests.dll",
+ }
+ ],
+ Level: fatal,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ },
+ {
+ Message: [42] Some event that includes the previous breadcrumbs,
+ Level: error
+ }
+ ],
+ Extra: {
+ Exception[0][details]: Do work always throws.,
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet6_0.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet6_0.verified.txt
new file mode 100644
index 0000000000..1ed4c52299
--- /dev/null
+++ b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet6_0.verified.txt
@@ -0,0 +1,224 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Debug message stored as breadcrumb.,
+ Formatted: [42] Debug message stored as breadcrumb.
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Message with a different MyTaskId,
+ Formatted: [65] Message with a different MyTaskId
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 65
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Some event that includes the previous breadcrumbs,
+ Formatted: [42] Some event that includes the previous breadcrumbs
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_4,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message,
+ Data: {
+ details: Do work always throws.
+ }
+ },
+ Message: {
+ Message: Error: with exception,
+ Formatted: [42] Error: with exception
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 46,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.Serilog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {},
+ Data: {
+ details: Do work always throws.
+ }
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Serilog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Serilog.Tests.dll",
+ }
+ ],
+ Level: fatal,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ },
+ {
+ Message: [42] Some event that includes the previous breadcrumbs,
+ Level: error
+ }
+ ],
+ Extra: {
+ Exception[0][details]: Do work always throws.,
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet7_0.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet7_0.verified.txt
new file mode 100644
index 0000000000..1ed4c52299
--- /dev/null
+++ b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.DotNet7_0.verified.txt
@@ -0,0 +1,224 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Debug message stored as breadcrumb.,
+ Formatted: [42] Debug message stored as breadcrumb.
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Message with a different MyTaskId,
+ Formatted: [65] Message with a different MyTaskId
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 65
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Some event that includes the previous breadcrumbs,
+ Formatted: [42] Some event that includes the previous breadcrumbs
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_4,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message,
+ Data: {
+ details: Do work always throws.
+ }
+ },
+ Message: {
+ Message: Error: with exception,
+ Formatted: [42] Error: with exception
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 46,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.Serilog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {},
+ Data: {
+ details: Do work always throws.
+ }
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Serilog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Serilog.Tests.dll",
+ }
+ ],
+ Level: fatal,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ },
+ {
+ Message: [42] Some event that includes the previous breadcrumbs,
+ Level: error
+ }
+ ],
+ Extra: {
+ Exception[0][details]: Do work always throws.,
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Mono4_0.verified.txt
similarity index 85%
rename from test/Sentry.Serilog.Tests/IntegrationTests.Simple.verified.txt
rename to test/Sentry.Serilog.Tests/IntegrationTests.Simple.Mono4_0.verified.txt
index f7c0fc4cf6..f6d68a98b1 100644
--- a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.verified.txt
+++ b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Mono4_0.verified.txt
@@ -1,4 +1,4 @@
-[
+[
{
Header: {
event_id: Guid_1,
@@ -160,9 +160,13 @@
Value: Exception message,
Stacktrace: {
Frames: [
- {
- Function: Task IntegrationTests.Simple(),
- InApp: true
+ {,
+ "function": "Task IntegrationTests.Simple()",
+ "in_app": false,
+ "package": "Sentry.Serilog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
}
]
},
@@ -172,6 +176,16 @@
}
}
],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Serilog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Serilog.Tests.dll",
+ }
+ ],
Level: fatal,
Request: {},
Contexts: {},
diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Net4_8.verified.txt b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Net4_8.verified.txt
new file mode 100644
index 0000000000..1ed4c52299
--- /dev/null
+++ b/test/Sentry.Serilog.Tests/IntegrationTests.Simple.Net4_8.verified.txt
@@ -0,0 +1,224 @@
+[
+ {
+ Header: {
+ event_id: Guid_1,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Debug message stored as breadcrumb.,
+ Formatted: [42] Debug message stored as breadcrumb.
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Message with a different MyTaskId,
+ Formatted: [65] Message with a different MyTaskId
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: debug,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 65
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Message: {
+ Message: Some event that includes the previous breadcrumbs,
+ Formatted: [42] Some event that includes the previous breadcrumbs
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ Level: error,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ }
+ ],
+ Extra: {
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_4,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Exception: {
+ $type: Exception,
+ Type: Exception,
+ Message: Exception message,
+ Data: {
+ details: Do work always throws.
+ }
+ },
+ Message: {
+ Message: Error: with exception,
+ Formatted: [42] Error: with exception
+ },
+ Platform: csharp,
+ ServerName: TheMachineName,
+ SentryExceptions: [
+ {
+ Type: System.Exception,
+ Value: Exception message,
+ Stacktrace: {
+ Frames: [
+ {,
+ "filename": ".../IntegrationTests.verify.cs",
+ "function": "Task IntegrationTests.Simple()",
+ "lineno": 46,
+ "colno": 17,
+ "in_app": false,
+ "package": "Sentry.Serilog.Tests, Version=SCRUBBED, Culture=SCRUBBED, PublicKeyToken=SCRUBBED",
+ "instruction_addr": "____",
+ "addr_mode": "rel:0",
+ "function_id": "____",
+ }
+ ]
+ },
+ Mechanism: {},
+ Data: {
+ details: Do work always throws.
+ }
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Serilog.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Serilog.Tests.dll",
+ }
+ ],
+ Level: fatal,
+ Request: {},
+ Contexts: {},
+ User: {
+ Username: TheUserName,
+ IpAddress: {{auto}}
+ },
+ Environment: production,
+ Breadcrumbs: [
+ {
+ Message: [42] Debug message stored as breadcrumb.,
+ Level: debug
+ },
+ {
+ Message: [65] Message with a different MyTaskId,
+ Level: debug
+ },
+ {
+ Message: [42] Some event that includes the previous breadcrumbs,
+ Level: error
+ }
+ ],
+ Extra: {
+ Exception[0][details]: Do work always throws.,
+ inventory: { SmallPotion = 3, BigPotion = 0, CheeseWheels = 512 },
+ MyTaskId: 42
+ }
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs b/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs
index d86e3606a4..7f9c8a45c6 100644
--- a/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs
+++ b/test/Sentry.Serilog.Tests/IntegrationTests.verify.cs
@@ -1,4 +1,7 @@
-[UsesVerify]
+namespace Sentry.Serilog.Tests;
+
+[UsesVerify]
+[Collection(nameof(SentrySdkCollection))]
public class IntegrationTests
{
[Fact]
@@ -52,6 +55,7 @@ public Task Simple()
Log.CloseAndFlush();
return Verify(transport.Envelopes)
+ .UniqueForRuntimeAndVersion()
.IgnoreStandardSentryMembers();
}
diff --git a/test/Sentry.Testing/VerifyExtensions.cs b/test/Sentry.Testing/VerifyExtensions.cs
index 2abb8fc55f..a87a313db4 100644
--- a/test/Sentry.Testing/VerifyExtensions.cs
+++ b/test/Sentry.Testing/VerifyExtensions.cs
@@ -1,4 +1,7 @@
#if !__MOBILE__
+
+namespace Sentry.Testing;
+
public static class VerifyExtensions
{
public static SettingsTask IgnoreStandardSentryMembers(this SettingsTask settings)
@@ -10,6 +13,8 @@ public static SettingsTask IgnoreStandardSentryMembers(this SettingsTask setting
{
_.Converters.Add(new SpansConverter());
_.Converters.Add(new ContextsConverter());
+ _.Converters.Add(new DebugImageConverter());
+ _.Converters.Add(new StackFrameConverter());
})
.IgnoreMembers("version", "elapsed")
.IgnoreMembersWithType()
@@ -40,7 +45,7 @@ public static SettingsTask IgnoreStandardSentryMembers(this SettingsTask setting
.IgnoreStackTrace();
}
- class SpansConverter : WriteOnlyJsonConverter>
+ private class SpansConverter : WriteOnlyJsonConverter>
{
public override void Write(VerifyJsonWriter writer, IReadOnlyCollection spans)
{
@@ -59,7 +64,7 @@ public override void Write(VerifyJsonWriter writer, IReadOnlyCollection sp
}
}
- class ContextsConverter : WriteOnlyJsonConverter
+ private class ContextsConverter : WriteOnlyJsonConverter
{
public override void Write(VerifyJsonWriter writer, Contexts contexts)
{
@@ -79,5 +84,82 @@ public override void Write(VerifyJsonWriter writer, Contexts contexts)
writer.Serialize(items);
}
}
+
+ private class DebugImageConverter : WriteOnlyJsonConverter
+ {
+ public override void Write(VerifyJsonWriter writer, DebugImage obj)
+ {
+ obj.DebugId = ScrubAlphaNum(obj.DebugId);
+ obj.DebugChecksum = ScrubAlphaNum(obj.DebugChecksum);
+ obj.DebugFile = ScrubPath(obj.DebugFile);
+ obj.CodeFile = ScrubPath(obj.CodeFile);
+ obj.CodeId = ScrubAlphaNum(obj.CodeId);
+ writer.WriteJson(obj);
+ }
+ }
+
+ private class StackFrameConverter : WriteOnlyJsonConverter
+ {
+ public override void Write(VerifyJsonWriter writer, SentryStackFrame obj)
+ {
+ obj.FileName = ScrubPath(obj.FileName);
+ obj.FunctionId = ScrubAlphaNum(obj.FunctionId);
+ obj.InstructionAddress = ScrubAlphaNum(obj.InstructionAddress);
+ obj.Package = obj.Package.Replace(new Regex("=[^,]+"), "=SCRUBBED");
+ writer.WriteJson(obj);
+ }
+ }
+
+ // Extension so we can use `nullableString?.Replace()`.
+ private static string Replace(this string str, Regex regex, string replacement) => regex.Replace(str, replacement);
+
+ private static string ScrubAlphaNum(string str) => str?.Replace(new Regex("[a-zA-Z0-9]"), "_");
+
+ private static string ScrubPath(string str) => str?.Replace(new Regex(@"^.*[/\\]"), ".../");
+
+ private static void WriteJson(this VerifyJsonWriter verifyWriter, IJsonSerializable @object)
+ {
+ using var stream = new MemoryStream();
+ using var jsonWriter = new Utf8JsonWriter(stream, new() { Indented = true });
+ @object.WriteTo(jsonWriter, null);
+ jsonWriter.Flush();
+ var str = Encoding.UTF8.GetString(stream.ToArray());
+
+ // Note: this is not perfect because we don't respect indentation. of the surrounding objects.
+ // Unfortunately, there doesn't seem to be the way to get current serialization depth.
+ // There's `Indentation` and `IndentChar` but those are only relevant in combination with the current depth
+ // should be available as `Top`, which is protected internal...
+ // verifyWriter.WriteValue(str);
+
+ // Therefore, we have the following best-effort approach of splitting lines and writing individually
+ // which makes the JsonWriter add proper indentation.
+ var lines = str.Replace("\r", "").Split('\n');
+ var depth = 0;
+ for (var i = 0; i < lines.Length; i++)
+ {
+ var line = lines[i].Trim();
+
+ // Note: we can't use WriteStartObject/EndObject because following WriteRawValueWithScrubbers() fails with:
+ // Argon.JsonWriterException : Token Undefined in state ObjectStart would result in an invalid JSON object. Path '[0].Items[0].Payload.Source.objs[0]'.
+ switch (line)
+ {
+ case "{":
+ case "[":
+ verifyWriter.WriteRawValue(line);
+ depth++;
+ break;
+ case "}":
+ case "]":
+ verifyWriter.WriteRawValue(line);
+ depth--;
+ break;
+ default:
+ var indent = new string(verifyWriter.IndentChar, verifyWriter.Indentation * depth);
+ // FYI: this always adds comma on the previous line before adding the given value. Can't help it.
+ verifyWriter.WriteRawValueWithScrubbers(indent + line.TrimEnd(','));
+ break;
+ }
+ }
+ }
}
#endif
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt
index ca82948b74..765428a6d4 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt
@@ -81,6 +81,7 @@ namespace Sentry
public DebugImage() { }
public string? CodeFile { get; set; }
public string? CodeId { get; set; }
+ public string? DebugChecksum { get; set; }
public string? DebugFile { get; set; }
public string? DebugId { get; set; }
public string? ImageAddress { get; set; }
@@ -676,6 +677,7 @@ namespace Sentry
public string? FileName { get; set; }
public System.Collections.Generic.IList FramesOmitted { get; }
public string? Function { get; set; }
+ public string? FunctionId { get; set; }
public long ImageAddress { get; set; }
public bool? InApp { get; set; }
public string? InstructionAddress { get; set; }
@@ -692,7 +694,7 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryStackFrame FromJson(System.Text.Json.JsonElement json) { }
}
- public sealed class SentryStackTrace : Sentry.IJsonSerializable
+ public class SentryStackTrace : Sentry.IJsonSerializable
{
public SentryStackTrace() { }
public System.Collections.Generic.IList Frames { get; set; }
@@ -1202,14 +1204,10 @@ namespace Sentry.Extensibility
public void Process(System.Exception? exception, Sentry.SentryEvent sentryEvent) { }
protected abstract void ProcessException(TException exception, Sentry.SentryEvent sentryEvent);
}
- public class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
+ public sealed class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
{
public SentryStackTraceFactory(Sentry.SentryOptions options) { }
- public virtual Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
- protected virtual Sentry.SentryStackFrame CreateFrame(System.Diagnostics.StackFrame stackFrame, bool isCurrentStackTrace) { }
- protected virtual System.Diagnostics.StackTrace CreateStackTrace(System.Exception? exception) { }
- protected virtual System.Reflection.MethodBase? GetMethod(System.Diagnostics.StackFrame stackFrame) { }
- protected Sentry.SentryStackFrame InternalCreateFrame(System.Diagnostics.StackFrame stackFrame, bool demangle) { }
+ public Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
}
}
namespace Sentry.Http
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet4_8.verified.txt
index 5341535b8a..45c5a92db6 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet4_8.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet4_8.verified.txt
@@ -81,6 +81,7 @@ namespace Sentry
public DebugImage() { }
public string? CodeFile { get; set; }
public string? CodeId { get; set; }
+ public string? DebugChecksum { get; set; }
public string? DebugFile { get; set; }
public string? DebugId { get; set; }
public string? ImageAddress { get; set; }
@@ -675,6 +676,7 @@ namespace Sentry
public string? FileName { get; set; }
public System.Collections.Generic.IList FramesOmitted { get; }
public string? Function { get; set; }
+ public string? FunctionId { get; set; }
public long ImageAddress { get; set; }
public bool? InApp { get; set; }
public string? InstructionAddress { get; set; }
@@ -691,7 +693,7 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryStackFrame FromJson(System.Text.Json.JsonElement json) { }
}
- public sealed class SentryStackTrace : Sentry.IJsonSerializable
+ public class SentryStackTrace : Sentry.IJsonSerializable
{
public SentryStackTrace() { }
public System.Collections.Generic.IList Frames { get; set; }
@@ -1201,14 +1203,10 @@ namespace Sentry.Extensibility
public void Process(System.Exception? exception, Sentry.SentryEvent sentryEvent) { }
protected abstract void ProcessException(TException exception, Sentry.SentryEvent sentryEvent);
}
- public class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
+ public sealed class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
{
public SentryStackTraceFactory(Sentry.SentryOptions options) { }
- public virtual Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
- protected virtual Sentry.SentryStackFrame CreateFrame(System.Diagnostics.StackFrame stackFrame, bool isCurrentStackTrace) { }
- protected virtual System.Diagnostics.StackTrace CreateStackTrace(System.Exception? exception) { }
- protected virtual System.Reflection.MethodBase? GetMethod(System.Diagnostics.StackFrame stackFrame) { }
- protected Sentry.SentryStackFrame InternalCreateFrame(System.Diagnostics.StackFrame stackFrame, bool demangle) { }
+ public Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
}
}
namespace Sentry.Http
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
index ca82948b74..765428a6d4 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
@@ -81,6 +81,7 @@ namespace Sentry
public DebugImage() { }
public string? CodeFile { get; set; }
public string? CodeId { get; set; }
+ public string? DebugChecksum { get; set; }
public string? DebugFile { get; set; }
public string? DebugId { get; set; }
public string? ImageAddress { get; set; }
@@ -676,6 +677,7 @@ namespace Sentry
public string? FileName { get; set; }
public System.Collections.Generic.IList FramesOmitted { get; }
public string? Function { get; set; }
+ public string? FunctionId { get; set; }
public long ImageAddress { get; set; }
public bool? InApp { get; set; }
public string? InstructionAddress { get; set; }
@@ -692,7 +694,7 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryStackFrame FromJson(System.Text.Json.JsonElement json) { }
}
- public sealed class SentryStackTrace : Sentry.IJsonSerializable
+ public class SentryStackTrace : Sentry.IJsonSerializable
{
public SentryStackTrace() { }
public System.Collections.Generic.IList Frames { get; set; }
@@ -1202,14 +1204,10 @@ namespace Sentry.Extensibility
public void Process(System.Exception? exception, Sentry.SentryEvent sentryEvent) { }
protected abstract void ProcessException(TException exception, Sentry.SentryEvent sentryEvent);
}
- public class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
+ public sealed class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
{
public SentryStackTraceFactory(Sentry.SentryOptions options) { }
- public virtual Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
- protected virtual Sentry.SentryStackFrame CreateFrame(System.Diagnostics.StackFrame stackFrame, bool isCurrentStackTrace) { }
- protected virtual System.Diagnostics.StackTrace CreateStackTrace(System.Exception? exception) { }
- protected virtual System.Reflection.MethodBase? GetMethod(System.Diagnostics.StackFrame stackFrame) { }
- protected Sentry.SentryStackFrame InternalCreateFrame(System.Diagnostics.StackFrame stackFrame, bool demangle) { }
+ public Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
}
}
namespace Sentry.Http
diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
index ca82948b74..765428a6d4 100644
--- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
+++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
@@ -81,6 +81,7 @@ namespace Sentry
public DebugImage() { }
public string? CodeFile { get; set; }
public string? CodeId { get; set; }
+ public string? DebugChecksum { get; set; }
public string? DebugFile { get; set; }
public string? DebugId { get; set; }
public string? ImageAddress { get; set; }
@@ -676,6 +677,7 @@ namespace Sentry
public string? FileName { get; set; }
public System.Collections.Generic.IList FramesOmitted { get; }
public string? Function { get; set; }
+ public string? FunctionId { get; set; }
public long ImageAddress { get; set; }
public bool? InApp { get; set; }
public string? InstructionAddress { get; set; }
@@ -692,7 +694,7 @@ namespace Sentry
public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { }
public static Sentry.SentryStackFrame FromJson(System.Text.Json.JsonElement json) { }
}
- public sealed class SentryStackTrace : Sentry.IJsonSerializable
+ public class SentryStackTrace : Sentry.IJsonSerializable
{
public SentryStackTrace() { }
public System.Collections.Generic.IList Frames { get; set; }
@@ -1202,14 +1204,10 @@ namespace Sentry.Extensibility
public void Process(System.Exception? exception, Sentry.SentryEvent sentryEvent) { }
protected abstract void ProcessException(TException exception, Sentry.SentryEvent sentryEvent);
}
- public class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
+ public sealed class SentryStackTraceFactory : Sentry.Extensibility.ISentryStackTraceFactory
{
public SentryStackTraceFactory(Sentry.SentryOptions options) { }
- public virtual Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
- protected virtual Sentry.SentryStackFrame CreateFrame(System.Diagnostics.StackFrame stackFrame, bool isCurrentStackTrace) { }
- protected virtual System.Diagnostics.StackTrace CreateStackTrace(System.Exception? exception) { }
- protected virtual System.Reflection.MethodBase? GetMethod(System.Diagnostics.StackFrame stackFrame) { }
- protected Sentry.SentryStackFrame InternalCreateFrame(System.Diagnostics.StackFrame stackFrame, bool demangle) { }
+ public Sentry.SentryStackTrace? Create(System.Exception? exception = null) { }
}
}
namespace Sentry.Http
diff --git a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt
new file mode 100644
index 0000000000..2694e50acf
--- /dev/null
+++ b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt
@@ -0,0 +1,162 @@
+[
+ {
+ Header: {
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ IsInitial: true
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Mechanism: {
+ Handled: false
+ }
+ }
+ ],
+ SentryThreads: [
+ {
+ Crashed: false,
+ Current: true
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_file": "System.Private.CoreLib.pdb",
+ "code_id": "______________",
+ "code_file": ".../System.Private.CoreLib.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Tests.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.execution.dotnet.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.execution.dotnet.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.core.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.core.dll",
+ }
+ ],
+ Level: error,
+ TransactionName: my transaction,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation:
+ }
+ },
+ User: {},
+ Environment: production
+ }
+ }
+ },
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ ErrorCount: 1,
+ IsInitial: false,
+ SequenceNumber: 1,
+ EndStatus: Crashed
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ },
+ trace: {
+ environment: production,
+ public_key: d4d82fc1c2c4032a83f3a29aa3a3aff,
+ release: release,
+ sample_rate: 1,
+ trace_id: Guid_4,
+ transaction: my transaction
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: transaction
+ },
+ Payload: {
+ Source: {
+ Name: my transaction,
+ Platform: csharp,
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true,
+ SampleRate: 1.0,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true
+ }
+ },
+ User: {},
+ Environment: production,
+ IsFinished: true
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt
new file mode 100644
index 0000000000..bff8475af9
--- /dev/null
+++ b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt
@@ -0,0 +1,162 @@
+[
+ {
+ Header: {
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ IsInitial: true
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Mechanism: {
+ Handled: false
+ }
+ }
+ ],
+ SentryThreads: [
+ {
+ Crashed: false,
+ Current: true
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_file": ".../System.Private.CoreLib.pdb",
+ "code_id": "______________",
+ "code_file": ".../System.Private.CoreLib.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Tests.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.execution.dotnet.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.execution.dotnet.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.core.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.core.dll",
+ }
+ ],
+ Level: error,
+ TransactionName: my transaction,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation:
+ }
+ },
+ User: {},
+ Environment: production
+ }
+ }
+ },
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ ErrorCount: 1,
+ IsInitial: false,
+ SequenceNumber: 1,
+ EndStatus: Crashed
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ },
+ trace: {
+ environment: production,
+ public_key: d4d82fc1c2c4032a83f3a29aa3a3aff,
+ release: release,
+ sample_rate: 1,
+ trace_id: Guid_4,
+ transaction: my transaction
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: transaction
+ },
+ Payload: {
+ Source: {
+ Name: my transaction,
+ Platform: csharp,
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true,
+ SampleRate: 1.0,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true
+ }
+ },
+ User: {},
+ Environment: production,
+ IsFinished: true
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt
new file mode 100644
index 0000000000..0bdaf10893
--- /dev/null
+++ b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt
@@ -0,0 +1,163 @@
+[
+ {
+ Header: {
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ IsInitial: true
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Mechanism: {
+ Handled: false
+ }
+ }
+ ],
+ SentryThreads: [
+ {
+ Crashed: false,
+ Current: true
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../System.Private.CoreLib.pdb",
+ "code_id": "______________",
+ "code_file": ".../System.Private.CoreLib.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Tests.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.execution.dotnet.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.execution.dotnet.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.core.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.core.dll",
+ }
+ ],
+ Level: error,
+ TransactionName: my transaction,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation:
+ }
+ },
+ User: {},
+ Environment: production
+ }
+ }
+ },
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ ErrorCount: 1,
+ IsInitial: false,
+ SequenceNumber: 1,
+ EndStatus: Crashed
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ },
+ trace: {
+ environment: production,
+ public_key: d4d82fc1c2c4032a83f3a29aa3a3aff,
+ release: release,
+ sample_rate: 1,
+ trace_id: Guid_4,
+ transaction: my transaction
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: transaction
+ },
+ Payload: {
+ Source: {
+ Name: my transaction,
+ Platform: csharp,
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true,
+ SampleRate: 1.0,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true
+ }
+ },
+ User: {},
+ Environment: production,
+ IsFinished: true
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt
new file mode 100644
index 0000000000..e35fbf62d2
--- /dev/null
+++ b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt
@@ -0,0 +1,162 @@
+[
+ {
+ Header: {
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ IsInitial: true
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_2,
+ sdk: {
+ name: sentry.dotnet
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: event
+ },
+ Payload: {
+ Source: {
+ Platform: csharp,
+ SentryExceptions: [
+ {
+ Mechanism: {
+ Handled: false
+ }
+ }
+ ],
+ SentryThreads: [
+ {
+ Crashed: false,
+ Current: true
+ }
+ ],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_file": ".../mscorlib.pdb",
+ "code_id": "______________",
+ "code_file": ".../mscorlib.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Tests.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.execution.desktop.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.execution.desktop.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.core.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.core.dll",
+ }
+ ],
+ Level: error,
+ TransactionName: my transaction,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation:
+ }
+ },
+ User: {},
+ Environment: production
+ }
+ }
+ },
+ {
+ Header: {
+ type: session
+ },
+ Payload: {
+ Source: {
+ DistinctId: Guid_1,
+ Release: release,
+ Environment: production,
+ ErrorCount: 1,
+ IsInitial: false,
+ SequenceNumber: 1,
+ EndStatus: Crashed
+ }
+ }
+ }
+ ]
+ },
+ {
+ Header: {
+ event_id: Guid_3,
+ sdk: {
+ name: sentry.dotnet
+ },
+ trace: {
+ environment: production,
+ public_key: d4d82fc1c2c4032a83f3a29aa3a3aff,
+ release: release,
+ sample_rate: 1,
+ trace_id: Guid_4,
+ transaction: my transaction
+ }
+ },
+ Items: [
+ {
+ Header: {
+ type: transaction
+ },
+ Payload: {
+ Source: {
+ Name: my transaction,
+ Platform: csharp,
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true,
+ SampleRate: 1.0,
+ Request: {},
+ Contexts: {
+ trace: {
+ Operation: my operation,
+ Description: ,
+ Status: Aborted,
+ IsSampled: true
+ }
+ },
+ User: {},
+ Environment: production,
+ IsFinished: true
+ }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.verified.txt b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt
similarity index 66%
rename from test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.verified.txt
rename to test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt
index 3d32bf47cb..ab2a8ee5de 100644
--- a/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.verified.txt
+++ b/test/Sentry.Tests/HubTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt
@@ -49,6 +49,32 @@
Current: true
}
],
+ DebugImages: [
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": ".../Sentry.Tests.pdb",
+ "code_id": "_____________",
+ "code_file": ".../Sentry.Tests.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.execution.desktop.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.execution.desktop.dll",
+ },
+ {,
+ "type": "pe_dotnet",
+ "debug_id": "________-____-____-____-____________-________",
+ "debug_checksum": "______:________________________________________________________________",
+ "debug_file": "xunit.core.pdb",
+ "code_id": "_____________",
+ "code_file": ".../xunit.core.dll",
+ }
+ ],
Level: error,
TransactionName: my transaction,
Request: {},
diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs
index c372b7d410..4170323a55 100644
--- a/test/Sentry.Tests/HubTests.cs
+++ b/test/Sentry.Tests/HubTests.cs
@@ -300,7 +300,7 @@ async Task VerifyAsync(HttpRequestMessage message)
var expectedContextKey = Guid.NewGuid().ToString();
var evt = new SentryEvent
{
- Contexts = {[expectedContextKey] = new EvilContext()},
+ Contexts = { [expectedContextKey] = new EvilContext() },
Message = new()
{
Formatted = expectedMessage
diff --git a/test/Sentry.Tests/HubTests.verify.cs b/test/Sentry.Tests/HubTests.verify.cs
index 28d7868cc0..2aaa4df7c9 100644
--- a/test/Sentry.Tests/HubTests.verify.cs
+++ b/test/Sentry.Tests/HubTests.verify.cs
@@ -1,5 +1,3 @@
-using Sentry.Testing;
-
namespace Sentry.Tests;
[UsesVerify]
@@ -40,8 +38,16 @@ public async Task CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEn
});
await Verify(worker.Envelopes)
+ .UniqueForRuntimeAndVersion()
.IgnoreStandardSentryMembers()
.IgnoreMember("Stacktrace")
- .IgnoreMember(_ => _.Name);
+ .IgnoreMember(_ => _.Name)
+ .IgnoreInstance(_ =>
+ _.DebugFile != null && (
+ _.DebugFile.Contains("Xunit.SkippableFact") ||
+ _.DebugFile.Contains("xunit.runner") ||
+ _.DebugFile.Contains("Microsoft.TestPlatform")
+ )
+ );
}
}
diff --git a/test/Sentry.Tests/Internals/DebugStackTraceTests.cs b/test/Sentry.Tests/Internals/DebugStackTraceTests.cs
new file mode 100644
index 0000000000..f61bda8a36
--- /dev/null
+++ b/test/Sentry.Tests/Internals/DebugStackTraceTests.cs
@@ -0,0 +1,174 @@
+// ReSharper disable once CheckNamespace
+// Stack trace filters out Sentry frames by namespace
+namespace Other.Tests.Internals;
+
+public class DebugStackTraceTests
+{
+ private class Fixture
+ {
+ public SentryOptions SentryOptions { get; set; } = new();
+ public InjectableDebugStackTrace GetSut() => new(SentryOptions);
+ }
+
+ private readonly Fixture _fixture = new();
+ private static readonly string ThisNamespace = typeof(SentryStackTraceFactoryTests).Namespace;
+
+ [Fact]
+ public void CreateSentryStackFrame_AppNamespace_InAppFrame()
+ {
+ var frame = new StackFrame();
+ var sut = _fixture.GetSut();
+
+ var actual = sut.CreateFrame(frame);
+
+ Assert.True(actual.InApp);
+ }
+
+ [Fact]
+ public void CreateSentryStackFrame_AppNamespaceExcluded_NotInAppFrame()
+ {
+ _fixture.SentryOptions.AddInAppExclude(ThisNamespace);
+ var sut = _fixture.GetSut();
+ var frame = new StackFrame();
+
+ var actual = sut.CreateFrame(frame);
+
+ Assert.False(actual.InApp);
+ }
+
+ [Fact]
+ public void CreateSentryStackFrame_NamespaceIncludedAndExcluded_IncludesTakesPrecedence()
+ {
+ _fixture.SentryOptions.AddInAppExclude(ThisNamespace);
+ _fixture.SentryOptions.AddInAppInclude(ThisNamespace);
+ var sut = _fixture.GetSut();
+ var frame = new StackFrame();
+
+ var actual = sut.CreateFrame(frame);
+
+ Assert.True(actual.InApp);
+ }
+
+ // https://github.com/getsentry/sentry-dotnet/issues/64
+ [Fact]
+ public void DemangleAnonymousFunction_NullFunction_ContinuesNull()
+ {
+ var stackFrame = new SentryStackFrame
+ {
+ Function = null
+ };
+
+ DebugStackTrace.DemangleAnonymousFunction(stackFrame);
+ Assert.Null(stackFrame.Function);
+ }
+
+ [Fact]
+ public void DemangleAsyncFunctionName_NullModule_ContinuesNull()
+ {
+ var stackFrame = new SentryStackFrame
+ {
+ Module = null
+ };
+
+ DebugStackTrace.DemangleAnonymousFunction(stackFrame);
+ Assert.Null(stackFrame.Module);
+ }
+
+ [Fact]
+ public void MergeDebugImages_Empty()
+ {
+ var sut = _fixture.GetSut();
+ var e = new SentryEvent();
+ sut.MergeDebugImagesInto(e);
+ Assert.Null(e.DebugImages);
+ }
+
+ [Fact]
+ public void MergeDebugImages_ThrowsOnSecondRun()
+ {
+ var sut = _fixture.GetSut();
+ sut.Inject(1);
+
+ var e = new SentryEvent();
+ sut.MergeDebugImagesInto(e);
+ Assert.NotNull(e.DebugImages);
+ Assert.Throws(() => sut.MergeDebugImagesInto(e));
+ }
+
+ [Fact]
+ public void MergeDebugImages()
+ {
+ var sut1 = _fixture.GetSut();
+ sut1.Inject(1111);
+ sut1.Inject(2222);
+ sut1.Frames.Add(new SentryStackFrame() { Function = "1", AddressMode = "rel:0" });
+ sut1.Frames.Add(new SentryStackFrame() { Function = "2", AddressMode = "rel:1" });
+ sut1.Frames.Add(new SentryStackFrame() { Function = "3", AddressMode = "rel:0" });
+
+ var e = new SentryEvent();
+ sut1.MergeDebugImagesInto(e);
+ Assert.NotNull(e.DebugImages);
+ Assert.Equal(2, e.DebugImages.Count);
+ Assert.Equal("1111.dll", e.DebugImages[0].CodeFile);
+ Assert.Equal("2222.dll", e.DebugImages[1].CodeFile);
+
+ // Stack trace isn't changed - there were no DebugImage relocations yet.
+ CheckStackTraceIsUnchanged(sut1);
+
+ var sut2 = _fixture.GetSut();
+ sut2.Inject(3333);
+ sut2.Inject(1111);
+ sut2.Inject(4444);
+ sut2.Frames.Add(new SentryStackFrame() { Function = "1", AddressMode = "rel:0" });
+ sut2.Frames.Add(new SentryStackFrame() { Function = "2", AddressMode = "rel:1" });
+ sut2.Frames.Add(new SentryStackFrame() { Function = "3", AddressMode = "rel:0" });
+ sut2.Frames.Add(new SentryStackFrame() { Function = "4", AddressMode = "rel:2" });
+
+ // Only the two new debug images are added.
+ sut2.MergeDebugImagesInto(e);
+ Assert.Equal(4, e.DebugImages.Count);
+ Assert.Equal("1111.dll", e.DebugImages[0].CodeFile);
+ Assert.Equal("2222.dll", e.DebugImages[1].CodeFile);
+ Assert.Equal("3333.dll", e.DebugImages[2].CodeFile);
+ Assert.Equal("4444.dll", e.DebugImages[3].CodeFile);
+
+ // First stack trace must remain unchanged.
+ CheckStackTraceIsUnchanged(sut1);
+
+ // Stack trace is updated to reflect new positions
+ Assert.Equal(4, sut2.Frames.Count);
+ Assert.Equal("1", sut2.Frames[0].Function);
+ Assert.Equal("rel:2", sut2.Frames[0].AddressMode);
+ Assert.Equal("2", sut2.Frames[1].Function);
+ Assert.Equal("rel:0", sut2.Frames[1].AddressMode);
+ Assert.Equal("3", sut2.Frames[2].Function);
+ Assert.Equal("rel:2", sut2.Frames[2].AddressMode);
+ Assert.Equal("4", sut2.Frames[3].Function);
+ Assert.Equal("rel:3", sut2.Frames[3].AddressMode);
+
+ void CheckStackTraceIsUnchanged(SentryStackTrace stackTrace)
+ {
+ Assert.Equal(3, stackTrace.Frames.Count);
+ Assert.Equal("1", stackTrace.Frames[0].Function);
+ Assert.Equal("rel:0", stackTrace.Frames[0].AddressMode);
+ Assert.Equal("2", stackTrace.Frames[1].Function);
+ Assert.Equal("rel:1", stackTrace.Frames[1].AddressMode);
+ Assert.Equal("3", stackTrace.Frames[2].Function);
+ Assert.Equal("rel:0", stackTrace.Frames[2].AddressMode);
+ }
+ }
+
+ private class InjectableDebugStackTrace : DebugStackTrace
+ {
+ public InjectableDebugStackTrace(SentryOptions options) : base(options) { }
+
+ public void Inject(int identifier)
+ {
+ DebugImages.Add(new DebugImage
+ {
+ CodeFile = $"{identifier}.dll",
+ ModuleVersionId = new Guid($"00000000-0000-0000-0000-{identifier:D12}")
+ });
+ }
+ }
+}
diff --git a/test/Sentry.Tests/Internals/MonoSentryStackTraceFactoryTests.cs b/test/Sentry.Tests/Internals/MonoSentryStackTraceFactoryTests.cs
deleted file mode 100644
index 35a0074eab..0000000000
--- a/test/Sentry.Tests/Internals/MonoSentryStackTraceFactoryTests.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-// ReSharper disable once CheckNamespace
-// Stack trace filters out Sentry frames by namespace
-namespace Other.Tests.Internals;
-
-public class MonoSentryStackTraceFactoryTests
-{
- private class Fixture
- {
- public SentryOptions SentryOptions { get; set; } = new();
- public MonoSentryStackTraceFactory GetSut() => new(SentryOptions);
- }
-
- private readonly Fixture _fixture = new();
-
- [Fact]
- public void Create_UnityIl2Cpp_LeavesOutZeroedOutPath()
- {
- var sut = _fixture.GetSut();
- var ex = new TestStackTraceException(@" at UnityEngine.ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, System.Boolean pressed, System.Boolean released) [0x00000] in <00000000000000000000000000000000>:0
- at UnityEngine.EventSystems.StandaloneInputModule.Process () [0x00000] in <00000000000000000000000000000000>:0 ");
-
- var actual = sut.Create(ex);
- Assert.Equal("Process ()", actual!.Frames[0].Function);
- Assert.Equal("UnityEngine.EventSystems.StandaloneInputModule", actual!.Frames[0].Module);
- Assert.Equal("ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, System.Boolean pressed, System.Boolean released)", actual!.Frames[1].Function);
- Assert.Equal("UnityEngine", actual!.Frames[1].Module);
-
- for (var i = 0; i < 2; i++)
- {
- Assert.Null(actual!.Frames[i].ColumnNumber);
- Assert.Null(actual!.Frames[i].LineNumber);
- Assert.Null(actual!.Frames[i].InstructionOffset);
- Assert.Null(actual!.Frames[i].Package);
- Assert.Null(actual!.Frames[i].Platform);
- Assert.Null(actual!.Frames[i].InternalVars);
- Assert.Null(actual!.Frames[i].AbsolutePath);
- Assert.Null(actual!.Frames[i].ContextLine);
- Assert.Null(actual!.Frames[i].FileName);
- Assert.Equal(0, actual!.Frames[i].ImageAddress);
- }
- }
-
- [Fact]
- public void Create_NoExceptionAndAttachStackTraceOptionFalse_NullResult()
- {
- _fixture.SentryOptions.AttachStacktrace = false;
- var sut = _fixture.GetSut();
-
- Assert.Null(sut.Create());
- }
-
- [Fact]
- public void Create_WithExceptionAndDefaultAttachStackTraceOption_HasStackTrace()
- {
- var sut = _fixture.GetSut();
-
- Exception exception;
- try
- {
- Throw();
- static void Throw() => throw null!;
- }
- catch (Exception e) { exception = e; }
-
- Assert.NotNull(sut.Create(exception));
- }
-
- [Fact]
- public void Create_WithExceptionAndAttachStackTraceOptionOn_HasStackTrace()
- {
- _fixture.SentryOptions.AttachStacktrace = true;
- var sut = _fixture.GetSut();
-
- Exception exception;
- try
- {
- Throw();
- static void Throw() => throw null!;
- }
- catch (Exception e) { exception = e; }
-
- var stackTrace = sut.Create(exception);
-
- Assert.Equal(new StackTrace(exception, true).FrameCount, stackTrace!.Frames.Count);
- }
-
- [Fact]
- public void CreateSentryStackFrame_AppNamespace_InAppFrame()
- {
- var frame = new StackFrame();
- var sut = _fixture.GetSut();
-
- var actual = sut.CreateFrame(frame);
-
- Assert.True(actual.InApp);
- }
-
- [Fact]
- public void CreateSentryStackFrame_AppNamespaceExcluded_NotInAppFrame()
- {
- _fixture.SentryOptions.AddInAppExclude(GetType().Namespace!);
- var sut = _fixture.GetSut();
- var frame = new StackFrame();
-
- var actual = sut.CreateFrame(frame);
-
- Assert.False(actual.InApp);
- }
-
- [Fact]
- public void CreateSentryStackFrame_NamespaceIncludedAndExcluded_IncludesTakesPrecedence()
- {
- _fixture.SentryOptions.AddInAppExclude(GetType().Namespace!);
- _fixture.SentryOptions.AddInAppInclude(GetType().Namespace!);
- var sut = _fixture.GetSut();
- var frame = new StackFrame();
-
- var actual = sut.CreateFrame(frame);
-
- Assert.True(actual.InApp);
- }
-
- private class TestStackTraceException : Exception
- {
- public TestStackTraceException(string stacktrace) => StackTrace = stacktrace;
-
- public TestStackTraceException() : base()
- {
- }
-
- public TestStackTraceException(string message, Exception innerException) : base(message, innerException)
- {
- }
-
- public override string StackTrace { get; }
- }
-}
diff --git a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Enhanced.verified.txt b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Enhanced.verified.txt
index f684faad9f..8fb43a4f4a 100644
--- a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Enhanced.verified.txt
+++ b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Enhanced.verified.txt
@@ -1,5 +1,6 @@
-{
+{
FileName: Internals/SentryStackTraceFactoryTests.cs,
Function: void SentryStackTraceFactoryTests.GenericMethodThatThrows(T value),
- InApp: true
+ InApp: true,
+ AddressMode: rel:0
}
\ No newline at end of file
diff --git a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Original.verified.txt b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Original.verified.txt
index da8ff44bf1..a64466b158 100644
--- a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Original.verified.txt
+++ b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.MethodGeneric_mode=Original.verified.txt
@@ -1,6 +1,7 @@
-{
+{
FileName: Internals/SentryStackTraceFactoryTests.cs,
Function: GenericMethodThatThrows,
Module: Other.Tests.Internals.SentryStackTraceFactoryTests,
- InApp: true
+ InApp: true,
+ AddressMode: rel:0
}
\ No newline at end of file
diff --git a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.cs b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.cs
index b703f66673..e4f607c8f1 100644
--- a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.cs
+++ b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.cs
@@ -8,12 +8,11 @@ public partial class SentryStackTraceFactoryTests
{
private class Fixture
{
- public SentryOptions SentryOptions { get; set; } = new();
+ public SentryOptions SentryOptions { get; } = new();
public SentryStackTraceFactory GetSut() => new(SentryOptions);
}
private readonly Fixture _fixture = new();
- private static readonly string ThisNamespace = typeof(SentryStackTraceFactoryTests).Namespace;
[Fact]
public void Create_NoExceptionAndAttachStackTraceOptionFalse_NullResult()
@@ -41,7 +40,7 @@ public void Create_NoExceptionAndAttachStackTraceOptionOnWithOriginalMode_Curren
Assert.DoesNotContain(stackTrace.Frames, p =>
p.Function?.StartsWith(
- nameof(SentryStackTraceFactory.CreateFrame) + '(',
+ nameof(DebugStackTrace.CreateFrame) + '(',
StringComparison.Ordinal
) == true);
}
@@ -105,7 +104,7 @@ public void Create_NoExceptionAndAttachStackTraceOptionOnWithEnhancedMode_Curren
Assert.DoesNotContain(stackTrace.Frames, p =>
p.Function?.StartsWith(
- nameof(SentryStackTraceFactory.CreateFrame) + '(',
+ nameof(DebugStackTrace.CreateFrame) + '(',
StringComparison.Ordinal
) == true);
}
@@ -196,67 +195,6 @@ public void Create_InlineCase_IncludesAmpersandAfterParameterType(StackTraceMode
frame.Function.Should().Be(method);
}
- [Fact]
- public void CreateSentryStackFrame_AppNamespace_InAppFrame()
- {
- var frame = new StackFrame();
- var sut = _fixture.GetSut();
-
- var actual = sut.CreateFrame(frame);
-
- Assert.True(actual.InApp);
- }
-
- [Fact]
- public void CreateSentryStackFrame_AppNamespaceExcluded_NotInAppFrame()
- {
- _fixture.SentryOptions.AddInAppExclude(ThisNamespace);
- var sut = _fixture.GetSut();
- var frame = new StackFrame();
-
- var actual = sut.CreateFrame(frame);
-
- Assert.False(actual.InApp);
- }
-
- [Fact]
- public void CreateSentryStackFrame_NamespaceIncludedAndExcluded_IncludesTakesPrecedence()
- {
- _fixture.SentryOptions.AddInAppExclude(ThisNamespace);
- _fixture.SentryOptions.AddInAppInclude(ThisNamespace);
- var sut = _fixture.GetSut();
- var frame = new StackFrame();
-
- var actual = sut.CreateFrame(frame);
-
- Assert.True(actual.InApp);
- }
-
- // https://github.com/getsentry/sentry-dotnet/issues/64
- [Fact]
- public void DemangleAnonymousFunction_NullFunction_ContinuesNull()
- {
- var stackFrame = new SentryStackFrame
- {
- Function = null
- };
-
- SentryStackTraceFactory.DemangleAnonymousFunction(stackFrame);
- Assert.Null(stackFrame.Function);
- }
-
- [Fact]
- public void DemangleAsyncFunctionName_NullModule_ContinuesNull()
- {
- var stackFrame = new SentryStackFrame
- {
- Module = null
- };
-
- SentryStackTraceFactory.DemangleAnonymousFunction(stackFrame);
- Assert.Null(stackFrame.Module);
- }
-
// ReSharper disable UnusedParameter.Local
[MethodImpl(MethodImplOptions.NoInlining)]
private static (Fixture f, int b) ByRefMethodThatThrows(int value, in int valueIn, ref int valueRef, out int valueOut) =>
diff --git a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.verify.cs b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.verify.cs
index 8f72024e60..e701641303 100644
--- a/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.verify.cs
+++ b/test/Sentry.Tests/Internals/SentryStackTraceFactoryTests.verify.cs
@@ -34,7 +34,9 @@ public Task MethodGeneric(StackTraceMode mode)
x => x.Package,
x => x.LineNumber,
x => x.ColumnNumber,
- x => x.InstructionOffset).AddScrubber(x => x.Replace(@"\", @"/"))
+ x => x.InstructionAddress,
+ x => x.FunctionId)
+ .AddScrubber(x => x.Replace(@"\", @"/"))
.UseParameters(mode);
}
}
diff --git a/test/Sentry.Tests/SentryOptionsTests.cs b/test/Sentry.Tests/SentryOptionsTests.cs
index 52c75befdc..72f12fd7e3 100644
--- a/test/Sentry.Tests/SentryOptionsTests.cs
+++ b/test/Sentry.Tests/SentryOptionsTests.cs
@@ -34,24 +34,4 @@ public void AttachStackTrace_ByDefault_True()
var sut = new SentryOptions();
Assert.True(sut.AttachStacktrace);
}
-
-#if NETFRAMEWORK
- [SkippableFact(typeof(IsTypeException))]
- public void StackTraceFactory_RunningOnMono_HasMonoStackTraceFactory()
- {
- Skip.If(!RuntimeInfo.GetRuntime().IsMono());
-
- var sut = new SentryOptions();
- Assert.IsType(sut.SentryStackTraceFactory);
- }
-
- [SkippableFact(typeof(IsNotTypeException))]
- public void StackTraceFactory_NotRunningOnMono_NotMonoStackTraceFactory()
- {
- Skip.If(RuntimeInfo.GetRuntime().IsMono());
-
- var sut = new SentryOptions();
- Assert.IsNotType(sut.SentryStackTraceFactory);
- }
-#endif
}