From bdc3bd19039e6dad8fbf66426e453bdf4d3647a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:03:44 +1200 Subject: [PATCH 01/56] build(deps): bump actions/create-github-app-token from 2.0.6 to 2.1.0 (#4430) Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2.0.6 to 2.1.0. - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/df432ceedc7162793a195dd1713ff69aefc7379e...0f859bf9e69e887678d5bbfbee594437cb440ffe) --- updated-dependencies: - dependency-name: actions/create-github-app-token dependency-version: 2.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31f6df0b26..5b6ad85765 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From e06e7dceb6ee8ec24432bf9d98d7e7fc465169b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:19:07 +1200 Subject: [PATCH 02/56] chore: update scripts/update-cli.ps1 to 2.52.0 (#4444) Co-authored-by: GitHub --- CHANGELOG.md | 6 +++--- Directory.Build.props | 2 +- src/Sentry/Sentry.csproj | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2219c95c1c..56493c596f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,9 @@ ### Dependencies -- Bump CLI from v2.50.2 to v2.51.1 ([#4419](https://github.com/getsentry/sentry-dotnet/pull/4419), [#4435](https://github.com/getsentry/sentry-dotnet/pull/4435)) - - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2511) - - [diff](https://github.com/getsentry/sentry-cli/compare/2.50.2...2.51.1) +- Bump CLI from v2.50.2 to v2.52.0 ([#4419](https://github.com/getsentry/sentry-dotnet/pull/4419), [#4435](https://github.com/getsentry/sentry-dotnet/pull/4435), [#4444](https://github.com/getsentry/sentry-dotnet/pull/4444)) + - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2520) + - [diff](https://github.com/getsentry/sentry-cli/compare/2.50.2...2.52.0) ## 5.14.0 diff --git a/Directory.Build.props b/Directory.Build.props index 0f0289c5e4..d287755b90 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -86,7 +86,7 @@ - 2.51.1 + 2.52.0 $(MSBuildThisFileDirectory)tools\sentry-cli\$(SentryCLIVersion)\ diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index e1aac028ff..033e256622 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -113,13 +113,13 @@ <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - - - - - - - + + + + + + + From cfbc2fd48b6b611211b0d66030c5f7b6cabc0579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:36:53 +0200 Subject: [PATCH 03/56] fix(logs): remove Dispose from the public API surface area (#4424) --- CHANGELOG.md | 2 ++ .../Internal/DefaultSentryStructuredLogger.cs | 11 +++-------- src/Sentry/Internal/Hub.cs | 2 +- src/Sentry/SentryStructuredLogger.cs | 17 +---------------- .../ApiApprovalTests.Run.DotNet8_0.verified.txt | 4 +--- .../ApiApprovalTests.Run.DotNet9_0.verified.txt | 4 +--- .../ApiApprovalTests.Run.Net4_8.verified.txt | 4 +--- .../Sentry.Tests/SentryStructuredLoggerTests.cs | 3 ++- 8 files changed, 12 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56493c596f..bebd5b1f82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Crontabs now support day names (MON-SUN) and allow step values and ranges to be combined ([#4407](https://github.com/getsentry/sentry-dotnet/pull/4407)) - Ensure the correct Sentry Cocoa SDK framework version is used on iOS ([#4411](https://github.com/getsentry/sentry-dotnet/pull/4411)) +- Experimental _Structured Logs_: + - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) ### Dependencies diff --git a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs index 37b03babbd..42d61705f1 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -3,7 +3,7 @@ namespace Sentry.Internal; -internal sealed class DefaultSentryStructuredLogger : SentryStructuredLogger +internal sealed class DefaultSentryStructuredLogger : SentryStructuredLogger, IDisposable { private readonly IHub _hub; private readonly SentryOptions _options; @@ -105,13 +105,8 @@ protected internal override void Flush() } /// - protected override void Dispose(bool disposing) + public void Dispose() { - if (disposing) - { - _batchProcessor.Dispose(); - } - - base.Dispose(disposing); + _batchProcessor.Dispose(); } } diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index af7de6635d..733cdf2391 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -832,7 +832,7 @@ public void Dispose() #endif Logger.Flush(); - Logger.Dispose(); + (Logger as IDisposable)?.Dispose(); // see Sentry.Internal.DefaultSentryStructuredLogger try { diff --git a/src/Sentry/SentryStructuredLogger.cs b/src/Sentry/SentryStructuredLogger.cs index 0170c28254..19842bbc72 100644 --- a/src/Sentry/SentryStructuredLogger.cs +++ b/src/Sentry/SentryStructuredLogger.cs @@ -8,7 +8,7 @@ namespace Sentry; /// This API is experimental and it may change in the future. /// [Experimental(DiagnosticId.ExperimentalFeature)] -public abstract class SentryStructuredLogger : IDisposable +public abstract class SentryStructuredLogger { internal static SentryStructuredLogger Create(IHub hub, SentryOptions options, ISystemClock clock) => Create(hub, options, clock, 100, TimeSpan.FromSeconds(5)); @@ -123,19 +123,4 @@ public void LogFatal(string template, object[]? parameters = null, Action - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Override in inherited types to clean up managed and unmanaged resources. - /// - /// Invoked from when ; Invoked from Finalize when . - protected virtual void Dispose(bool disposing) - { - } } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index ffedc70e00..d2aa1f6c92 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -1011,11 +1011,9 @@ namespace Sentry public static Sentry.SentryStackTrace FromJson(System.Text.Json.JsonElement json) { } } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public abstract class SentryStructuredLogger : System.IDisposable + public abstract class SentryStructuredLogger { protected abstract void CaptureLog(Sentry.SentryLog log); - public void Dispose() { } - protected virtual void Dispose(bool disposing) { } protected abstract void Flush(); [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] public void LogDebug(string template, object[]? parameters = null, System.Action? configureLog = null) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index ffedc70e00..d2aa1f6c92 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -1011,11 +1011,9 @@ namespace Sentry public static Sentry.SentryStackTrace FromJson(System.Text.Json.JsonElement json) { } } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public abstract class SentryStructuredLogger : System.IDisposable + public abstract class SentryStructuredLogger { protected abstract void CaptureLog(Sentry.SentryLog log); - public void Dispose() { } - protected virtual void Dispose(bool disposing) { } protected abstract void Flush(); [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] public void LogDebug(string template, object[]? parameters = null, System.Action? configureLog = null) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index fb49f7c839..b5093e14bd 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -971,11 +971,9 @@ namespace Sentry public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } public static Sentry.SentryStackTrace FromJson(System.Text.Json.JsonElement json) { } } - public abstract class SentryStructuredLogger : System.IDisposable + public abstract class SentryStructuredLogger { protected abstract void CaptureLog(Sentry.SentryLog log); - public void Dispose() { } - protected virtual void Dispose(bool disposing) { } protected abstract void Flush(); public void LogDebug(string template, object[]? parameters = null, System.Action? configureLog = null) { } public void LogError(string template, object[]? parameters = null, System.Action? configureLog = null) { } diff --git a/test/Sentry.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Tests/SentryStructuredLoggerTests.cs index aeb121badc..b64b258e69 100644 --- a/test/Sentry.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Tests/SentryStructuredLoggerTests.cs @@ -260,7 +260,8 @@ public void Dispose_BeforeLog_DoesNotCaptureEnvelope() _fixture.Options.Experimental.EnableLogs = true; var logger = _fixture.GetSut(); - logger.Dispose(); + var defaultLogger = logger.Should().BeOfType().Which; + defaultLogger.Dispose(); logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); From f7ec5eed1da822d14ff52ed812688d01ae0587fa Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 14 Aug 2025 22:31:34 +1200 Subject: [PATCH 04/56] chore: Remove dangling experimental metrics classes (#4410) Resolves #4404 - https://github.com/getsentry/sentry-dotnet/issues/4404 #skip-changelog --- src/Sentry/BuiltInSystemDiagnosticsMeters.cs | 249 ------------------ src/Sentry/ExperimentalMetricsOptions.cs | 61 ----- ...piApprovalTests.Run.DotNet8_0.verified.txt | 23 -- ...piApprovalTests.Run.DotNet9_0.verified.txt | 23 -- .../ApiApprovalTests.Run.Net4_8.verified.txt | 23 -- .../BuiltInSystemDiagnosticsMetersTests.cs | 104 -------- 6 files changed, 483 deletions(-) delete mode 100644 src/Sentry/BuiltInSystemDiagnosticsMeters.cs delete mode 100644 src/Sentry/ExperimentalMetricsOptions.cs delete mode 100644 test/Sentry.Tests/BuiltInSystemDiagnosticsMetersTests.cs diff --git a/src/Sentry/BuiltInSystemDiagnosticsMeters.cs b/src/Sentry/BuiltInSystemDiagnosticsMeters.cs deleted file mode 100644 index 145546897f..0000000000 --- a/src/Sentry/BuiltInSystemDiagnosticsMeters.cs +++ /dev/null @@ -1,249 +0,0 @@ -using Sentry.Internal; - -namespace Sentry; - -/// -/// Well known values for built-in metrics that can be configured for -/// -/// -public static partial class BuiltInSystemDiagnosticsMeters -{ - private const string MicrosoftAspNetCoreHostingPattern = @"^Microsoft\.AspNetCore\.Hosting$"; - private const string MicrosoftAspNetCoreRoutingPattern = @"^Microsoft\.AspNetCore\.Routing$"; - private const string MicrosoftAspNetCoreDiagnosticsPattern = @"^Microsoft\.AspNetCore\.Diagnostics$"; - private const string MicrosoftAspNetCoreRateLimitingPattern = @"^Microsoft\.AspNetCore\.RateLimiting$"; - private const string MicrosoftAspNetCoreHeaderParsingPattern = @"^Microsoft\.AspNetCore\.HeaderParsing$"; - private const string MicrosoftAspNetCoreServerKestrelPattern = @"^Microsoft\.AspNetCore\.Server\.Kestrel$"; - private const string MicrosoftAspNetCoreHttpConnectionsPattern = @"^Microsoft\.AspNetCore\.Http\.Connections$"; - private const string MicrosoftExtensionsDiagnosticsHealthChecksPattern = @"^Microsoft\.Extensions\.Diagnostics\.HealthChecks$"; - private const string MicrosoftExtensionsDiagnosticsResourceMonitoringPattern = @"^Microsoft\.Extensions\.Diagnostics\.ResourceMonitoring$"; - private const string OpenTelemetryInstrumentationRuntimePattern = @"^OpenTelemetry\.Instrumentation\.Runtime$"; - private const string SystemNetNameResolutionPattern = @"^System\.Net\.NameResolution$"; - private const string SystemNetHttpPattern = @"^System\.Net\.Http$"; - - /// - /// Matches the built-in Microsoft.AspNetCore.Hosting metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreHosting = MicrosoftAspNetCoreHostingRegex; - - [GeneratedRegex(MicrosoftAspNetCoreHostingPattern)] - private static partial Regex MicrosoftAspNetCoreHostingRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreHosting = MicrosoftAspNetCoreHostingRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreHostingPattern)] - private static partial Regex MicrosoftAspNetCoreHostingRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreHosting = new Regex(MicrosoftAspNetCoreHostingPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.AspNetCore.Routing metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreRouting = MicrosoftAspNetCoreRoutingRegex; - - [GeneratedRegex(MicrosoftAspNetCoreRoutingPattern)] - private static partial Regex MicrosoftAspNetCoreRoutingRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreRouting = MicrosoftAspNetCoreRoutingRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreRoutingPattern)] - private static partial Regex MicrosoftAspNetCoreRoutingRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreRouting = new Regex(MicrosoftAspNetCoreRoutingPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.AspNetCore.Diagnostics metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreDiagnostics = MicrosoftAspNetCoreDiagnosticsRegex; - - [GeneratedRegex(MicrosoftAspNetCoreDiagnosticsPattern)] - private static partial Regex MicrosoftAspNetCoreDiagnosticsRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreDiagnostics = MicrosoftAspNetCoreDiagnosticsRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreDiagnosticsPattern)] - private static partial Regex MicrosoftAspNetCoreDiagnosticsRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreDiagnostics = new Regex(MicrosoftAspNetCoreDiagnosticsPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.AspNetCore.RateLimiting metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreRateLimiting = MicrosoftAspNetCoreRateLimitingRegex; - - [GeneratedRegex(MicrosoftAspNetCoreRateLimitingPattern)] - private static partial Regex MicrosoftAspNetCoreRateLimitingRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreRateLimiting = MicrosoftAspNetCoreRateLimitingRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreRateLimitingPattern)] - private static partial Regex MicrosoftAspNetCoreRateLimitingRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreRateLimiting = new Regex(MicrosoftAspNetCoreRateLimitingPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.AspNetCore.HeaderParsing metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreHeaderParsing = MicrosoftAspNetCoreHeaderParsingRegex; - - [GeneratedRegex(MicrosoftAspNetCoreHeaderParsingPattern)] - private static partial Regex MicrosoftAspNetCoreHeaderParsingRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreHeaderParsing = MicrosoftAspNetCoreHeaderParsingRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreHeaderParsingPattern)] - private static partial Regex MicrosoftAspNetCoreHeaderParsingRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreHeaderParsing = new Regex(MicrosoftAspNetCoreHeaderParsingPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.AspNetCore.Server.Kestrel metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreServerKestrel = MicrosoftAspNetCoreServerKestrelRegex; - - [GeneratedRegex(MicrosoftAspNetCoreServerKestrelPattern)] - private static partial Regex MicrosoftAspNetCoreServerKestrelRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreServerKestrel = MicrosoftAspNetCoreServerKestrelRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreServerKestrelPattern)] - private static partial Regex MicrosoftAspNetCoreServerKestrelRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreServerKestrel = new Regex(MicrosoftAspNetCoreServerKestrelPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.AspNetCore.Http.Connections metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftAspNetCoreHttpConnections = MicrosoftAspNetCoreHttpConnectionsRegex; - - [GeneratedRegex(MicrosoftAspNetCoreHttpConnectionsPattern)] - private static partial Regex MicrosoftAspNetCoreHttpConnectionsRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftAspNetCoreHttpConnections = MicrosoftAspNetCoreHttpConnectionsRegex(); - - [GeneratedRegex(MicrosoftAspNetCoreHttpConnectionsPattern)] - private static partial Regex MicrosoftAspNetCoreHttpConnectionsRegex(); -#else - public static readonly StringOrRegex MicrosoftAspNetCoreHttpConnections = new Regex(MicrosoftAspNetCoreHttpConnectionsPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.Extensions.Diagnostics.HealthChecks metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftExtensionsDiagnosticsHealthChecks = MicrosoftExtensionsDiagnosticsHealthChecksRegex; - - [GeneratedRegex(MicrosoftExtensionsDiagnosticsHealthChecksPattern)] - private static partial Regex MicrosoftExtensionsDiagnosticsHealthChecksRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftExtensionsDiagnosticsHealthChecks = MicrosoftExtensionsDiagnosticsHealthChecksRegex(); - - [GeneratedRegex(MicrosoftExtensionsDiagnosticsHealthChecksPattern)] - private static partial Regex MicrosoftExtensionsDiagnosticsHealthChecksRegex(); -#else - public static readonly StringOrRegex MicrosoftExtensionsDiagnosticsHealthChecks = new Regex(MicrosoftExtensionsDiagnosticsHealthChecksPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in Microsoft.Extensions.Diagnostics.ResourceMonitoring metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex MicrosoftExtensionsDiagnosticsResourceMonitoring = MicrosoftExtensionsDiagnosticsResourceMonitoringRegex; - - [GeneratedRegex(MicrosoftExtensionsDiagnosticsResourceMonitoringPattern)] - private static partial Regex MicrosoftExtensionsDiagnosticsResourceMonitoringRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex MicrosoftExtensionsDiagnosticsResourceMonitoring = MicrosoftExtensionsDiagnosticsResourceMonitoringRegex(); - - [GeneratedRegex(MicrosoftExtensionsDiagnosticsResourceMonitoringPattern)] - private static partial Regex MicrosoftExtensionsDiagnosticsResourceMonitoringRegex(); -#else - public static readonly StringOrRegex MicrosoftExtensionsDiagnosticsResourceMonitoring = new Regex(MicrosoftExtensionsDiagnosticsResourceMonitoringPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in System.Net.NameResolution metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex OpenTelemetryInstrumentationRuntime = OpenTelemetryInstrumentationRuntimeRegex; - - [GeneratedRegex(OpenTelemetryInstrumentationRuntimePattern)] - private static partial Regex OpenTelemetryInstrumentationRuntimeRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex OpenTelemetryInstrumentationRuntime = OpenTelemetryInstrumentationRuntimeRegex(); - - [GeneratedRegex(OpenTelemetryInstrumentationRuntimePattern)] - private static partial Regex OpenTelemetryInstrumentationRuntimeRegex(); -#else - public static readonly StringOrRegex OpenTelemetryInstrumentationRuntime = new Regex(OpenTelemetryInstrumentationRuntimePattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in System.Net.NameResolution metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex SystemNetNameResolution = SystemNetNameResolutionRegex; - - [GeneratedRegex(SystemNetNameResolutionPattern)] - private static partial Regex SystemNetNameResolutionRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex SystemNetNameResolution = SystemNetNameResolutionRegex(); - - [GeneratedRegex(SystemNetNameResolutionPattern)] - private static partial Regex SystemNetNameResolutionRegex(); -#else - public static readonly StringOrRegex SystemNetNameResolution = new Regex(SystemNetNameResolutionPattern, RegexOptions.Compiled); -#endif - - /// - /// Matches the built-in metrics - /// -#if NET9_0_OR_GREATER - public static readonly StringOrRegex SystemNetHttp = SystemNetHttpRegex; - - [GeneratedRegex(SystemNetHttpPattern)] - private static partial Regex SystemNetHttpRegex { get; } -#elif NET8_0 - public static readonly StringOrRegex SystemNetHttp = SystemNetHttpRegex(); - - [GeneratedRegex(SystemNetHttpPattern)] - private static partial Regex SystemNetHttpRegex(); -#else - public static readonly StringOrRegex SystemNetHttp = new Regex(SystemNetHttpPattern, RegexOptions.Compiled); -#endif - - private static readonly Lazy> LazyAll = new(() => new List - { - MicrosoftAspNetCoreHosting, - MicrosoftAspNetCoreRouting, - MicrosoftAspNetCoreDiagnostics, - MicrosoftAspNetCoreRateLimiting, - MicrosoftAspNetCoreHeaderParsing, - MicrosoftAspNetCoreServerKestrel, - MicrosoftAspNetCoreHttpConnections, - MicrosoftExtensionsDiagnosticsHealthChecks, - MicrosoftExtensionsDiagnosticsResourceMonitoring, - OpenTelemetryInstrumentationRuntime, - SystemNetNameResolution, - SystemNetHttp, - }); - - /// - /// Matches all built-in metrics - /// - /// - public static IList All => LazyAll.Value; -} diff --git a/src/Sentry/ExperimentalMetricsOptions.cs b/src/Sentry/ExperimentalMetricsOptions.cs deleted file mode 100644 index 0b07ad0e0b..0000000000 --- a/src/Sentry/ExperimentalMetricsOptions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Sentry.Internal; - -namespace Sentry; - -/// -/// Settings for the experimental Metrics feature. This feature is preview only and will very likely change in the future -/// without a major version bump... so use at your own risk. -/// -public class ExperimentalMetricsOptions -{ - /// - /// Determines whether code locations should be recorded for Metrics - /// - public bool EnableCodeLocations { get; set; } = true; - - private IList _captureSystemDiagnosticsInstruments = new List(); - - /// - /// - /// A list of Substrings or Regular Expressions. Any `System.Diagnostics.Metrics.Instrument` whose name - /// matches one of the items in this list will be collected and reported to Sentry. - /// - /// - /// These can be either custom Instruments that you have created or any of the built-in metrics that are available. - /// - /// - /// See https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics for more information. - /// - /// - public IList CaptureSystemDiagnosticsInstruments - { - // NOTE: During configuration binding, .NET 6 and lower used to just call Add on the existing item. - // .NET 7 changed this to call the setter with an array that already starts with the old value. - // We have to handle both cases. - get => _captureSystemDiagnosticsInstruments; - set => _captureSystemDiagnosticsInstruments = value.WithConfigBinding(); - } - - private IList _captureSystemDiagnosticsMeters = BuiltInSystemDiagnosticsMeters.All; - - /// - /// - /// A list of Substrings or Regular Expressions. Instruments for any `System.Diagnostics.Metrics.Meter` - /// whose name matches one of the items in this list will be collected and reported to Sentry. - /// - /// - /// These can be either custom Instruments that you have created or any of the built-in metrics that are available. - /// - /// - /// See https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics for more information. - /// - /// - public IList CaptureSystemDiagnosticsMeters - { - // NOTE: During configuration binding, .NET 6 and lower used to just call Add on the existing item. - // .NET 7 changed this to call the setter with an array that already starts with the old value. - // We have to handle both cases. - get => _captureSystemDiagnosticsMeters; - set => _captureSystemDiagnosticsMeters = value.WithConfigBinding(); - } -} diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index d2aa1f6c92..b94a74dc34 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -40,22 +40,6 @@ namespace Sentry [System.Runtime.Serialization.EnumMember(Value="critical")] Critical = 3, } - public static class BuiltInSystemDiagnosticsMeters - { - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreDiagnostics; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHeaderParsing; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHosting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHttpConnections; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreRateLimiting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreRouting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreServerKestrel; - public static readonly Sentry.StringOrRegex MicrosoftExtensionsDiagnosticsHealthChecks; - public static readonly Sentry.StringOrRegex MicrosoftExtensionsDiagnosticsResourceMonitoring; - public static readonly Sentry.StringOrRegex OpenTelemetryInstrumentationRuntime; - public static readonly Sentry.StringOrRegex SystemNetHttp; - public static readonly Sentry.StringOrRegex SystemNetNameResolution; - public static System.Collections.Generic.IList All { get; } - } public class ByteAttachmentContent : Sentry.IAttachmentContent { public ByteAttachmentContent(byte[] bytes) { } @@ -112,13 +96,6 @@ namespace Sentry public static void SetFingerprint(this Sentry.IEventLike eventLike, System.Collections.Generic.IEnumerable fingerprint) { } public static void SetFingerprint(this Sentry.IEventLike eventLike, params string[] fingerprint) { } } - public class ExperimentalMetricsOptions - { - public ExperimentalMetricsOptions() { } - public System.Collections.Generic.IList CaptureSystemDiagnosticsInstruments { get; set; } - public System.Collections.Generic.IList CaptureSystemDiagnosticsMeters { get; set; } - public bool EnableCodeLocations { get; set; } - } public class FileAttachmentContent : Sentry.IAttachmentContent { public FileAttachmentContent(string filePath) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index d2aa1f6c92..b94a74dc34 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -40,22 +40,6 @@ namespace Sentry [System.Runtime.Serialization.EnumMember(Value="critical")] Critical = 3, } - public static class BuiltInSystemDiagnosticsMeters - { - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreDiagnostics; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHeaderParsing; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHosting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHttpConnections; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreRateLimiting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreRouting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreServerKestrel; - public static readonly Sentry.StringOrRegex MicrosoftExtensionsDiagnosticsHealthChecks; - public static readonly Sentry.StringOrRegex MicrosoftExtensionsDiagnosticsResourceMonitoring; - public static readonly Sentry.StringOrRegex OpenTelemetryInstrumentationRuntime; - public static readonly Sentry.StringOrRegex SystemNetHttp; - public static readonly Sentry.StringOrRegex SystemNetNameResolution; - public static System.Collections.Generic.IList All { get; } - } public class ByteAttachmentContent : Sentry.IAttachmentContent { public ByteAttachmentContent(byte[] bytes) { } @@ -112,13 +96,6 @@ namespace Sentry public static void SetFingerprint(this Sentry.IEventLike eventLike, System.Collections.Generic.IEnumerable fingerprint) { } public static void SetFingerprint(this Sentry.IEventLike eventLike, params string[] fingerprint) { } } - public class ExperimentalMetricsOptions - { - public ExperimentalMetricsOptions() { } - public System.Collections.Generic.IList CaptureSystemDiagnosticsInstruments { get; set; } - public System.Collections.Generic.IList CaptureSystemDiagnosticsMeters { get; set; } - public bool EnableCodeLocations { get; set; } - } public class FileAttachmentContent : Sentry.IAttachmentContent { public FileAttachmentContent(string filePath) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index b5093e14bd..aa006ffe82 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -40,22 +40,6 @@ namespace Sentry [System.Runtime.Serialization.EnumMember(Value="critical")] Critical = 3, } - public static class BuiltInSystemDiagnosticsMeters - { - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreDiagnostics; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHeaderParsing; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHosting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreHttpConnections; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreRateLimiting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreRouting; - public static readonly Sentry.StringOrRegex MicrosoftAspNetCoreServerKestrel; - public static readonly Sentry.StringOrRegex MicrosoftExtensionsDiagnosticsHealthChecks; - public static readonly Sentry.StringOrRegex MicrosoftExtensionsDiagnosticsResourceMonitoring; - public static readonly Sentry.StringOrRegex OpenTelemetryInstrumentationRuntime; - public static readonly Sentry.StringOrRegex SystemNetHttp; - public static readonly Sentry.StringOrRegex SystemNetNameResolution; - public static System.Collections.Generic.IList All { get; } - } public class ByteAttachmentContent : Sentry.IAttachmentContent { public ByteAttachmentContent(byte[] bytes) { } @@ -102,13 +86,6 @@ namespace Sentry public static void SetFingerprint(this Sentry.IEventLike eventLike, System.Collections.Generic.IEnumerable fingerprint) { } public static void SetFingerprint(this Sentry.IEventLike eventLike, params string[] fingerprint) { } } - public class ExperimentalMetricsOptions - { - public ExperimentalMetricsOptions() { } - public System.Collections.Generic.IList CaptureSystemDiagnosticsInstruments { get; set; } - public System.Collections.Generic.IList CaptureSystemDiagnosticsMeters { get; set; } - public bool EnableCodeLocations { get; set; } - } public class FileAttachmentContent : Sentry.IAttachmentContent { public FileAttachmentContent(string filePath) { } diff --git a/test/Sentry.Tests/BuiltInSystemDiagnosticsMetersTests.cs b/test/Sentry.Tests/BuiltInSystemDiagnosticsMetersTests.cs deleted file mode 100644 index c9b045f776..0000000000 --- a/test/Sentry.Tests/BuiltInSystemDiagnosticsMetersTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace Sentry.Tests; - -public class BuiltInSystemDiagnosticsMetersTests -{ - [Fact] - public void MicrosoftAspNetCoreHosting_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreHosting; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.Hosting"); - } - - [Fact] - public void MicrosoftAspNetCoreRouting_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreRouting; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.Routing"); - } - - [Fact] - public void MicrosoftAspNetCoreDiagnostics_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreDiagnostics; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.Diagnostics"); - } - - [Fact] - public void MicrosoftAspNetCoreRateLimiting_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreRateLimiting; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.RateLimiting"); - } - - [Fact] - public void MicrosoftAspNetCoreHeaderParsing_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreHeaderParsing; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.HeaderParsing"); - } - - [Fact] - public void MicrosoftAspNetCoreServerKestrel_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreServerKestrel; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.Server.Kestrel"); - } - - [Fact] - public void MicrosoftAspNetCoreHttpConnections_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftAspNetCoreHttpConnections; - - pattern.ShouldMatchOnlyExactText("Microsoft.AspNetCore.Http.Connections"); - } - - [Fact] - public void MicrosoftExtensionsDiagnosticsHealthChecks_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftExtensionsDiagnosticsHealthChecks; - - pattern.ShouldMatchOnlyExactText("Microsoft.Extensions.Diagnostics.HealthChecks"); - } - - [Fact] - public void MicrosoftExtensionsDiagnosticsResourceMonitoring_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.MicrosoftExtensionsDiagnosticsResourceMonitoring; - - pattern.ShouldMatchOnlyExactText("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); - } - - [Fact] - public void SystemNetNameResolution_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.SystemNetNameResolution; - - pattern.ShouldMatchOnlyExactText("System.Net.NameResolution"); - } - - [Fact] - public void SystemNetHttp_ExactString_Matches() - { - var pattern = BuiltInSystemDiagnosticsMeters.SystemNetHttp; - - pattern.ShouldMatchOnlyExactText("System.Net.Http"); - } -} - -internal static class BuiltInSystemDiagnosticsMetersTestsExtensions -{ - internal static void ShouldMatchOnlyExactText(this StringOrRegex pattern, string actual) - { - var withPrefix = "prefix" + actual; - var withSuffix = actual + "suffix"; - SubstringOrPatternMatcher.Default.IsMatch(pattern, actual).Should().BeTrue(); - SubstringOrPatternMatcher.Default.IsMatch(pattern, withPrefix).Should().BeFalse(); - SubstringOrPatternMatcher.Default.IsMatch(pattern, withSuffix).Should().BeFalse(); - } -} From b6fb3231d30aa07a71d590337853727d49f14c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:42:37 +0200 Subject: [PATCH 05/56] fix(logs): flush Logger on UnhandledException that IsTerminating (#4425) --- CHANGELOG.md | 10 ++++++++-- src/Sentry/Internal/Hub.cs | 1 + test/Sentry.Tests/HubTests.cs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b841681e99..c685603f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,19 @@ # Changelog +## Unreleased + +### Fixes + +- Experimental _Structured Logs_: + - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) + - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) + ## 5.14.1 ### Fixes - Crontabs now support day names (MON-SUN) and allow step values and ranges to be combined ([#4407](https://github.com/getsentry/sentry-dotnet/pull/4407)) - Ensure the correct Sentry Cocoa SDK framework version is used on iOS ([#4411](https://github.com/getsentry/sentry-dotnet/pull/4411)) -- Experimental _Structured Logs_: - - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) ### Dependencies diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 733cdf2391..1c0d6ebf8b 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -798,6 +798,7 @@ public async Task FlushAsync(TimeSpan timeout) { try { + Logger.Flush(); await CurrentClient.FlushAsync(timeout).ConfigureAwait(false); } catch (Exception e) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 9c20fbc0fe..1f7e94ee13 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1624,6 +1624,31 @@ public void Logger_DisableAfterCreate_HasNoEffect() hub.Logger.Should().BeOfType(); } + [Fact] + public async Task Logger_FlushAsync_DoesCaptureLog() + { + // Arrange + _fixture.Options.Experimental.EnableLogs = true; + var hub = _fixture.GetSut(); + + // Act + hub.Logger.LogWarning("Message"); + await hub.FlushAsync(); + + // Assert + _fixture.Client.Received(1).CaptureEnvelope( + Arg.Is(envelope => + envelope.Items.Single(item => item.Header["type"].Equals("log")).Payload.GetType().IsAssignableFrom(typeof(JsonSerializable)) + ) + ); + await _fixture.Client.Received(1).FlushAsync( + Arg.Is(timeout => + timeout.Equals(_fixture.Options.FlushTimeout) + ) + ); + hub.Logger.Should().BeOfType(); + } + [Fact] public void Logger_Dispose_DoesCaptureLog() { @@ -1641,6 +1666,11 @@ public void Logger_Dispose_DoesCaptureLog() envelope.Items.Single(item => item.Header["type"].Equals("log")).Payload.GetType().IsAssignableFrom(typeof(JsonSerializable)) ) ); + _fixture.Client.Received(1).FlushAsync( + Arg.Is(timeout => + timeout.Equals(_fixture.Options.ShutdownTimeout) + ) + ); hub.Logger.Should().BeOfType(); } From 3d2b22a4205a59a89db2db5c26228117544c6f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Fri, 15 Aug 2025 05:04:36 +0200 Subject: [PATCH 06/56] ci: fix iOS Device Tests (#4447) --------- Co-authored-by: James Crosswell --- scripts/device-test.ps1 | 12 ++- scripts/ios-simulator-utils.ps1 | 95 +++++++++++++++++++ .../Sentry.Maui.Device.TestApp.csproj | 2 +- 3 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 scripts/ios-simulator-utils.ps1 diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index 6a20d133d5..4a3d29e6c7 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -11,6 +11,7 @@ param( Set-StrictMode -Version latest $ErrorActionPreference = 'Stop' +. $PSScriptRoot/ios-simulator-utils.ps1 if (!$Build -and !$Run) { @@ -57,8 +58,15 @@ try '--app', "$buildDir/Sentry.Maui.Device.TestApp.app", '--target', 'ios-simulator-64', '--launch-timeout', '00:10:00', - '--set-env', 'CI=$envValue' + '--set-env', "CI=$envValue" ) + + $udid = Get-IosSimulatorUdid -IosVersion '18.5' -Verbose + if ($udid) { + $arguments += @('--device', $udid) + } else { + Write-Host "No suitable simulator found; proceeding without a specific --device" + } } if ($Build) @@ -76,7 +84,7 @@ try if (!(Get-Command xharness -ErrorAction SilentlyContinue)) { Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/temp') - dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25330.2' ` + dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25412.1' ` --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json Pop-Location } diff --git a/scripts/ios-simulator-utils.ps1 b/scripts/ios-simulator-utils.ps1 new file mode 100644 index 0000000000..51f1b72fbd --- /dev/null +++ b/scripts/ios-simulator-utils.ps1 @@ -0,0 +1,95 @@ +function Get-IosSimulatorUdid { + [CmdletBinding()] + param( + [string]$IosVersion = '18.5', + [string[]]$PreferredDeviceTypes = @( + 'com.apple.CoreSimulator.SimDeviceType.iPhone-XS', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-16', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-15' + ) + ) + + try { + $simDevices = & xcrun simctl list devices --json | ConvertFrom-Json + } catch { + Write-Verbose "Failed to query simctl: $($_.Exception.Message)" + return $null + } + if (-not $simDevices -or -not $simDevices.devices) { + Write-Verbose "No devices structure returned." + return $null + } + + $devicesByRuntime = $simDevices.devices + $preferredIndex = @{} + for ($i = 0; $i -lt $PreferredDeviceTypes.Count; $i++) { + $preferredIndex[$PreferredDeviceTypes[$i]] = $i + } + + $dashVer = $IosVersion -replace '\.','-' + $exactKey = "com.apple.CoreSimulator.SimRuntime.iOS-$dashVer" + $runtimeKey = $null + + $allRuntimeNames = $devicesByRuntime.PSObject.Properties.Name + if ($allRuntimeNames -contains $exactKey) { + $runtimeKey = $exactKey + Write-Verbose "Found exact runtime: $runtimeKey" + } else { + $major = ($IosVersion.Split('.')[0]) + $candidates = $allRuntimeNames | Where-Object { $_ -match "com\.apple\.CoreSimulator\.SimRuntime\.iOS-$major-" } + if ($candidates) { + $runtimeKey = $candidates | + Sort-Object { + $v = ($_ -replace '.*iOS-','') -replace '-','.' + try { [Version]$v } catch { [Version]'0.0' } + } -Descending | + Select-Object -First 1 + Write-Verbose "Exact runtime $exactKey not found. Using fallback runtime $runtimeKey" + } else { + Write-Verbose "No simulator runtime found for iOS major $major" + return $null + } + } + + $runtimeDevices = $devicesByRuntime.PSObject.Properties | + Where-Object { $_.Name -eq $runtimeKey } | + Select-Object -ExpandProperty Value + + if (-not $runtimeDevices) { + Write-Verbose "Runtime key $runtimeKey present but no devices listed." + return $null + } + + $usable = $runtimeDevices | Where-Object { $_.isAvailable -and $_.state -in @('Shutdown','Booted') } + if (-not $usable) { + Write-Verbose "No available devices in runtime $runtimeKey" + return $null + } + + $ranked = $usable | ForEach-Object { + $dt = $_.deviceTypeIdentifier + $weightPref = if ($preferredIndex.ContainsKey($dt)) { $preferredIndex[$dt] } else { 9999 } + $weightFamily = if ($dt -match 'iPhone') { 0 } else { 1 } # prefer iPhone if not explicitly listed + [PSCustomObject]@{ + Device = $_ + WeightPref = $weightPref + WeightFamily = $weightFamily + WeightBoot = if ($_.state -eq 'Booted') { 0 } else { 1 } + SortName = $_.name + } + } + + $sorted = $ranked | Sort-Object WeightPref, WeightFamily, WeightBoot, SortName + $sorted | Select-Object -First 5 | ForEach-Object { + Write-Verbose ("Candidate: {0} | {1} | pref={2} fam={3} bootW={4}" -f $_.Device.name, $_.Device.deviceTypeIdentifier, $_.WeightPref, $_.WeightFamily, $_.WeightBoot) + } + + $selected = $sorted | Select-Object -First 1 + if (-not $selected) { + Write-Verbose "Failed to select a simulator." + return $null + } + + Write-Verbose ("Selected simulator: {0} ({1}) [{2}]" -f $selected.Device.name, $selected.Device.deviceTypeIdentifier, $selected.Device.udid) + return $selected.Device.udid +} 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 15beeb7e92..3ea49a432c 100644 --- a/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj +++ b/test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj @@ -34,7 +34,7 @@ 21.0 - 18 + 18.0 true From ebaeb6b8d5fd27c860753290f30eb20741dbaf33 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 14 Aug 2025 23:07:42 -0400 Subject: [PATCH 07/56] ignore Android binding warnings (#4423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix Android binding warnings - treat as errors but suppress expected warnings - Enable TreatWarningsAsErrors for Android bindings project - Suppress expected/unavoidable Android binding warnings (BG8xxx codes) - These warnings are caused by Java interface circular dependencies in Sentry SDK - Warnings are documented with explanations for each type - New warning types will now break the build for review - Analyzed 1528 warnings and confirmed they're due to binding limitations - Solution preserves existing Metadata.xml strategic type removals --------- Co-authored-by: Stefan Pölz <38893694+Flash0ver@users.noreply.github.com> --- .../Sentry.Bindings.Android.csproj | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj b/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj index b8dbd70ea6..632b8ba146 100644 --- a/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj +++ b/src/Sentry.Bindings.Android/Sentry.Bindings.Android.csproj @@ -6,6 +6,19 @@ .NET Bindings for the Sentry Android SDK + + + + + + + + + + + + + $(NoWarn);BG8801;BG8C01;BG8701;BG8800;BG8700;BG8605;BG8606;BG8604;BG8502;BG8401;BG8400;BG8503;BG8C00 From 52c485886c334b41e372d72470402c38d9e9b228 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:02:48 +1200 Subject: [PATCH 08/56] chore(deps): update Native SDK to v0.10.0 (#4436) --------- Co-authored-by: GitHub Co-authored-by: James Crosswell --- CHANGELOG.md | 6 ++++++ modules/sentry-native | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c685603f23..1bcfa31848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) +### Dependencies + +- Bump Native SDK from v0.9.1 to v0.10.0 ([#4436](https://github.com/getsentry/sentry-dotnet/pull/4436)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0100) + - [diff](https://github.com/getsentry/sentry-native/compare/0.9.1...0.10.0) + ## 5.14.1 ### Fixes diff --git a/modules/sentry-native b/modules/sentry-native index a64d5bd8ee..b3320bb360 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit a64d5bd8ee130f2cda196b6fa7d9b65bfa6d32e2 +Subproject commit b3320bb36003d4737d332158211811693cb0fbf1 From f1e7e927ef04fcee3d4233e5b4733c5fa566d1c7 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 15 Aug 2025 22:12:50 +1200 Subject: [PATCH 09/56] Increase timeout for xharness from 15 min (default) to 25 min (#4449) See this test run on the main branch (times out after 15 minutes): - https://github.com/getsentry/sentry-dotnet/actions/runs/16981934177 #skip-changelog --- scripts/device-test.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index 4a3d29e6c7..03abdc2041 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -37,6 +37,7 @@ try '--app', "$buildDir/io.sentry.dotnet.maui.device.testapp-Signed.apk", '--package-name', 'io.sentry.dotnet.maui.device.testapp', '--launch-timeout', '00:10:00', + '--timeout', '00:25:00', '--instrumentation', 'Sentry.Maui.Device.TestApp.SentryInstrumentation' ) @@ -58,6 +59,7 @@ try '--app', "$buildDir/Sentry.Maui.Device.TestApp.app", '--target', 'ios-simulator-64', '--launch-timeout', '00:10:00', + '--timeout', '00:25:00', '--set-env', "CI=$envValue" ) From cbd289b307202b8651caeb705d7d974887b6be23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:12:26 +0200 Subject: [PATCH 10/56] fix(logs): race condition in high volume logging scenarios (#4428) --- CHANGELOG.md | 1 + ...gBatchProcessorBenchmarks-report-github.md | 24 ++++++---- .../StructuredLogBatchProcessorBenchmarks.cs | 45 ++++++++++++------- .../Internal/StructuredLogBatchBuffer.cs | 2 + src/Sentry/Threading/ScopedCountdownLock.cs | 23 ++++++---- .../Threading/ScopedCountdownLockTests.cs | 14 +++--- 6 files changed, 70 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bcfa31848..e816ffe624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Experimental _Structured Logs_: - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) + - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) ### Dependencies diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md index befa791365..8461170b2c 100644 --- a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md +++ b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md @@ -1,6 +1,6 @@ ``` -BenchmarkDotNet v0.13.12, macOS 15.5 (24F74) [Darwin 24.5.0] +BenchmarkDotNet v0.13.12, macOS 15.6 (24G84) [Darwin 24.6.0] Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores .NET SDK 9.0.301 [Host] : .NET 8.0.14 (8.0.1425.11118), Arm64 RyuJIT AdvSIMD @@ -8,11 +8,17 @@ Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores ``` -| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Gen0 | Allocated | -|---------------- |----------- |-------------------- |------------:|---------:|---------:|-------:|----------:| -| **EnqueueAndFlush** | **10** | **100** | **1,774.5 ns** | **7.57 ns** | **6.71 ns** | **0.6104** | **5 KB** | -| **EnqueueAndFlush** | **10** | **200** | **3,468.5 ns** | **11.16 ns** | **10.44 ns** | **1.2207** | **10 KB** | -| **EnqueueAndFlush** | **10** | **1000** | **17,259.7 ns** | **51.92 ns** | **46.02 ns** | **6.1035** | **50 KB** | -| **EnqueueAndFlush** | **100** | **100** | **857.5 ns** | **4.21 ns** | **3.73 ns** | **0.1469** | **1.2 KB** | -| **EnqueueAndFlush** | **100** | **200** | **1,681.4 ns** | **1.74 ns** | **1.63 ns** | **0.2937** | **2.41 KB** | -| **EnqueueAndFlush** | **100** | **1000** | **8,302.2 ns** | **12.00 ns** | **10.64 ns** | **1.4648** | **12.03 KB** | +| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Gen0 | Allocated | +|------------------------- |----------- |-------------------- |-------------:|------------:|------------:|-------:|----------:| +| **EnqueueAndFlush** | **10** | **100** | **1,793.4 ns** | **13.75 ns** | **12.86 ns** | **0.6104** | **5 KB** | +| EnqueueAndFlush_Parallel | 10 | 100 | 18,550.8 ns | 368.24 ns | 889.34 ns | 1.1292 | 9.16 KB | +| **EnqueueAndFlush** | **10** | **200** | **3,679.8 ns** | **18.65 ns** | **16.53 ns** | **1.2207** | **10 KB** | +| EnqueueAndFlush_Parallel | 10 | 200 | 41,246.4 ns | 508.07 ns | 475.25 ns | 1.7090 | 14.04 KB | +| **EnqueueAndFlush** | **10** | **1000** | **17,239.1 ns** | **62.50 ns** | **58.46 ns** | **6.1035** | **50 KB** | +| EnqueueAndFlush_Parallel | 10 | 1000 | 192,059.3 ns | 956.92 ns | 895.11 ns | 4.3945 | 37.52 KB | +| **EnqueueAndFlush** | **100** | **100** | **866.7 ns** | **1.99 ns** | **1.77 ns** | **0.1469** | **1.2 KB** | +| EnqueueAndFlush_Parallel | 100 | 100 | 6,714.8 ns | 100.75 ns | 94.24 ns | 0.5569 | 4.52 KB | +| **EnqueueAndFlush** | **100** | **200** | **1,714.5 ns** | **3.20 ns** | **3.00 ns** | **0.2937** | **2.41 KB** | +| EnqueueAndFlush_Parallel | 100 | 200 | 43,842.8 ns | 860.74 ns | 1,718.99 ns | 0.9155 | 7.51 KB | +| **EnqueueAndFlush** | **100** | **1000** | **8,537.8 ns** | **9.80 ns** | **9.17 ns** | **1.4648** | **12.03 KB** | +| EnqueueAndFlush_Parallel | 100 | 1000 | 313,421.4 ns | 6,159.27 ns | 6,846.01 ns | 1.9531 | 18.37 KB | diff --git a/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs b/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs index 336d726926..9085068cce 100644 --- a/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs @@ -1,5 +1,4 @@ using BenchmarkDotNet.Attributes; -using NSubstitute; using Sentry.Extensibility; using Sentry.Internal; @@ -30,22 +29,10 @@ public void Setup() }; var batchInterval = Timeout.InfiniteTimeSpan; - - var clientReportRecorder = Substitute.For(); - clientReportRecorder - .When(static recorder => recorder.RecordDiscardedEvent(Arg.Any(), Arg.Any(), Arg.Any())) - .Throw(); - - var diagnosticLogger = Substitute.For(); - diagnosticLogger - .When(static logger => logger.IsEnabled(Arg.Any())) - .Throw(); - diagnosticLogger - .When(static logger => logger.Log(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())) - .Throw(); + var clientReportRecorder = new NullClientReportRecorder(); _hub = new Hub(options, DisabledHub.Instance); - _batchProcessor = new StructuredLogBatchProcessor(_hub, BatchCount, batchInterval, clientReportRecorder, diagnosticLogger); + _batchProcessor = new StructuredLogBatchProcessor(_hub, BatchCount, batchInterval, clientReportRecorder, null); _log = new SentryLog(DateTimeOffset.Now, SentryId.Empty, SentryLogLevel.Trace, "message"); } @@ -59,6 +46,16 @@ public void EnqueueAndFlush() _batchProcessor.Flush(); } + [Benchmark] + public void EnqueueAndFlush_Parallel() + { + _ = Parallel.For(0, OperationsPerInvoke, (int i) => + { + _batchProcessor.Enqueue(_log); + }); + _batchProcessor.Flush(); + } + [GlobalCleanup] public void Cleanup() { @@ -66,3 +63,21 @@ public void Cleanup() _hub.Dispose(); } } + +file sealed class NullClientReportRecorder : IClientReportRecorder +{ + public void RecordDiscardedEvent(DiscardReason reason, DataCategory category, int quantity = 1) + { + // no-op + } + + public ClientReport GenerateClientReport() + { + throw new UnreachableException(); + } + + public void Load(ClientReport report) + { + throw new UnreachableException(); + } +} diff --git a/src/Sentry/Internal/StructuredLogBatchBuffer.cs b/src/Sentry/Internal/StructuredLogBatchBuffer.cs index 7e518253a6..a9f49216b7 100644 --- a/src/Sentry/Internal/StructuredLogBatchBuffer.cs +++ b/src/Sentry/Internal/StructuredLogBatchBuffer.cs @@ -8,6 +8,8 @@ namespace Sentry.Internal; /// /// Must be attempted to flush via when either the is reached, /// or when the is exceeded. +/// Utilizes a , basically used as an inverse , +/// allowing multiple threads for or exclusive access for . /// [DebuggerDisplay("Name = {Name}, Capacity = {Capacity}, Additions = {_additions}, AddCount = {AddCount}, IsDisposed = {_disposed}")] internal sealed class StructuredLogBatchBuffer : IDisposable diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index bc3725f1a9..f3d992f893 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -40,7 +40,7 @@ internal ScopedCountdownLock() internal bool IsEngaged => _isEngaged == 1; /// - /// No will be entered when the has reached while the lock is engaged via an active . + /// No will be entered when the has reached , or while the lock is engaged via an active . /// Check via whether the underlying has not been set/signaled yet. /// To signal the underlying , ensure is called. /// @@ -49,6 +49,11 @@ internal ScopedCountdownLock() /// internal CounterScope TryEnterCounterScope() { + if (IsEngaged) + { + return new CounterScope(); + } + if (_event.TryAddCount(1)) { return new CounterScope(this); @@ -59,6 +64,7 @@ internal CounterScope TryEnterCounterScope() private void ExitCounterScope() { + Debug.Assert(_event.CurrentCount >= 1); _ = _event.Signal(); } @@ -75,7 +81,8 @@ internal LockScope TryEnterLockScope() { if (Interlocked.CompareExchange(ref _isEngaged, 1, 0) == 0) { - _ = _event.Signal(); // decrement the initial count of 1, so that the event can be set with the count reaching 0 when all 'CounterScope's have exited + Debug.Assert(_event.CurrentCount >= 1); + _ = _event.Signal(); // decrement the initial count of 1, so that the event can be set with the count reaching 0 when all entered 'CounterScope' instances have exited return new LockScope(this); } @@ -84,13 +91,13 @@ internal LockScope TryEnterLockScope() private void ExitLockScope() { - if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) == 1) + Debug.Assert(_event.IsSet); + _event.Reset(); // reset the signaled event to the initial count of 1, so that new 'CounterScope' instances can be entered again + + if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) != 1) { - _event.Reset(); // reset the signaled event to the initial count of 1, so that new `CounterScope`s can be entered again - return; + Debug.Fail("The Lock should have not been disengaged without being engaged first."); } - - Debug.Fail("The Lock should have not been disengaged without being engaged first."); } /// @@ -139,7 +146,7 @@ internal LockScope(ScopedCountdownLock lockObj) internal void Wait() { var lockObj = _lockObj; - lockObj?._event.Wait(); + lockObj?._event.Wait(Timeout.Infinite, CancellationToken.None); } public void Dispose() diff --git a/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs b/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs index dad07e1e23..d6bafc79a7 100644 --- a/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs +++ b/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs @@ -79,12 +79,12 @@ public void TryEnterLockScope_IsEngaged_IsSet() lockTwo.Dispose(); AssertEngaged(false, 1); - // successfully enter another CounterScope ... lock is engaged but not yet set + // cannot enter another CounterScope as long as the lock is already engaged by a LockScope var counterTwo = _lock.TryEnterCounterScope(); - counterTwo.IsEntered.Should().BeTrue(); - AssertEngaged(false, 2); + counterTwo.IsEntered.Should().BeFalse(); + AssertEngaged(false, 1); - // exit a CounterScope ... decrement the count + // no-op ... CounterScope is not entered counterTwo.Dispose(); AssertEngaged(false, 1); @@ -92,7 +92,7 @@ public void TryEnterLockScope_IsEngaged_IsSet() counterOne.Dispose(); AssertEngaged(true, 0); - // cannot enter another CounterScope as long as the engaged lock is set + // cannot enter another CounterScope as long as the lock is engaged and set var counterThree = _lock.TryEnterCounterScope(); counterThree.IsEntered.Should().BeFalse(); AssertEngaged(true, 0); @@ -102,11 +102,11 @@ public void TryEnterLockScope_IsEngaged_IsSet() // would block if the count of the engaged lock was not zero lockOne.Wait(); - // exit the LockScope ... reset the lock + // exit the LockScope ... reset and disengage the lock lockOne.Dispose(); AssertDisengaged(false, 0); - // can enter a CounterScope again ... the lock not set + // can enter a CounterScope again ... the lock is no longer set and no longer engaged var counterFour = _lock.TryEnterCounterScope(); counterFour.IsEntered.Should().BeTrue(); AssertDisengaged(false, 1); From 679eebdbdc24ad5da7162324fe161307195c043b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Sat, 16 Aug 2025 12:41:16 +0200 Subject: [PATCH 11/56] ref(logs): shorten `Microsoft.Extensions.Logging` attribute names (#4450) --- CHANGELOG.md | 9 +++++++-- .../SentryStructuredLogger.cs | 6 +++--- ...AspNetCoreStructuredLoggerProviderTests.cs | 2 +- .../SentryStructuredLoggerProviderTests.cs | 2 +- .../SentryStructuredLoggerTests.cs | 20 +++++++++---------- ...SentryMauiStructuredLoggerProviderTests.cs | 2 +- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e816ffe624..3e1c1ba100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,16 @@ ## Unreleased +### Features + +- Experimental _Structured Logs_: + - Shorten the `key` names of `Microsoft.Extensions.Logging` attributes ([#4450](https://github.com/getsentry/sentry-dotnet/pull/4450)) + ### Fixes - Experimental _Structured Logs_: - - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) + - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) + - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) ### Dependencies diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index 87a0daae49..36e68454a6 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -92,15 +92,15 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (_categoryName is not null) { - log.SetAttribute("microsoft.extensions.logging.category_name", _categoryName); + log.SetAttribute("category.name", _categoryName); } if (eventId.Name is not null || eventId.Id != 0) { - log.SetAttribute("microsoft.extensions.logging.event.id", eventId.Id); + log.SetAttribute("event.id", eventId.Id); } if (eventId.Name is not null) { - log.SetAttribute("microsoft.extensions.logging.event.name", eventId.Name); + log.SetAttribute("event.name", eventId.Name); } _hub.Logger.CaptureLog(log); diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs index 9edf8363ac..797aedf435 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs @@ -83,7 +83,7 @@ public void CreateLogger_DependencyInjection_CanLog() logger.LogInformation("message"); Assert.NotNull(capturedLog); - capturedLog.TryGetAttribute("microsoft.extensions.logging.category_name", out object? categoryName).Should().BeTrue(); + capturedLog.TryGetAttribute("category.name", out object? categoryName).Should().BeTrue(); categoryName.Should().Be(typeof(SentryAspNetCoreStructuredLoggerProviderTests).FullName); capturedLog.TryGetAttribute("sentry.sdk.name", out object? name).Should().BeTrue(); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs index bd43dfc668..cb48224c51 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs @@ -83,7 +83,7 @@ public void CreateLogger_DependencyInjection_CanLog() logger.LogInformation("message"); Assert.NotNull(capturedLog); - capturedLog.TryGetAttribute("microsoft.extensions.logging.category_name", out object? categoryName).Should().BeTrue(); + capturedLog.TryGetAttribute("category.name", out object? categoryName).Should().BeTrue(); categoryName.Should().Be(typeof(SentryStructuredLoggerProviderTests).FullName); capturedLog.TryGetAttribute("sentry.sdk.name", out object? name).Should().BeTrue(); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs index 65878cdf93..dcf7e0a4c5 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs @@ -111,9 +111,9 @@ public void Log_LogLevel_CaptureLog(LogLevel logLevel, SentryLogLevel expectedLe log.AssertAttribute("sentry.release", "my-release"); log.AssertAttribute("sentry.sdk.name", "SDK Name"); log.AssertAttribute("sentry.sdk.version", "SDK Version"); - log.AssertAttribute("microsoft.extensions.logging.category_name", "CategoryName"); - log.AssertAttribute("microsoft.extensions.logging.event.id", 123); - log.AssertAttribute("microsoft.extensions.logging.event.name", "EventName"); + log.AssertAttribute("category.name", _fixture.CategoryName); + log.AssertAttribute("event.id", eventId.Id); + log.AssertAttribute("event.name", eventId.Name!); } [Fact] @@ -192,7 +192,7 @@ public void Log_WithoutCategoryName_CaptureLog() logger.Log(LogLevel.Information, new EventId(123, "EventName"), new InvalidOperationException("message"), "Message with {Argument}.", "argument"); var log = _fixture.CapturedLogs.Dequeue(); - log.TryGetAttribute("microsoft.extensions.logging.category_name", out object? _).Should().BeFalse(); + log.TryGetAttribute("category.name", out object? _).Should().BeFalse(); } [Fact] @@ -216,8 +216,8 @@ public void Log_WithoutEvent_CaptureLog() logger.Log(LogLevel.Information, new InvalidOperationException("message"), "Message with {Argument}.", "argument"); var log = _fixture.CapturedLogs.Dequeue(); - log.TryGetAttribute("microsoft.extensions.logging.event.id", out object? _).Should().BeFalse(); - log.TryGetAttribute("microsoft.extensions.logging.event.name", out object? _).Should().BeFalse(); + log.TryGetAttribute("event.id", out object? _).Should().BeFalse(); + log.TryGetAttribute("event.name", out object? _).Should().BeFalse(); } [Fact] @@ -228,8 +228,8 @@ public void Log_WithoutEventId_CaptureLog() logger.Log(LogLevel.Information, new EventId(0, "EventName"), new InvalidOperationException("message"), "Message with {Argument}.", "argument"); var log = _fixture.CapturedLogs.Dequeue(); - log.AssertAttribute("microsoft.extensions.logging.event.id", 0); - log.AssertAttribute("microsoft.extensions.logging.event.name", "EventName"); + log.AssertAttribute("event.id", 0); + log.AssertAttribute("event.name", "EventName"); } [Fact] @@ -240,8 +240,8 @@ public void Log_WithoutEventName_CaptureLog() logger.Log(LogLevel.Information, new EventId(123), new InvalidOperationException("message"), "Message with {Argument}.", "argument"); var log = _fixture.CapturedLogs.Dequeue(); - log.AssertAttribute("microsoft.extensions.logging.event.id", 123); - log.TryGetAttribute("microsoft.extensions.logging.event.name", out object? _).Should().BeFalse(); + log.AssertAttribute("event.id", 123); + log.TryGetAttribute("event.name", out object? _).Should().BeFalse(); } [Theory] diff --git a/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs b/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs index 1715498a5b..425ced9bbe 100644 --- a/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs +++ b/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs @@ -83,7 +83,7 @@ public void CreateLogger_DependencyInjection_CanLog() logger.LogInformation("message"); Assert.NotNull(capturedLog); - capturedLog.TryGetAttribute("microsoft.extensions.logging.category_name", out object? categoryName).Should().BeTrue(); + capturedLog.TryGetAttribute("category.name", out object? categoryName).Should().BeTrue(); categoryName.Should().Be(typeof(SentryMauiStructuredLoggerProviderTests).FullName); capturedLog.TryGetAttribute("sentry.sdk.name", out object? name).Should().BeTrue(); From 087408c12e0d4b59e3b756f05458bce28922b945 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 18 Aug 2025 19:39:02 +1200 Subject: [PATCH 12/56] Reapply "Bump to cocoa sdk v8.46.0 (#4103)" (#4442) * Revert "Revert "Bump to cocoa sdk v8.46.0 (#4103)" (#4202)" This reverts commit e3ad69a89d42a879c199eb87a4e50a21c95e91d9. --- CHANGELOG.md | 5 +- Directory.Build.props | 2 +- Directory.Build.targets | 2 +- modules/sentry-cocoa.properties | 2 +- .../Sentry.Samples.Maui.csproj | 2 +- scripts/generate-cocoa-bindings.ps1 | 80 +--- src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 188 +++++--- .../Sentry.Bindings.Cocoa.csproj | 2 + src/Sentry.Bindings.Cocoa/StructsAndEnums.cs | 22 - .../SwiftApiDefinitions.cs | 405 ++++++++++++++++++ .../SwiftStructsAndEnums.cs | 65 +++ 11 files changed, 618 insertions(+), 157 deletions(-) create mode 100644 src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs create mode 100644 src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1c1ba100..ed287feb39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ ### Dependencies +- Reapply "Bump Cocoa SDK from v8.39.0 to v8.46.0 (#4103)" ([#4442](https://github.com/getsentry/sentry-dotnet/pull/4442)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8460) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.39.0...8.46.0) - Bump Native SDK from v0.9.1 to v0.10.0 ([#4436](https://github.com/getsentry/sentry-dotnet/pull/4436)) - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0100) - [diff](https://github.com/getsentry/sentry-native/compare/0.9.1...0.10.0) @@ -181,7 +184,7 @@ ### Fixes - Revert "Bump Cocoa SDK from v8.39.0 to v8.46.0 (#4103)" ([#4202](https://github.com/getsentry/sentry-dotnet/pull/4202)) - - IMPORTANT: Fixes multiple issues running versions 5.6.x and 5.7.x of the Sentry SDK for .NET on iOS (initialising the SDK and sending data to Sentry) + - IMPORTANT: Fixes multiple issues running versions 5.6.x and 5.7.x of the Sentry SDK for .NET on iOS (initialising the SDK and sending data to Sentry) ### Dependencies diff --git a/Directory.Build.props b/Directory.Build.props index 783032b382..d7fbc4e99e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -48,7 +48,7 @@ --> $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - 12.2 + 13.0 15.0 21.0 10.0.17763.0 diff --git a/Directory.Build.targets b/Directory.Build.targets index 7593967e09..b01536755f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -10,7 +10,7 @@ --> $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - 12.2 + 13.0 15.0 21.0 10.0.17763.0 diff --git a/modules/sentry-cocoa.properties b/modules/sentry-cocoa.properties index 799f17ac06..9d8498fef2 100644 --- a/modules/sentry-cocoa.properties +++ b/modules/sentry-cocoa.properties @@ -1,2 +1,2 @@ -version = 8.39.0 +version = 8.46.0 repo = https://github.com/getsentry/sentry-cocoa diff --git a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj index 9f87848b21..77c4e079ee 100644 --- a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj +++ b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj @@ -32,7 +32,7 @@ 1.0 1 - 12.2 + 13.0 15.0 21.0 10.0.17763.0 diff --git a/scripts/generate-cocoa-bindings.ps1 b/scripts/generate-cocoa-bindings.ps1 index dc56868e59..90b3d76a51 100644 --- a/scripts/generate-cocoa-bindings.ps1 +++ b/scripts/generate-cocoa-bindings.ps1 @@ -1,3 +1,5 @@ +# Reference: https://github.com/xamarin/xamarin-macios/blob/main/docs/website/binding_types_reference_guide.md + Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' @@ -173,41 +175,6 @@ $Text = $Text -replace '\bpublic\b', 'internal' # Remove static CFunctions class $Text = $Text -replace '(?ms)\nstatic class CFunctions.*?}\n', '' -# This enum resides in the Sentry-Swift.h -# Appending it here so we don't need to import and create bindings for the entire header -$SentryLevel = @' - -[Native] -internal enum SentryLevel : ulong -{ - None = 0, - Debug = 1, - Info = 2, - Warning = 3, - Error = 4, - Fatal = 5 -} -'@ - -# This enum resides in the Sentry-Swift.h -# Appending it here so we don't need to import and create bindings for the entire header -$SentryTransactionNameSource = @' - -[Native] -internal enum SentryTransactionNameSource : long -{ - Custom = 0, - Url = 1, - Route = 2, - View = 3, - Component = 4, - Task = 5 -} -'@ - -$Text += "`n$SentryLevel" -$Text += "`n$SentryTransactionNameSource" - # Add header and output file $Text = "$Header`n`n$Text" $Text | Out-File "$BindingsPath/$File" @@ -238,6 +205,9 @@ $Text = $Text -replace '\bISentrySerializable\b', 'SentrySerializable' # Remove INSCopying due to https://github.com/xamarin/xamarin-macios/issues/17130 $Text = $Text -replace ': INSCopying,', ':' -replace '\s?[:,] INSCopying', '' +# Remove iOS attributes like [iOS (13, 0)] +$Text = $Text -replace '\[iOS \(13, 0\)\]\n?', '' + # Fix delegate argument names $Text = $Text -replace '(NSError) arg\d', '$1 error' $Text = $Text -replace '(NSHttpUrlResponse) arg\d', '$1 response' @@ -279,7 +249,7 @@ $Text = $Text -replace '\[Static\]\s*\[Internal\]\s*partial\s+interface\s+Consta # Update MethodToProperty translations $Text = $Text -replace '(Export \("get\w+"\)\]\n)\s*\[Verify \(MethodToProperty\)\]\n(.+ \{ get; \})', '$1$2' -$Text = $Text -replace '\[Verify \(MethodToProperty\)\]\n\s*(.+ (?:Hash|Value|DefaultIntegrations) \{ get; \})', '$1' +$Text = $Text -replace '\[Verify \(MethodToProperty\)\]\n\s*(.+ (?:Hash|Value|DefaultIntegrations|AppStartMeasurementWithSpans|BaggageHttpHeader) \{ get; \})', '$1' $Text = $Text -replace '\[Verify \(MethodToProperty\)\]\n\s*(.+) \{ get; \}', '$1();' # Allow weakly typed NSArray @@ -292,7 +262,7 @@ $Text = $Text -replace '(DEPRECATED_MSG_ATTRIBUTE\()\n\s*', '$1' # Remove default IsEqual implementation (already implemented by NSObject) $Text = $Text -replace '(?ms)\n?^ *// [^\n]*isEqual:.*?$.*?;\n', '' -# Replace obsolete platform avaialbility attributes +# Replace obsolete platform availability attributes $Text = $Text -replace '([\[,] )MacCatalyst \(', '$1Introduced (PlatformName.MacCatalyst, ' $Text = $Text -replace '([\[,] )Mac \(', '$1Introduced (PlatformName.MacOSX, ' $Text = $Text -replace '([\[,] )iOS \(', '$1Introduced (PlatformName.iOS, ' @@ -305,7 +275,6 @@ $Text = $Text -replace '(?m)(^\s*\/\/[^\r\n]*$\s*\[Export \("serialize"\)\]$\s*) $Text = $Text -replace '.*SentryEnvelope .*?[\s\S]*?\n\n', '' $Text = $Text -replace '.*typedef.*SentryOnAppStartMeasurementAvailable.*?[\s\S]*?\n\n', '' -$Text = $Text -replace '\n.*SentryReplayBreadcrumbConverter.*?[\s\S]*?\);\n', '' $propertiesToRemove = @( 'SentryAppStartMeasurement', @@ -321,41 +290,6 @@ foreach ($property in $propertiesToRemove) $Text = $Text -replace "\n.*property.*$property.*?[\s\S]*?\}\n", '' } -# This interface resides in the Sentry-Swift.h -# Appending it here so we don't need to import and create bindings for the entire header -$SentryId = @' - -// @interface SentryId : NSObject -[BaseType (typeof(NSObject), Name = "_TtC6Sentry8SentryId")] -[Internal] -interface SentryId -{ - // @property (nonatomic, strong, class) SentryId * _Nonnull empty; - [Static] - [Export ("empty", ArgumentSemantic.Strong)] - SentryId Empty { get; set; } - - // @property (readonly, copy, nonatomic) NSString * _Nonnull sentryIdString; - [Export ("sentryIdString")] - string SentryIdString { get; } - - // -(instancetype _Nonnull)initWithUuid:(NSUUID * _Nonnull)uuid __attribute__((objc_designated_initializer)); - [Export ("initWithUuid:")] - [DesignatedInitializer] - NativeHandle Constructor (NSUuid uuid); - - // -(instancetype _Nonnull)initWithUUIDString:(NSString * _Nonnull)uuidString __attribute__((objc_designated_initializer)); - [Export ("initWithUUIDString:")] - [DesignatedInitializer] - NativeHandle Constructor (string uuidString); - - // @property (readonly, nonatomic) NSUInteger hash; - [Export ("hash")] - nuint Hash { get; } -} -'@ - -$Text += "`n$SentryId" # Add header and output file $Text = "$Header`n`n$Text" diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 77ad1d5797..cf38a5e4b4 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -27,6 +27,7 @@ namespace Sentry.CocoaSdk; // typedef SentryEvent * _Nullable (^SentryBeforeSendEventCallback)(SentryEvent * _Nonnull); [Internal] +[return: NullAllowed] delegate SentryEvent SentryBeforeSendEventCallback (SentryEvent @event); // typedef id _Nullable (^SentryBeforeSendSpanCallback)(id _Nonnull); @@ -54,13 +55,13 @@ namespace Sentry.CocoaSdk; [return: NullAllowed] delegate NSNumber SentryTracesSamplerCallback (SentrySamplingContext samplingContext); -// typedef void (^SentrySpanCallback)(id _Nullable); +// typedef void (^SentrySpanCallback)(DEPRECATED_MSG_ATTRIBUTE("See `SentryScope.useSpan` for reasoning of deprecation.") id); [Internal] -delegate void SentrySpanCallback ([NullAllowed] SentrySpan span); +delegate void SentrySpanCallback (SentrySpan span); -// typedef BOOL (^SentryBeforeEmitMetricCallback)(NSString * _Nonnull, NSDictionary * _Nonnull); +// typedef void (^SentryUserFeedbackConfigurationBlock)(SentryUserFeedbackConfiguration * _Nonnull); [Internal] -delegate bool SentryBeforeEmitMetricCallback (string arg0, NSDictionary arg1); +delegate void SentryUserFeedbackConfigurationBlock (SentryUserFeedbackConfiguration arg0); // @interface SentryAttachment : NSObject [BaseType (typeof(NSObject))] @@ -138,6 +139,10 @@ interface SentryBaggage [NullAllowed, Export ("userSegment")] string UserSegment { get; } + // @property (readonly, nonatomic) NSString * _Nullable sampleRand; + [NullAllowed, Export ("sampleRand")] + string SampleRand { get; } + // @property (readonly, nonatomic) NSString * _Nullable sampleRate; [NullAllowed, Export ("sampleRate")] string SampleRate { get; } @@ -154,6 +159,10 @@ interface SentryBaggage [Export ("initWithTraceId:publicKey:releaseName:environment:transaction:userSegment:sampleRate:sampled:replayId:")] NativeHandle Constructor (SentryId traceId, string publicKey, [NullAllowed] string releaseName, [NullAllowed] string environment, [NullAllowed] string transaction, [NullAllowed] string userSegment, [NullAllowed] string sampleRate, [NullAllowed] string sampled, [NullAllowed] string replayId); + // -(instancetype _Nonnull)initWithTraceId:(SentryId * _Nonnull)traceId publicKey:(NSString * _Nonnull)publicKey releaseName:(NSString * _Nullable)releaseName environment:(NSString * _Nullable)environment transaction:(NSString * _Nullable)transaction userSegment:(NSString * _Nullable)userSegment sampleRate:(NSString * _Nullable)sampleRate sampleRand:(NSString * _Nullable)sampleRand sampled:(NSString * _Nullable)sampled replayId:(NSString * _Nullable)replayId; + [Export ("initWithTraceId:publicKey:releaseName:environment:transaction:userSegment:sampleRate:sampleRand:sampled:replayId:")] + NativeHandle Constructor (SentryId traceId, string publicKey, [NullAllowed] string releaseName, [NullAllowed] string environment, [NullAllowed] string transaction, [NullAllowed] string userSegment, [NullAllowed] string sampleRate, [NullAllowed] string sampleRand, [NullAllowed] string sampled, [NullAllowed] string replayId); + // -(NSString * _Nonnull)toHTTPHeaderWithOriginalBaggage:(NSDictionary * _Nullable)originalBaggage; [Export ("toHTTPHeaderWithOriginalBaggage:")] string ToHTTPHeaderWithOriginalBaggage ([NullAllowed] NSDictionary originalBaggage); @@ -198,6 +207,10 @@ interface SentryBreadcrumb : SentrySerializable [NullAllowed, Export ("message")] string Message { get; set; } + // @property (copy, nonatomic) NSString * _Nullable origin; + [NullAllowed, Export ("origin")] + string Origin { get; set; } + // @property (nonatomic, strong) NSDictionary * _Nullable data; [NullAllowed, Export ("data", ArgumentSemantic.Strong)] NSDictionary Data { get; set; } @@ -269,10 +282,14 @@ interface SentryClient [Export ("captureMessage:withScope:")] SentryId CaptureMessage (string message, SentryScope scope); - // -(void)captureUserFeedback:(SentryUserFeedback * _Nonnull)userFeedback __attribute__((swift_name("capture(userFeedback:)"))); + // -(void)captureUserFeedback:(SentryUserFeedback * _Nonnull)userFeedback __attribute__((swift_name("capture(userFeedback:)"))) __attribute__((deprecated("Use -[SentryClient captureFeedback:withScope:]."))); [Export ("captureUserFeedback:")] void CaptureUserFeedback (SentryUserFeedback userFeedback); + // -(void)captureFeedback:(SentryFeedback * _Nonnull)feedback withScope:(SentryScope * _Nonnull)scope __attribute__((swift_name("capture(feedback:scope:)"))); + [Export ("captureFeedback:withScope:")] + void CaptureFeedback (SentryFeedback feedback, SentryScope scope); + // -(void)flush:(NSTimeInterval)timeout __attribute__((swift_name("flush(timeout:)"))); [Export ("flush:")] void Flush (double timeout); @@ -282,13 +299,6 @@ interface SentryClient void Close (); } -// @interface SentryCrashExceptionApplication : NSObject -[BaseType (typeof(NSObject))] -[Internal] -interface SentryCrashExceptionApplication -{ -} - // @interface SentryDebugImageProvider : NSObject [BaseType (typeof(NSObject))] [Internal] @@ -298,7 +308,7 @@ interface SentryDebugImageProvider [Export ("getDebugImagesForThreads:")] SentryDebugMeta[] GetDebugImagesForThreads (SentryThread[] threads); - // -(NSArray * _Nonnull)getDebugImagesForThreads:(NSArray * _Nonnull)threads isCrash:(BOOL)isCrash; + // -(NSArray * _Nonnull)getDebugImagesForThreads:(NSArray * _Nonnull)threads isCrash:(BOOL)isCrash __attribute__((deprecated("This method is slow and will be removed in a future version. Use -[getDebugImagesFromCacheForThreads:] instead."))); [Export ("getDebugImagesForThreads:isCrash:")] SentryDebugMeta[] GetDebugImagesForThreads (SentryThread[] threads, bool isCrash); @@ -306,7 +316,7 @@ interface SentryDebugImageProvider [Export ("getDebugImagesForFrames:")] SentryDebugMeta[] GetDebugImagesForFrames (SentryFrame[] frames); - // -(NSArray * _Nonnull)getDebugImagesForFrames:(NSArray * _Nonnull)frames isCrash:(BOOL)isCrash; + // -(NSArray * _Nonnull)getDebugImagesForFrames:(NSArray * _Nonnull)frames isCrash:(BOOL)isCrash __attribute__((deprecated("This method is slow and will be removed in a future version. Use -[getDebugImagesFromCacheForFrames:] instead."))); [Export ("getDebugImagesForFrames:isCrash:")] SentryDebugMeta[] GetDebugImagesForFrames (SentryFrame[] frames, bool isCrash); @@ -314,7 +324,7 @@ interface SentryDebugImageProvider [Export ("getDebugImages")] SentryDebugMeta[] DebugImages { get; } - // -(NSArray * _Nonnull)getDebugImagesCrashed:(BOOL)isCrash; + // -(NSArray * _Nonnull)getDebugImagesCrashed:(BOOL)isCrash __attribute__((deprecated("This method is slow and will be removed in a future version. Use -[getDebugImagesFromCache:] instead."))); [Export ("getDebugImagesCrashed:")] SentryDebugMeta[] GetDebugImagesCrashed (bool isCrash); } @@ -549,6 +559,13 @@ interface SentryEvent : SentrySerializable NativeHandle Constructor (NSError error); } +// @interface SentryEventDecodable : SentryEvent +[BaseType (typeof(SentryEvent))] +[Internal] +interface SentryEventDecodable +{ +} + // @interface SentryException : NSObject [BaseType (typeof(NSObject))] [DisableDefaultCtor] @@ -873,7 +890,7 @@ interface SentrySpan : SentrySerializable // @required -(NSString * _Nullable)baggageHttpHeader; [Abstract] [NullAllowed, Export ("baggageHttpHeader")] - string BaggageHttpHeader(); + string BaggageHttpHeader { get; } } // @interface SentryHub : NSObject @@ -954,10 +971,14 @@ interface SentryHub [Export ("captureMessage:withScope:")] SentryId CaptureMessage (string message, SentryScope scope); - // -(void)captureUserFeedback:(SentryUserFeedback * _Nonnull)userFeedback __attribute__((swift_name("capture(userFeedback:)"))); + // -(void)captureUserFeedback:(SentryUserFeedback * _Nonnull)userFeedback __attribute__((swift_name("capture(userFeedback:)"))) __attribute__((deprecated("Use -[SentryHub captureFeedback:]."))); [Export ("captureUserFeedback:")] void CaptureUserFeedback (SentryUserFeedback userFeedback); + // -(void)captureFeedback:(SentryFeedback * _Nonnull)feedback; + [Export ("captureFeedback:")] + void CaptureFeedback (SentryFeedback feedback); + // -(void)configureScope:(void (^ _Nonnull)(SentryScope * _Nonnull))callback; [Export ("configureScope:")] void ConfigureScope (Action callback); @@ -1397,6 +1418,10 @@ interface SentryOptions [Export ("enablePerformanceV2")] bool EnablePerformanceV2 { get; set; } + // @property (assign, nonatomic) BOOL enablePersistingTracesWhenCrashing; + [Export ("enablePersistingTracesWhenCrashing")] + bool EnablePersistingTracesWhenCrashing { get; set; } + // @property (nonatomic) SentryScope * _Nonnull (^ _Nonnull)(SentryScope * _Nonnull) initialScope; [Export ("initialScope", ArgumentSemantic.Assign)] Func InitialScope { get; set; } @@ -1429,6 +1454,10 @@ interface SentryOptions [Export ("enablePreWarmedAppStartTracing")] bool EnablePreWarmedAppStartTracing { get; set; } + // @property (nonatomic, strong) SentryReplayOptions * _Nonnull sessionReplay; + [Export ("sessionReplay", ArgumentSemantic.Strong)] + SentryReplayOptions SessionReplay { get; set; } + // @property (assign, nonatomic) BOOL enableNetworkTracking; [Export ("enableNetworkTracking")] bool EnableNetworkTracking { get; set; } @@ -1575,6 +1604,10 @@ interface SentryOptions // @property (copy, nonatomic) NSString * _Nonnull spotlightUrl; [Export ("spotlightUrl")] string SpotlightUrl { get; set; } + + // @property (copy, nonatomic) API_AVAILABLE(ios(13.0)) SentryUserFeedbackConfigurationBlock configureUserFeedback __attribute__((availability(ios, introduced=13.0))); + [Export ("configureUserFeedback", ArgumentSemantic.Copy)] + SentryUserFeedbackConfigurationBlock ConfigureUserFeedback { get; set; } } // @interface SentryReplayApi : NSObject @@ -1605,6 +1638,18 @@ interface SentryReplayApi // -(void)stop; [Export ("stop")] void Stop (); + + // -(void)showMaskPreview; + [Export ("showMaskPreview")] + void ShowMaskPreview (); + + // -(void)showMaskPreview:(CGFloat)opacity; + [Export ("showMaskPreview:")] + void ShowMaskPreview (nfloat opacity); + + // -(void)hideMaskPreview; + [Export ("hideMaskPreview")] + void HideMaskPreview (); } // @interface SentryRequest : NSObject @@ -1762,15 +1807,15 @@ interface SentrySDK [Export ("captureMessage:withScopeBlock:")] SentryId CaptureMessage (string message, Action block); - // +(void)captureUserFeedback:(SentryUserFeedback * _Nonnull)userFeedback __attribute__((swift_name("capture(userFeedback:)"))); + // +(void)captureUserFeedback:(SentryUserFeedback * _Nonnull)userFeedback __attribute__((swift_name("capture(userFeedback:)"))) __attribute__((deprecated("Use SentrySDK.captureFeedback or use or configure our new managed UX with SentryOptions.configureUserFeedback."))); [Static] [Export ("captureUserFeedback:")] void CaptureUserFeedback (SentryUserFeedback userFeedback); - // +(void)showUserFeedbackForm; + // +(void)captureFeedback:(SentryFeedback * _Nonnull)feedback __attribute__((swift_name("capture(feedback:)"))); [Static] - [Export ("showUserFeedbackForm")] - void ShowUserFeedbackForm (); + [Export ("captureFeedback:")] + void CaptureFeedback (SentryFeedback feedback); // +(void)addBreadcrumb:(SentryBreadcrumb * _Nonnull)crumb __attribute__((swift_name("addBreadcrumb(_:)"))); [Static] @@ -1980,7 +2025,7 @@ partial interface SentryScope : SentrySerializable [Export ("clear")] void Clear (); - // -(void)useSpan:(SentrySpanCallback _Nonnull)callback; + // -(void)useSpan:(SentrySpanCallback _Nonnull)callback __attribute__((deprecated("This method was used to create an atomic block that could be used to mutate the current span. It is not atomic anymore and due to issues with memory safety in `NSBlock` it is now considered unsafe and deprecated. Use `span` instead."))); [Export ("useSpan:")] void UseSpan (SentrySpanCallback callback); } @@ -2103,6 +2148,10 @@ interface SentryTraceContext : SentrySerializable [NullAllowed, Export ("sampleRate")] string SampleRate { get; } + // @property (readonly, nonatomic) NSString * _Nullable sampleRand; + [NullAllowed, Export ("sampleRand")] + string SampleRand { get; } + // @property (readonly, nonatomic) NSString * _Nullable sampled; [NullAllowed, Export ("sampled")] string Sampled { get; } @@ -2115,6 +2164,10 @@ interface SentryTraceContext : SentrySerializable [Export ("initWithTraceId:publicKey:releaseName:environment:transaction:userSegment:sampleRate:sampled:replayId:")] NativeHandle Constructor (SentryId traceId, string publicKey, [NullAllowed] string releaseName, [NullAllowed] string environment, [NullAllowed] string transaction, [NullAllowed] string userSegment, [NullAllowed] string sampleRate, [NullAllowed] string sampled, [NullAllowed] string replayId); + // -(instancetype _Nonnull)initWithTraceId:(SentryId * _Nonnull)traceId publicKey:(NSString * _Nonnull)publicKey releaseName:(NSString * _Nullable)releaseName environment:(NSString * _Nullable)environment transaction:(NSString * _Nullable)transaction userSegment:(NSString * _Nullable)userSegment sampleRate:(NSString * _Nullable)sampleRate sampleRand:(NSString * _Nullable)sampleRand sampled:(NSString * _Nullable)sampled replayId:(NSString * _Nullable)replayId; + [Export ("initWithTraceId:publicKey:releaseName:environment:transaction:userSegment:sampleRate:sampleRand:sampled:replayId:")] + NativeHandle Constructor (SentryId traceId, string publicKey, [NullAllowed] string releaseName, [NullAllowed] string environment, [NullAllowed] string transaction, [NullAllowed] string userSegment, [NullAllowed] string sampleRate, [NullAllowed] string sampleRand, [NullAllowed] string sampled, [NullAllowed] string replayId); + // -(instancetype _Nullable)initWithScope:(SentryScope * _Nonnull)scope options:(SentryOptions * _Nonnull)options; [Export ("initWithScope:options:")] NativeHandle Constructor (SentryScope scope, SentryOptions options); @@ -2177,13 +2230,25 @@ interface SentryTransactionContext [Export ("nameSource")] SentryTransactionNameSource NameSource { get; } + // @property (nonatomic, strong) NSNumber * _Nullable sampleRate; + [NullAllowed, Export ("sampleRate", ArgumentSemantic.Strong)] + NSNumber SampleRate { get; set; } + + // @property (nonatomic, strong) NSNumber * _Nullable sampleRand; + [NullAllowed, Export ("sampleRand", ArgumentSemantic.Strong)] + NSNumber SampleRand { get; set; } + // @property (nonatomic) SentrySampleDecision parentSampled; [Export ("parentSampled", ArgumentSemantic.Assign)] SentrySampleDecision ParentSampled { get; set; } - // @property (nonatomic, strong) NSNumber * _Nullable sampleRate; - [NullAllowed, Export ("sampleRate", ArgumentSemantic.Strong)] - NSNumber SampleRate { get; set; } + // @property (nonatomic, strong) NSNumber * _Nullable parentSampleRate; + [NullAllowed, Export ("parentSampleRate", ArgumentSemantic.Strong)] + NSNumber ParentSampleRate { get; set; } + + // @property (nonatomic, strong) NSNumber * _Nullable parentSampleRand; + [NullAllowed, Export ("parentSampleRand", ArgumentSemantic.Strong)] + NSNumber ParentSampleRand { get; set; } // @property (assign, nonatomic) BOOL forNextAppLaunch; [Export ("forNextAppLaunch")] @@ -2193,13 +2258,21 @@ interface SentryTransactionContext [Export ("initWithName:operation:")] NativeHandle Constructor (string name, string operation); - // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name operation:(NSString * _Nonnull)operation sampled:(SentrySampleDecision)sampled; + // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name operation:(NSString * _Nonnull)operation sampled:(SentrySampleDecision)sampled __attribute__((deprecated("Use initWithName:operation:sampled:sampleRate:sampleRand instead"))); [Export ("initWithName:operation:sampled:")] NativeHandle Constructor (string name, string operation, SentrySampleDecision sampled); - // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name operation:(NSString * _Nonnull)operation traceId:(SentryId * _Nonnull)traceId spanId:(SentrySpanId * _Nonnull)spanId parentSpanId:(SentrySpanId * _Nullable)parentSpanId parentSampled:(SentrySampleDecision)parentSampled; + // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name operation:(NSString * _Nonnull)operation sampled:(SentrySampleDecision)sampled sampleRate:(NSNumber * _Nullable)sampleRate sampleRand:(NSNumber * _Nullable)sampleRand; + [Export ("initWithName:operation:sampled:sampleRate:sampleRand:")] + NativeHandle Constructor (string name, string operation, SentrySampleDecision sampled, [NullAllowed] NSNumber sampleRate, [NullAllowed] NSNumber sampleRand); + + // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name operation:(NSString * _Nonnull)operation traceId:(SentryId * _Nonnull)traceId spanId:(SentrySpanId * _Nonnull)spanId parentSpanId:(SentrySpanId * _Nullable)parentSpanId parentSampled:(SentrySampleDecision)parentSampled __attribute__((deprecated("Use initWithName:operation:traceId:spanId:parentSpanId:parentSampled:parentSampleRate:parentSampleRand instead"))); [Export ("initWithName:operation:traceId:spanId:parentSpanId:parentSampled:")] NativeHandle Constructor (string name, string operation, SentryId traceId, SentrySpanId spanId, [NullAllowed] SentrySpanId parentSpanId, SentrySampleDecision parentSampled); + + // -(instancetype _Nonnull)initWithName:(NSString * _Nonnull)name operation:(NSString * _Nonnull)operation traceId:(SentryId * _Nonnull)traceId spanId:(SentrySpanId * _Nonnull)spanId parentSpanId:(SentrySpanId * _Nullable)parentSpanId parentSampled:(SentrySampleDecision)parentSampled parentSampleRate:(NSNumber * _Nullable)parentSampleRate parentSampleRand:(NSNumber * _Nullable)parentSampleRand; + [Export ("initWithName:operation:traceId:spanId:parentSpanId:parentSampled:parentSampleRate:parentSampleRand:")] + NativeHandle Constructor (string name, string operation, SentryId traceId, SentrySpanId spanId, [NullAllowed] SentrySpanId parentSpanId, SentrySampleDecision parentSampled, [NullAllowed] NSNumber parentSampleRate, [NullAllowed] NSNumber parentSampleRand); } // @interface SentryUser : NSObject @@ -2353,6 +2426,11 @@ interface PrivateSentrySDKOnly [Export ("getSdkVersionString")] string SdkVersionString { get; } + // +(void)addSdkPackage:(NSString * _Nonnull)name version:(NSString * _Nonnull)version; + [Static] + [Export ("addSdkPackage:version:")] + void AddSdkPackage (string name, string version); + // +(NSDictionary * _Nonnull)getExtraContext; [Static] [Export ("getExtraContext")] @@ -2419,6 +2497,16 @@ interface PrivateSentrySDKOnly [Export ("setCurrentScreen:")] void SetCurrentScreen (string screenName); + // +(UIView * _Nonnull)sessionReplayMaskingOverlay:(id _Nonnull)options; + [Static] + [Export ("sessionReplayMaskingOverlay:")] + UIView SessionReplayMaskingOverlay (SentryRedactOptions options); + + // +(void)configureSessionReplayWith:(id _Nullable)breadcrumbConverter screenshotProvider:(id _Nullable)screenshotProvider; + [Static] + [Export ("configureSessionReplayWith:screenshotProvider:")] + void ConfigureSessionReplayWith ([NullAllowed] SentryReplayBreadcrumbConverter breadcrumbConverter, [NullAllowed] SentryViewScreenshotProvider screenshotProvider); + // +(void)captureReplay; [Static] [Export ("captureReplay")] @@ -2439,10 +2527,25 @@ interface PrivateSentrySDKOnly [Export ("addReplayRedactClasses:")] void AddReplayRedactClasses (Class[] classes); + // +(void)setIgnoreContainerClass:(Class _Nonnull)containerClass; + [Static] + [Export ("setIgnoreContainerClass:")] + void SetIgnoreContainerClass (Class containerClass); + + // +(void)setRedactContainerClass:(Class _Nonnull)containerClass; + [Static] + [Export ("setRedactContainerClass:")] + void SetRedactContainerClass (Class containerClass); + + // +(void)setReplayTags:(NSDictionary * _Nonnull)tags; + [Static] + [Export ("setReplayTags:")] + void SetReplayTags (NSDictionary tags); + // +(NSDictionary * _Nullable)appStartMeasurementWithSpans; [Static] [NullAllowed, Export ("appStartMeasurementWithSpans")] - NSDictionary AppStartMeasurementWithSpans(); + NSDictionary AppStartMeasurementWithSpans { get; } // +(SentryUser * _Nonnull)userWithDictionary:(NSDictionary * _Nonnull)dictionary; [Static] @@ -2454,32 +2557,3 @@ interface PrivateSentrySDKOnly [Export ("breadcrumbWithDictionary:")] SentryBreadcrumb BreadcrumbWithDictionary (NSDictionary dictionary); } - -// @interface SentryId : NSObject -[BaseType (typeof(NSObject), Name = "_TtC6Sentry8SentryId")] -[Internal] -interface SentryId -{ - // @property (nonatomic, strong, class) SentryId * _Nonnull empty; - [Static] - [Export ("empty", ArgumentSemantic.Strong)] - SentryId Empty { get; set; } - - // @property (readonly, copy, nonatomic) NSString * _Nonnull sentryIdString; - [Export ("sentryIdString")] - string SentryIdString { get; } - - // -(instancetype _Nonnull)initWithUuid:(NSUUID * _Nonnull)uuid __attribute__((objc_designated_initializer)); - [Export ("initWithUuid:")] - [DesignatedInitializer] - NativeHandle Constructor (NSUuid uuid); - - // -(instancetype _Nonnull)initWithUUIDString:(NSString * _Nonnull)uuidString __attribute__((objc_designated_initializer)); - [Export ("initWithUUIDString:")] - [DesignatedInitializer] - NativeHandle Constructor (string uuidString); - - // @property (readonly, nonatomic) NSUInteger hash; - [Export ("hash")] - nuint Hash { get; } -} diff --git a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj index 5d175c990b..0409fadeba 100644 --- a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj +++ b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj @@ -33,7 +33,9 @@ + + diff --git a/src/Sentry.Bindings.Cocoa/StructsAndEnums.cs b/src/Sentry.Bindings.Cocoa/StructsAndEnums.cs index 7fb471de9c..9735003cd1 100644 --- a/src/Sentry.Bindings.Cocoa/StructsAndEnums.cs +++ b/src/Sentry.Bindings.Cocoa/StructsAndEnums.cs @@ -66,25 +66,3 @@ internal enum SentrySpanStatus : ulong OutOfRange, DataLoss } - -[Native] -internal enum SentryLevel : ulong -{ - None = 0, - Debug = 1, - Info = 2, - Warning = 3, - Error = 4, - Fatal = 5 -} - -[Native] -internal enum SentryTransactionNameSource : long -{ - Custom = 0, - Url = 1, - Route = 2, - View = 3, - Component = 4, - Task = 5 -} diff --git a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs new file mode 100644 index 0000000000..61fa7556a0 --- /dev/null +++ b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs @@ -0,0 +1,405 @@ +/* + * This file defines iOS API contracts for the members we need from Sentry-Swift.h + * Note that we are **not** using Objective Sharpie to generate contracts (instead they're maintained manually). + */ +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Sentry.CocoaSdk; + +[BaseType(typeof(NSObject), Name = "_TtC6Sentry14SentryFeedback")] +[DisableDefaultCtor] // Marks the default constructor as unavailable +[Internal] +interface SentryFeedback +{ + [Export("name", ArgumentSemantic.Copy)] + string Name { get; set; } + + [Export("email", ArgumentSemantic.Copy)] + string Email { get; set; } + + [Export("message", ArgumentSemantic.Copy)] + string Message { get; set; } + + [Export("source")] + SentryFeedbackSource Source { get; set; } + + [Export("eventId", ArgumentSemantic.Strong)] + SentryId EventId { get; } + + [Export("associatedEventId", ArgumentSemantic.Strong)] + SentryId AssociatedEventId { get; set; } + + [Export("initWithMessage:name:email:source:associatedEventId:attachments:")] + [DesignatedInitializer] + IntPtr Constructor(string message, [NullAllowed] string name, [NullAllowed] string email, SentryFeedbackSource source, [NullAllowed] SentryId associatedEventId, [NullAllowed] NSData[] attachments); +} + +// @interface SentryId : NSObject +[BaseType (typeof(NSObject), Name = "_TtC6Sentry8SentryId")] +[Internal] +interface SentryId +{ + // @property (nonatomic, strong, class) SentryId * _Nonnull empty; + [Static] + [Export ("empty", ArgumentSemantic.Strong)] + SentryId Empty { get; set; } + + // @property (readonly, copy, nonatomic) NSString * _Nonnull sentryIdString; + [Export ("sentryIdString")] + string SentryIdString { get; } + + // -(instancetype _Nonnull)initWithUuid:(NSUUID * _Nonnull)uuid __attribute__((objc_designated_initializer)); + [Export ("initWithUuid:")] + [DesignatedInitializer] + NativeHandle Constructor (NSUuid uuid); + + // -(instancetype _Nonnull)initWithUUIDString:(NSString * _Nonnull)uuidString __attribute__((objc_designated_initializer)); + [Export ("initWithUUIDString:")] + [DesignatedInitializer] + NativeHandle Constructor (string uuidString); + + // @property (readonly, nonatomic) NSUInteger hash; + [Export ("hash")] + nuint Hash { get; } +} + +// @interface SentrySessionReplayIntegration : SentryBaseIntegration +[BaseType (typeof(NSObject))] +[Internal] +interface SentrySessionReplayIntegration +{ + // -(instancetype _Nonnull)initForManualUse:(SentryOptions * _Nonnull)options; + [Export ("initForManualUse:")] + NativeHandle Constructor (SentryOptions options); + // -(BOOL)captureReplay; + [Export ("captureReplay")] + bool CaptureReplay(); + // -(void)configureReplayWith:(id _Nullable)breadcrumbConverter screenshotProvider:(id _Nullable)screenshotProvider; + [Export ("configureReplayWith:screenshotProvider:")] + void ConfigureReplayWith ([NullAllowed] SentryReplayBreadcrumbConverter breadcrumbConverter, [NullAllowed] SentryViewScreenshotProvider screenshotProvider); + // -(void)pause; + [Export ("pause")] + void Pause (); + // -(void)resume; + [Export ("resume")] + void Resume (); + // -(void)stop; + [Export ("stop")] + void Stop (); + // -(void)start; + [Export ("start")] + void Start (); + // +(id _Nonnull)createBreadcrumbwithTimestamp:(NSDate * _Nonnull)timestamp category:(NSString * _Nonnull)category message:(NSString * _Nullable)message level:(enum SentryLevel)level data:(NSDictionary * _Nullable)data; + [Static] + [Export ("createBreadcrumbwithTimestamp:category:message:level:data:")] + SentryRRWebEvent CreateBreadcrumbwithTimestamp (NSDate timestamp, string category, [NullAllowed] string message, SentryLevel level, [NullAllowed] NSDictionary data); + // +(id _Nonnull)createNetworkBreadcrumbWithTimestamp:(NSDate * _Nonnull)timestamp endTimestamp:(NSDate * _Nonnull)endTimestamp operation:(NSString * _Nonnull)operation description:(NSString * _Nonnull)description data:(NSDictionary * _Nonnull)data; + [Static] + [Export ("createNetworkBreadcrumbWithTimestamp:endTimestamp:operation:description:data:")] + SentryRRWebEvent CreateNetworkBreadcrumbWithTimestamp (NSDate timestamp, NSDate endTimestamp, string operation, string description, NSDictionary data); + // +(id _Nonnull)createDefaultBreadcrumbConverter; + [Static] + [Export ("createDefaultBreadcrumbConverter")] + SentryReplayBreadcrumbConverter CreateDefaultBreadcrumbConverter(); +} + +[Protocol(Name = "_TtP6Sentry19SentryRedactOptions_")] +[Model] +[BaseType(typeof(NSObject))] +[Internal] +internal interface SentryRedactOptions +{ + [Abstract] + [Export("maskAllText")] + bool MaskAllText { get; } + + [Abstract] + [Export("maskAllImages")] + bool MaskAllImages { get; } + + [Abstract] + [Export("maskedViewClasses", ArgumentSemantic.Copy)] + Class[] MaskedViewClasses { get; } + + [Abstract] + [Export("unmaskedViewClasses", ArgumentSemantic.Copy)] + Class[] UnmaskedViewClasses { get; } +} + +// @protocol SentryReplayBreadcrumbConverter +[Protocol (Name = "_TtP6Sentry31SentryReplayBreadcrumbConverter_")] +[BaseType (typeof(NSObject), Name = "_TtP6Sentry31SentryReplayBreadcrumbConverter_")] +[Model] +[Internal] +interface SentryReplayBreadcrumbConverter +{ + // @required -(id _Nullable)convertFrom:(SentryBreadcrumb * _Nonnull)breadcrumb __attribute__((warn_unused_result(""))); + [Abstract] + [Export ("convertFrom:")] + [return: NullAllowed] + SentryRRWebEvent ConvertFrom (SentryBreadcrumb breadcrumb); +} + +// @interface SentryReplayOptions : NSObject +[BaseType (typeof(NSObject), Name = "_TtC6Sentry19SentryReplayOptions")] +[Internal] +interface SentryReplayOptions //: ISentryRedactOptions +{ + // @property (nonatomic) float sessionSampleRate; + [Export ("sessionSampleRate")] + float SessionSampleRate { get; set; } + // @property (nonatomic) float onErrorSampleRate; + [Export ("onErrorSampleRate")] + float OnErrorSampleRate { get; set; } + // @property (nonatomic) BOOL maskAllText; + [Export ("maskAllText")] + bool MaskAllText { get; set; } + // @property (nonatomic) BOOL maskAllImages; + [Export ("maskAllImages")] + bool MaskAllImages { get; set; } + // @property (nonatomic) enum SentryReplayQuality quality; + [Export ("quality", ArgumentSemantic.Assign)] + SentryReplayQuality Quality { get; set; } + /* + // @property (copy, nonatomic) NSArray * _Nonnull maskedViewClasses; + //[Export ("maskedViewClasses", ArgumentSemantic.Copy)] + //Class[] MaskedViewClasses { get; set; } + // @property (copy, nonatomic) NSArray * _Nonnull unmaskedViewClasses; + //[Export ("unmaskedViewClasses", ArgumentSemantic.Copy)] + //Class[] UnmaskedViewClasses { get; set; } + // @property (readonly, nonatomic) NSInteger replayBitRate; + [Export ("replayBitRate")] + nint ReplayBitRate { get; } + // @property (readonly, nonatomic) float sizeScale; + [Export ("sizeScale")] + float SizeScale { get; } + // @property (nonatomic) NSUInteger frameRate; + [Export ("frameRate")] + nuint FrameRate { get; set; } + // @property (readonly, nonatomic) NSTimeInterval errorReplayDuration; + [Export ("errorReplayDuration")] + double ErrorReplayDuration { get; } + // @property (readonly, nonatomic) NSTimeInterval sessionSegmentDuration; + [Export ("sessionSegmentDuration")] + double SessionSegmentDuration { get; } + // @property (readonly, nonatomic) NSTimeInterval maximumDuration; + [Export ("maximumDuration")] + double MaximumDuration { get; } + // -(instancetype _Nonnull)initWithSessionSampleRate:(float)sessionSampleRate onErrorSampleRate:(float)onErrorSampleRate maskAllText:(BOOL)maskAllText maskAllImages:(BOOL)maskAllImages __attribute__((objc_designated_initializer)); + [Export ("initWithSessionSampleRate:onErrorSampleRate:maskAllText:maskAllImages:")] + [DesignatedInitializer] + NativeHandle Constructor (float sessionSampleRate, float onErrorSampleRate, bool maskAllText, bool maskAllImages); + // -(instancetype _Nonnull)initWithDictionary:(NSDictionary * _Nonnull)dictionary; + [Export ("initWithDictionary:")] + NativeHandle Constructor (NSDictionary dictionary); + */ +} + +// @interface SentryRRWebEvent : NSObject +[BaseType (typeof(NSObject), Name = "_TtC6Sentry16SentryRRWebEvent")] +[Protocol] +[Model] +[DisableDefaultCtor] +[Internal] +interface SentryRRWebEvent : SentrySerializable +{ + // @property (readonly, nonatomic) enum SentryRRWebEventType type; + [Export ("type")] + SentryRRWebEventType Type { get; } + // @property (readonly, copy, nonatomic) NSDate * _Nonnull timestamp; + [Export ("timestamp", ArgumentSemantic.Copy)] + NSDate Timestamp { get; } + // @property (readonly, copy, nonatomic) NSDictionary * _Nullable data; + [NullAllowed, Export ("data", ArgumentSemantic.Copy)] + NSDictionary Data { get; } + // -(instancetype _Nonnull)initWithType:(enum SentryRRWebEventType)type timestamp:(NSDate * _Nonnull)timestamp data:(NSDictionary * _Nullable)data __attribute__((objc_designated_initializer)); + [Export ("initWithType:timestamp:data:")] + [DesignatedInitializer] + NativeHandle Constructor (SentryRRWebEventType type, NSDate timestamp, [NullAllowed] NSDictionary data); + // -(NSDictionary * _Nonnull)serialize __attribute__((warn_unused_result(""))); + [Export ("serialize")] + new NSDictionary Serialize(); +} + +[BaseType(typeof(NSObject), Name = "_TtC6Sentry31SentryUserFeedbackConfiguration")] +[DisableDefaultCtor] +[Internal] +interface SentryUserFeedbackConfiguration +{ + [Export("animations")] + bool Animations { get; set; } + + [NullAllowed, Export("configureWidget", ArgumentSemantic.Copy)] + Action ConfigureWidget { get; set; } + + [Export("widgetConfig", ArgumentSemantic.Strong)] + SentryUserFeedbackWidgetConfiguration WidgetConfig { get; set; } + + [Export("useShakeGesture")] + bool UseShakeGesture { get; set; } + + [Export("showFormForScreenshots")] + bool ShowFormForScreenshots { get; set; } + + // [NullAllowed, Export("configureForm", ArgumentSemantic.Copy)] + // Action ConfigureForm { get; set; } + + // [Export("formConfig", ArgumentSemantic.Strong)] + // SentryUserFeedbackFormConfiguration FormConfig { get; set; } + + [NullAllowed, Export("tags", ArgumentSemantic.Copy)] + NSDictionary Tags { get; set; } + + [NullAllowed, Export("onFormOpen", ArgumentSemantic.Copy)] + Action OnFormOpen { get; set; } + + [NullAllowed, Export("onFormClose", ArgumentSemantic.Copy)] + Action OnFormClose { get; set; } + + [NullAllowed, Export("onSubmitSuccess", ArgumentSemantic.Copy)] + Action> OnSubmitSuccess { get; set; } + + [NullAllowed, Export("onSubmitError", ArgumentSemantic.Copy)] + Action OnSubmitError { get; set; } + + // [NullAllowed, Export("configureTheme", ArgumentSemantic.Copy)] + // Action ConfigureTheme { get; set; } + // + // [Export("theme", ArgumentSemantic.Strong)] + // SentryUserFeedbackThemeConfiguration Theme { get; set; } + // + // [NullAllowed, Export("configureDarkTheme", ArgumentSemantic.Copy)] + // Action ConfigureDarkTheme { get; set; } + // + // [Export("darkTheme", ArgumentSemantic.Strong)] + // SentryUserFeedbackThemeConfiguration DarkTheme { get; set; } + + [Export("textEffectiveHeightCenter")] + nfloat TextEffectiveHeightCenter { get; set; } + + [Export("scaleFactor")] + nfloat ScaleFactor { get; set; } + + [Export("calculateScaleFactor")] + nfloat CalculateScaleFactor(); + + [Export("paddingScaleFactor")] + nfloat PaddingScaleFactor { get; set; } + + [Export("calculatePaddingScaleFactor")] + nfloat CalculatePaddingScaleFactor(); + + [Export("recalculateScaleFactors")] + void RecalculateScaleFactors(); + + [Export("padding")] + nfloat Padding { get; } + + [Export("spacing")] + nfloat Spacing { get; } + + [Export("margin")] + nfloat Margin { get; } + + [Export("init")] + [DesignatedInitializer] + IntPtr Constructor(); +} + +// [BaseType(typeof(NSObject), Name = "_TtC6Sentry37SentryUserFeedbackThemeConfiguration")] +// [DisableDefaultCtor] +// [Internal] +// interface SentryUserFeedbackThemeConfiguration +// { +// [Export("backgroundColor", ArgumentSemantic.Strong)] +// UIColor BackgroundColor { get; set; } +// +// [Export("textColor", ArgumentSemantic.Strong)] +// UIColor TextColor { get; set; } +// +// [Export("buttonColor", ArgumentSemantic.Strong)] +// UIColor ButtonColor { get; set; } +// +// [Export("buttonTextColor", ArgumentSemantic.Strong)] +// UIColor ButtonTextColor { get; set; } +// +// [Export("init")] +// [DesignatedInitializer] +// IntPtr Constructor(); +// } + +[BaseType(typeof(NSObject), Name = "_TtC6Sentry37SentryUserFeedbackWidgetConfiguration")] +[DisableDefaultCtor] +[Internal] +interface SentryUserFeedbackWidgetConfiguration +{ + [Export("autoInject")] + bool AutoInject { get; set; } + + [Export("defaultLabelText", ArgumentSemantic.Copy)] + string DefaultLabelText { get; } + + [NullAllowed, Export("labelText", ArgumentSemantic.Copy)] + string LabelText { get; set; } + + [Export("showIcon")] + bool ShowIcon { get; set; } + + [NullAllowed, Export("widgetAccessibilityLabel", ArgumentSemantic.Copy)] + string WidgetAccessibilityLabel { get; set; } + + [Export("windowLevel")] + nfloat WindowLevel { get; set; } + + [Export("location")] + NSDirectionalRectEdge Location { get; set; } + + [Export("layoutUIOffset")] + UIOffset LayoutUIOffset { get; set; } + + [Export("init")] + [DesignatedInitializer] + IntPtr Constructor(); +} + + +// [BaseType(typeof(NSObject), Name = "_TtC6Sentry37SentryUserFeedbackFormConfiguration")] +// [DisableDefaultCtor] +// [Internal] +// interface SentryUserFeedbackFormConfiguration +// { +// [Export("title", ArgumentSemantic.Copy)] +// string Title { get; set; } +// +// [Export("subtitle", ArgumentSemantic.Copy)] +// string Subtitle { get; set; } +// +// [Export("submitButtonTitle", ArgumentSemantic.Copy)] +// string SubmitButtonTitle { get; set; } +// +// [Export("cancelButtonTitle", ArgumentSemantic.Copy)] +// string CancelButtonTitle { get; set; } +// +// [Export("thankYouMessage", ArgumentSemantic.Copy)] +// string ThankYouMessage { get; set; } +// +// [Export("init")] +// [DesignatedInitializer] +// IntPtr Constructor(); +// } + +// @protocol SentryViewScreenshotProvider +[Protocol (Name = "_TtP6Sentry28SentryViewScreenshotProvider_")] +[Model] +[BaseType (typeof(NSObject), Name = "_TtP6Sentry28SentryViewScreenshotProvider_")] +[Internal] +interface SentryViewScreenshotProvider +{ + // @required -(void)imageWithView:(UIView * _Nonnull)view onComplete:(void (^ _Nonnull)(UIImage * _Nonnull))onComplete; + [Abstract] + [Export ("imageWithView:onComplete:")] + void OnComplete (UIView view, Action onComplete); +} diff --git a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs new file mode 100644 index 0000000000..efd86efee2 --- /dev/null +++ b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs @@ -0,0 +1,65 @@ +/* + * This file defines iOS API contracts for enums we need from Sentry-Swift.h. + * Note that we are **not** using Objective Sharpie to generate these contracts (instead they're maintained manually). + */ +using System.Runtime.InteropServices; +using Foundation; +using ObjCRuntime; +using Sentry; + +namespace Sentry.CocoaSdk; + +[Native] +internal enum SentryFeedbackSource : long +{ + Unknown = 0, + User = 1, + System = 2, + Other = 3 +} + +[Native] +internal enum SentryLevel : ulong +{ + None = 0, + Debug = 1, + Info = 2, + Warning = 3, + Error = 4, + Fatal = 5 +} + +[Native] +internal enum SentryReplayQuality : long +{ + Low = 0, + Medium = 1, + High = 2 +} + +[Native] +internal enum SentryReplayType : long +{ + Session = 0, + Buffer = 1 +} + +[Native] +internal enum SentryRRWebEventType : long +{ + None = 0, + Touch = 3, + Meta = 4, + Custom = 5 +} + +[Native] +internal enum SentryTransactionNameSource : long +{ + Custom = 0, + Url = 1, + Route = 2, + View = 3, + Component = 4, + Task = 5 +} From 57761c093abbfb6b62d31d6b977afbcce51845b2 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 22 Aug 2025 22:37:04 +1200 Subject: [PATCH 13/56] Disable Profiler_MultipleProfiles_Works in CI (flaky) (#4383) * Disable Profiler_MultipleProfiles_Works in CI (flaky) #skip-changelog * Skip Profiler_WithZeroStartupTimeout_CapturesAfterStartingAsynchronously on targets as well * . --- .../SamplingTransactionProfilerTests.cs | 8 ++++++++ test/Sentry.Testing/TestEnvironment.cs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 1d1b1c7828..468ff45a62 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -117,6 +117,12 @@ private SampleProfile CaptureAndValidate(ITransactionProfilerFactory factory) [SkippableFact] public void Profiler_WithZeroStartupTimeout_CapturesAfterStartingAsynchronously() { + if (TestEnvironment.IsGitHubActions) + { + Skip.If(TestEnvironment.IsWinX64, "Flaky in CI on Windows X64."); + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Flaky in CI on Linux."); + } + using var factory = new SamplingTransactionProfilerFactory(_testSentryOptions, TimeSpan.Zero); var profiler = factory.Start(new TransactionTracer(Substitute.For(), "test", ""), CancellationToken.None); Assert.Null(profiler); @@ -146,6 +152,8 @@ private void Profiler_SingleProfile_Works(int startTimeoutSeconds) [InlineData(10)] public void Profiler_MultipleProfiles_Works(int startTimeoutSeconds) { + Skip.If(TestEnvironment.IsGitHubActions, "Flaky in CI"); + using var factory = new SamplingTransactionProfilerFactory(_testSentryOptions, TimeSpan.FromSeconds(startTimeoutSeconds)); // in the async startup case, we need to wait before collecting if (startTimeoutSeconds == 0) diff --git a/test/Sentry.Testing/TestEnvironment.cs b/test/Sentry.Testing/TestEnvironment.cs index a674533ae1..1c2763b439 100644 --- a/test/Sentry.Testing/TestEnvironment.cs +++ b/test/Sentry.Testing/TestEnvironment.cs @@ -15,4 +15,7 @@ public static bool IsGitHubActions return isGitHubActions?.Equals("true", StringComparison.OrdinalIgnoreCase) == true; } } + + public static bool IsWinX64 => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + && RuntimeInformation.OSArchitecture == Architecture.X64; } From e01ed72fadf0f627cd8d72ae73ab532127ca0d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:03:24 +0200 Subject: [PATCH 14/56] meta: sync packages of README and Bug-Report issue template (#4460) --- .github/ISSUE_TEMPLATE/bug_report.yml | 10 +++++++--- README.md | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5dfa58384b..37cff669b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -10,17 +10,21 @@ body: options: - Sentry - Sentry.AspNetCore + - Sentry.AspNetCore.Blazor.WebAssembly - Sentry.AspNetCore.Grpc - Sentry.AspNet + - Sentry.Azure.Functions.Worker - Sentry.DiagnosticSource - Sentry.EntityFramework - Sentry.Extensions.Logging - Sentry.Google.Cloud.Functions - - Sentry.Log4net + - Sentry.Hangfire + - Sentry.Log4Net - Sentry.Maui - - Sentry.Nlog + - Sentry.NLog + - Sentry.OpenTelemetry + - Sentry.Profiling - Sentry.Serilog - - Sentry.Tunnel - Other validations: required: true diff --git a/README.md b/README.md index da7fb8c6e8..4c020e8e31 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Sentry SDK for .NET | **Sentry.Azure.Functions.Worker** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Azure.Functions.Worker.svg)](https://www.nuget.org/packages/Sentry.Azure.Functions.Worker) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Azure.Functions.Worker.svg)](https://www.nuget.org/packages/Sentry.Azure.Functions.Worker) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Azure.Functions.Worker.svg)](https://www.nuget.org/packages/Sentry.Azure.Functions.Worker) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/azure-functions-worker/) | | **Sentry.DiagnosticSource** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.DiagnosticSource.svg)](https://www.nuget.org/packages/Sentry.DiagnosticSource) | [![NuGet](https://img.shields.io/nuget/v/Sentry.DiagnosticSource.svg)](https://www.nuget.org/packages/Sentry.DiagnosticSource) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.DiagnosticSource.svg)](https://www.nuget.org/packages/Sentry.DiagnosticSource) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/performance/instrumentation/automatic-instrumentation/#diagnosticsource-integration) | | **Sentry.EntityFramework** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.EntityFramework.svg)](https://www.nuget.org/packages/Sentry.EntityFramework) | [![NuGet](https://img.shields.io/nuget/v/Sentry.EntityFramework.svg)](https://www.nuget.org/packages/Sentry.EntityFramework) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.EntityFramework.svg)](https://www.nuget.org/packages/Sentry.EntityFramework) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/entityframework) | +| **Sentry.Extensions.Logging** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Extensions.Logging.svg)](https://www.nuget.org/packages/Sentry.Extensions.Logging) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Extensions.Logging.svg)](https://www.nuget.org/packages/Sentry.Extensions.Logging) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Extensions.Logging.svg)](https://www.nuget.org/packages/Sentry.Extensions.Logging) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/extensions-logging/) | | **Sentry.Google.Cloud.Functions** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/google-cloud-functions/) | | **Sentry.Hangfire** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Hangfire.svg)](https://www.nuget.org/packages/Sentry.Hangfire) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Profiling.svg)](https://www.nuget.org/packages/Sentry.Hangfire) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Hangfire.svg)](https://www.nuget.org/packages/Sentry.Hangfire) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/crons/hangfire/) | | **Sentry.Log4Net** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Log4Net.svg)](https://www.nuget.org/packages/Sentry.Log4Net) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Log4Net.svg)](https://www.nuget.org/packages/Sentry.Log4Net) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Log4Net.svg)](https://www.nuget.org/packages/Sentry.Log4Net) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/log4net) | From f7a83af5d60e603ab58ba8bf6be5f2f618256580 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Sat, 23 Aug 2025 08:15:52 +1200 Subject: [PATCH 15/56] fix: Handled is no longer set to false for blocking calls (#4458) The 'handled' property is now omitted from the mechanism for blocking call detection events, so they are not treated as unhandled crashes in Sentry UI. --- CHANGELOG.md | 1 + src/Sentry/Ben.BlockingDetector/BlockingMonitor.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed287feb39..578b58e5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) +- Blocking calls are no longer treated as unhandled crashes ([#4458](https://github.com/getsentry/sentry-dotnet/pull/4458)) ### Dependencies diff --git a/src/Sentry/Ben.BlockingDetector/BlockingMonitor.cs b/src/Sentry/Ben.BlockingDetector/BlockingMonitor.cs index 113a4e85ae..d91b5d2207 100644 --- a/src/Sentry/Ben.BlockingDetector/BlockingMonitor.cs +++ b/src/Sentry/Ben.BlockingDetector/BlockingMonitor.cs @@ -70,7 +70,6 @@ public void BlockingStart(DetectionSource detectionSource) Mechanism = new Mechanism { Type = "BlockingCallDetector", - Handled = false, Description = "Blocking calls can cause ThreadPool starvation.", Source = detectionSource.ToString() }, From 0a08754494b8fed2708f8b930419f1101134e0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 25 Aug 2025 04:11:54 +0200 Subject: [PATCH 16/56] feat(logs): rework method overloads of Structured Logger (#4451) --- CHANGELOG.md | 1 + .../Sentry.Samples.Console.Basic/Program.cs | 8 +- src/Sentry/SentryStructuredLogger.Format.cs | 156 ++++++++++++++++++ src/Sentry/SentryStructuredLogger.cs | 80 +-------- ...piApprovalTests.Run.DotNet8_0.verified.txt | 24 ++- ...piApprovalTests.Run.DotNet9_0.verified.txt | 24 ++- .../ApiApprovalTests.Run.Net4_8.verified.txt | 18 +- .../SentryStructuredLoggerTests.Format.cs | 143 ++++++++++++++++ .../SentryStructuredLoggerTests.cs | 126 +++++--------- 9 files changed, 391 insertions(+), 189 deletions(-) create mode 100644 src/Sentry/SentryStructuredLogger.Format.cs create mode 100644 test/Sentry.Tests/SentryStructuredLoggerTests.Format.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 578b58e5ae..3b47a5320e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Experimental _Structured Logs_: + - Redesign SDK Logger APIs to allow usage of `params` ([#4451](https://github.com/getsentry/sentry-dotnet/pull/4451)) - Shorten the `key` names of `Microsoft.Extensions.Logging` attributes ([#4450](https://github.com/getsentry/sentry-dotnet/pull/4450)) ### Fixes diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs index f1ae39b6ff..6b1815bf93 100644 --- a/samples/Sentry.Samples.Console.Basic/Program.cs +++ b/samples/Sentry.Samples.Console.Basic/Program.cs @@ -37,7 +37,7 @@ // This option tells Sentry to capture 100% of traces. You still need to start transactions and spans. options.TracesSampleRate = 1.0; - // This option enables Sentry Logs created via SentrySdk.Logger. + // This option enables Sentry Logs created via SentrySdk.Experimental.Logger. options.Experimental.EnableLogs = true; options.Experimental.SetBeforeSendLog(static log => { @@ -94,7 +94,8 @@ async Task SecondFunction() SentrySdk.CaptureException(exception); span.Finish(exception); - SentrySdk.Experimental.Logger.LogError("Error with message: {0}", [exception.Message], static log => log.SetAttribute("method", nameof(SecondFunction))); + SentrySdk.Experimental.Logger.LogError(static log => log.SetAttribute("method", nameof(SecondFunction)), + "Error with message: {0}", exception.Message); } span.Finish(); @@ -108,7 +109,8 @@ async Task ThirdFunction() // Simulate doing some work await Task.Delay(100); - SentrySdk.Experimental.Logger.LogFatal("Crash imminent!", [], static log => log.SetAttribute("suppress", true)); + SentrySdk.Experimental.Logger.LogFatal(static log => log.SetAttribute("suppress", true), + "Crash imminent!"); // This is an example of an unhandled exception. It will be captured automatically. throw new InvalidOperationException("Something happened that crashed the app!"); diff --git a/src/Sentry/SentryStructuredLogger.Format.cs b/src/Sentry/SentryStructuredLogger.Format.cs new file mode 100644 index 0000000000..4575b5e0d9 --- /dev/null +++ b/src/Sentry/SentryStructuredLogger.Format.cs @@ -0,0 +1,156 @@ +using Sentry.Infrastructure; + +namespace Sentry; + +public abstract partial class SentryStructuredLogger +{ + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogTrace(string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Trace, template, parameters, null); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogTrace(Action configureLog, string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Trace, template, parameters, configureLog); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogDebug(string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Debug, template, parameters, null); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogDebug(Action configureLog, string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Debug, template, parameters, configureLog); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogInfo(string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Info, template, parameters, null); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogInfo(Action configureLog, string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Info, template, parameters, configureLog); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogWarning(string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Warning, template, parameters, null); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogWarning(Action configureLog, string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Warning, template, parameters, configureLog); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogError(string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Error, template, parameters, null); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogError(Action configureLog, string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Error, template, parameters, configureLog); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogFatal(string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Fatal, template, parameters, null); + } + + /// + /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. + /// This API is experimental and it may change in the future. + /// + /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. + /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. + /// The arguments to the . See System.String.Format. + [Experimental(DiagnosticId.ExperimentalFeature)] + public void LogFatal(Action configureLog, string template, params object[] parameters) + { + CaptureLog(SentryLogLevel.Fatal, template, parameters, configureLog); + } +} diff --git a/src/Sentry/SentryStructuredLogger.cs b/src/Sentry/SentryStructuredLogger.cs index 19842bbc72..8a0dd9da1b 100644 --- a/src/Sentry/SentryStructuredLogger.cs +++ b/src/Sentry/SentryStructuredLogger.cs @@ -8,7 +8,7 @@ namespace Sentry; /// This API is experimental and it may change in the future. /// [Experimental(DiagnosticId.ExperimentalFeature)] -public abstract class SentryStructuredLogger +public abstract partial class SentryStructuredLogger { internal static SentryStructuredLogger Create(IHub hub, SentryOptions options, ISystemClock clock) => Create(hub, options, clock, 100, TimeSpan.FromSeconds(5)); @@ -45,82 +45,4 @@ private protected SentryStructuredLogger() /// Clears all buffers for this logger and causes any buffered logs to be sent by the underlying . /// protected internal abstract void Flush(); - - /// - /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. - /// This API is experimental and it may change in the future. - /// - /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. - /// The arguments to the . See System.String.Format. - /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. - [Experimental(DiagnosticId.ExperimentalFeature)] - public void LogTrace(string template, object[]? parameters = null, Action? configureLog = null) - { - CaptureLog(SentryLogLevel.Trace, template, parameters, configureLog); - } - - /// - /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. - /// This API is experimental and it may change in the future. - /// - /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. - /// The arguments to the . See System.String.Format. - /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. - [Experimental(DiagnosticId.ExperimentalFeature)] - public void LogDebug(string template, object[]? parameters = null, Action? configureLog = null) - { - CaptureLog(SentryLogLevel.Debug, template, parameters, configureLog); - } - - /// - /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. - /// This API is experimental and it may change in the future. - /// - /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. - /// The arguments to the . See System.String.Format. - /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. - [Experimental(DiagnosticId.ExperimentalFeature)] - public void LogInfo(string template, object[]? parameters = null, Action? configureLog = null) - { - CaptureLog(SentryLogLevel.Info, template, parameters, configureLog); - } - - /// - /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. - /// This API is experimental and it may change in the future. - /// - /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. - /// The arguments to the . See System.String.Format. - /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. - [Experimental(DiagnosticId.ExperimentalFeature)] - public void LogWarning(string template, object[]? parameters = null, Action? configureLog = null) - { - CaptureLog(SentryLogLevel.Warning, template, parameters, configureLog); - } - - /// - /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. - /// This API is experimental and it may change in the future. - /// - /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. - /// The arguments to the . See System.String.Format. - /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. - [Experimental(DiagnosticId.ExperimentalFeature)] - public void LogError(string template, object[]? parameters = null, Action? configureLog = null) - { - CaptureLog(SentryLogLevel.Error, template, parameters, configureLog); - } - - /// - /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled. - /// This API is experimental and it may change in the future. - /// - /// A formattable . When incompatible with the , the log will not be captured. See System.String.Format. - /// The arguments to the . See System.String.Format. - /// A delegate to set attributes on the . When the delegate throws an during invocation, the log will not be captured. - [Experimental(DiagnosticId.ExperimentalFeature)] - public void LogFatal(string template, object[]? parameters = null, Action? configureLog = null) - { - CaptureLog(SentryLogLevel.Fatal, template, parameters, configureLog); - } } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index b94a74dc34..07c413e828 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -993,17 +993,29 @@ namespace Sentry protected abstract void CaptureLog(Sentry.SentryLog log); protected abstract void Flush(); [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogDebug(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogDebug(string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogError(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogDebug(System.Action configureLog, string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogFatal(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogError(string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogInfo(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogError(System.Action configureLog, string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogTrace(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogFatal(string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogWarning(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogFatal(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogInfo(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogInfo(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogTrace(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogTrace(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogWarning(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogWarning(System.Action configureLog, string template, params object[] parameters) { } } public sealed class SentryThread : Sentry.ISentryJsonSerializable { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index b94a74dc34..07c413e828 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -993,17 +993,29 @@ namespace Sentry protected abstract void CaptureLog(Sentry.SentryLog log); protected abstract void Flush(); [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogDebug(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogDebug(string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogError(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogDebug(System.Action configureLog, string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogFatal(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogError(string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogInfo(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogError(System.Action configureLog, string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogTrace(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogFatal(string template, params object[] parameters) { } [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public void LogWarning(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogFatal(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogInfo(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogInfo(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogTrace(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogTrace(System.Action configureLog, string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogWarning(string template, params object[] parameters) { } + [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] + public void LogWarning(System.Action configureLog, string template, params object[] parameters) { } } public sealed class SentryThread : Sentry.ISentryJsonSerializable { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index aa006ffe82..2de7c68513 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -952,12 +952,18 @@ namespace Sentry { protected abstract void CaptureLog(Sentry.SentryLog log); protected abstract void Flush(); - public void LogDebug(string template, object[]? parameters = null, System.Action? configureLog = null) { } - public void LogError(string template, object[]? parameters = null, System.Action? configureLog = null) { } - public void LogFatal(string template, object[]? parameters = null, System.Action? configureLog = null) { } - public void LogInfo(string template, object[]? parameters = null, System.Action? configureLog = null) { } - public void LogTrace(string template, object[]? parameters = null, System.Action? configureLog = null) { } - public void LogWarning(string template, object[]? parameters = null, System.Action? configureLog = null) { } + public void LogDebug(string template, params object[] parameters) { } + public void LogDebug(System.Action configureLog, string template, params object[] parameters) { } + public void LogError(string template, params object[] parameters) { } + public void LogError(System.Action configureLog, string template, params object[] parameters) { } + public void LogFatal(string template, params object[] parameters) { } + public void LogFatal(System.Action configureLog, string template, params object[] parameters) { } + public void LogInfo(string template, params object[] parameters) { } + public void LogInfo(System.Action configureLog, string template, params object[] parameters) { } + public void LogTrace(string template, params object[] parameters) { } + public void LogTrace(System.Action configureLog, string template, params object[] parameters) { } + public void LogWarning(string template, params object[] parameters) { } + public void LogWarning(System.Action configureLog, string template, params object[] parameters) { } } public sealed class SentryThread : Sentry.ISentryJsonSerializable { diff --git a/test/Sentry.Tests/SentryStructuredLoggerTests.Format.cs b/test/Sentry.Tests/SentryStructuredLoggerTests.Format.cs new file mode 100644 index 0000000000..87894fed75 --- /dev/null +++ b/test/Sentry.Tests/SentryStructuredLoggerTests.Format.cs @@ -0,0 +1,143 @@ +#nullable enable + +namespace Sentry.Tests; + +public partial class SentryStructuredLoggerTests +{ + [Theory] + [InlineData(SentryLogLevel.Trace)] + [InlineData(SentryLogLevel.Debug)] + [InlineData(SentryLogLevel.Info)] + [InlineData(SentryLogLevel.Warning)] + [InlineData(SentryLogLevel.Error)] + [InlineData(SentryLogLevel.Fatal)] + public void Log_Enabled_CapturesEnvelope(SentryLogLevel level) + { + _fixture.Options.Experimental.EnableLogs = true; + var logger = _fixture.GetSut(); + + Envelope envelope = null!; + _fixture.Hub.CaptureEnvelope(Arg.Do(arg => envelope = arg)); + + logger.Log(level, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); + logger.Flush(); + + _fixture.Hub.Received(1).CaptureEnvelope(Arg.Any()); + _fixture.AssertEnvelopeWithoutAttributes(envelope, level); + } + + [Theory] + [InlineData(SentryLogLevel.Trace)] + [InlineData(SentryLogLevel.Debug)] + [InlineData(SentryLogLevel.Info)] + [InlineData(SentryLogLevel.Warning)] + [InlineData(SentryLogLevel.Error)] + [InlineData(SentryLogLevel.Fatal)] + public void Log_Disabled_DoesNotCaptureEnvelope(SentryLogLevel level) + { + _fixture.Options.Experimental.EnableLogs.Should().BeFalse(); + var logger = _fixture.GetSut(); + + logger.Log(level, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); + logger.Flush(); + + _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); + } + + [Theory] + [InlineData(SentryLogLevel.Trace)] + [InlineData(SentryLogLevel.Debug)] + [InlineData(SentryLogLevel.Info)] + [InlineData(SentryLogLevel.Warning)] + [InlineData(SentryLogLevel.Error)] + [InlineData(SentryLogLevel.Fatal)] + public void Log_ConfigureLog_Enabled_CapturesEnvelope(SentryLogLevel level) + { + _fixture.Options.Experimental.EnableLogs = true; + var logger = _fixture.GetSut(); + + Envelope envelope = null!; + _fixture.Hub.CaptureEnvelope(Arg.Do(arg => envelope = arg)); + + logger.Log(level, ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); + logger.Flush(); + + _fixture.Hub.Received(1).CaptureEnvelope(Arg.Any()); + _fixture.AssertEnvelope(envelope, level); + } + + [Theory] + [InlineData(SentryLogLevel.Trace)] + [InlineData(SentryLogLevel.Debug)] + [InlineData(SentryLogLevel.Info)] + [InlineData(SentryLogLevel.Warning)] + [InlineData(SentryLogLevel.Error)] + [InlineData(SentryLogLevel.Fatal)] + public void Log_ConfigureLog_Disabled_DoesNotCaptureEnvelope(SentryLogLevel level) + { + _fixture.Options.Experimental.EnableLogs.Should().BeFalse(); + var logger = _fixture.GetSut(); + + logger.Log(level, ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); + logger.Flush(); + + _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); + } +} + +file static class SentryStructuredLoggerExtensions +{ + public static void Log(this SentryStructuredLogger logger, SentryLogLevel level, string template, params object[] parameters) + { + switch (level) + { + case SentryLogLevel.Trace: + logger.LogTrace(template, parameters); + break; + case SentryLogLevel.Debug: + logger.LogDebug(template, parameters); + break; + case SentryLogLevel.Info: + logger.LogInfo(template, parameters); + break; + case SentryLogLevel.Warning: + logger.LogWarning(template, parameters); + break; + case SentryLogLevel.Error: + logger.LogError(template, parameters); + break; + case SentryLogLevel.Fatal: + logger.LogFatal(template, parameters); + break; + default: + throw new ArgumentOutOfRangeException(nameof(level), level, null); + } + } + + public static void Log(this SentryStructuredLogger logger, SentryLogLevel level, Action configureLog, string template, params object[] parameters) + { + switch (level) + { + case SentryLogLevel.Trace: + logger.LogTrace(configureLog, template, parameters); + break; + case SentryLogLevel.Debug: + logger.LogDebug(configureLog, template, parameters); + break; + case SentryLogLevel.Info: + logger.LogInfo(configureLog, template, parameters); + break; + case SentryLogLevel.Warning: + logger.LogWarning(configureLog, template, parameters); + break; + case SentryLogLevel.Error: + logger.LogError(configureLog, template, parameters); + break; + case SentryLogLevel.Fatal: + logger.LogFatal(configureLog, template, parameters); + break; + default: + throw new ArgumentOutOfRangeException(nameof(level), level, null); + } + } +} diff --git a/test/Sentry.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Tests/SentryStructuredLoggerTests.cs index b64b258e69..b0a2e6e3a5 100644 --- a/test/Sentry.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Tests/SentryStructuredLoggerTests.cs @@ -5,7 +5,7 @@ namespace Sentry.Tests; /// /// /// -public class SentryStructuredLoggerTests : IDisposable +public partial class SentryStructuredLoggerTests : IDisposable { internal sealed class Fixture { @@ -28,6 +28,11 @@ public Fixture() var traceHeader = new SentryTraceHeader(TraceId, ParentSpanId.Value, null); Hub.GetTraceHeader().Returns(traceHeader); + + ExpectedAttributes = new Dictionary(1) + { + { "attribute-key", "attribute-value" }, + }; } public InMemoryDiagnosticLogger DiagnosticLogger { get; } @@ -39,6 +44,8 @@ public Fixture() public SentryId TraceId { get; private set; } public SpanId? ParentSpanId { get; private set; } + public Dictionary ExpectedAttributes { get; } + public void WithoutTraceHeader() { Hub.GetTraceHeader().Returns((SentryTraceHeader?)null); @@ -85,45 +92,6 @@ public void Create_Disabled_CachedDisabledInstance() instance.Should().BeSameAs(other); } - [Theory] - [InlineData(SentryLogLevel.Trace)] - [InlineData(SentryLogLevel.Debug)] - [InlineData(SentryLogLevel.Info)] - [InlineData(SentryLogLevel.Warning)] - [InlineData(SentryLogLevel.Error)] - [InlineData(SentryLogLevel.Fatal)] - public void Log_Enabled_CapturesEnvelope(SentryLogLevel level) - { - _fixture.Options.Experimental.EnableLogs = true; - var logger = _fixture.GetSut(); - - Envelope envelope = null!; - _fixture.Hub.CaptureEnvelope(Arg.Do(arg => envelope = arg)); - - logger.Log(level, "Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); - logger.Flush(); - - _fixture.Hub.Received(1).CaptureEnvelope(Arg.Any()); - envelope.AssertEnvelope(_fixture, level); - } - - [Theory] - [InlineData(SentryLogLevel.Trace)] - [InlineData(SentryLogLevel.Debug)] - [InlineData(SentryLogLevel.Info)] - [InlineData(SentryLogLevel.Warning)] - [InlineData(SentryLogLevel.Error)] - [InlineData(SentryLogLevel.Fatal)] - public void Log_Disabled_DoesNotCaptureEnvelope(SentryLogLevel level) - { - _fixture.Options.Experimental.EnableLogs.Should().BeFalse(); - var logger = _fixture.GetSut(); - - logger.Log(level, "Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); - - _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); - } - [Fact] public void Log_WithoutTraceHeader_CapturesEnvelope() { @@ -134,11 +102,11 @@ public void Log_WithoutTraceHeader_CapturesEnvelope() Envelope envelope = null!; _fixture.Hub.CaptureEnvelope(Arg.Do(arg => envelope = arg)); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); + logger.LogTrace(ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); logger.Flush(); _fixture.Hub.Received(1).CaptureEnvelope(Arg.Any()); - envelope.AssertEnvelope(_fixture, SentryLogLevel.Trace); + _fixture.AssertEnvelope(envelope, SentryLogLevel.Trace); } [Fact] @@ -156,12 +124,12 @@ public void Log_WithBeforeSendLog_InvokesCallback() }); var logger = _fixture.GetSut(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); + logger.LogTrace(ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); logger.Flush(); _fixture.Hub.Received(1).CaptureEnvelope(Arg.Any()); invocations.Should().Be(1); - configuredLog.AssertLog(_fixture, SentryLogLevel.Trace); + _fixture.AssertLog(configuredLog, SentryLogLevel.Trace); } [Fact] @@ -177,7 +145,7 @@ public void Log_WhenBeforeSendLogReturnsNull_DoesNotCaptureEnvelope() }); var logger = _fixture.GetSut(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); + logger.LogTrace(ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); invocations.Should().Be(1); @@ -189,7 +157,7 @@ public void Log_InvalidFormat_DoesNotCaptureEnvelope() _fixture.Options.Experimental.EnableLogs = true; var logger = _fixture.GetSut(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}, {4}", ["string", true, 1, 2.2]); + logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}, {4}", "string", true, 1, 2.2); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); var entry = _fixture.DiagnosticLogger.Dequeue(); @@ -205,7 +173,7 @@ public void Log_InvalidConfigureLog_DoesNotCaptureEnvelope() _fixture.Options.Experimental.EnableLogs = true; var logger = _fixture.GetSut(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], static (SentryLog log) => throw new InvalidOperationException()); + logger.LogTrace(static (SentryLog log) => throw new InvalidOperationException(), "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); var entry = _fixture.DiagnosticLogger.Dequeue(); @@ -222,7 +190,7 @@ public void Log_InvalidBeforeSendLog_DoesNotCaptureEnvelope() _fixture.Options.Experimental.SetBeforeSendLog(static (SentryLog log) => throw new InvalidOperationException()); var logger = _fixture.GetSut(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2]); + logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); var entry = _fixture.DiagnosticLogger.Dequeue(); @@ -245,13 +213,13 @@ public void Flush_AfterLog_CapturesEnvelope() _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); envelope.Should().BeNull(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); + logger.LogTrace(ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); envelope.Should().BeNull(); logger.Flush(); _fixture.Hub.Received(1).CaptureEnvelope(Arg.Any()); - envelope.AssertEnvelope(_fixture, SentryLogLevel.Trace); + _fixture.AssertEnvelope(envelope, SentryLogLevel.Trace); } [Fact] @@ -262,7 +230,7 @@ public void Dispose_BeforeLog_DoesNotCaptureEnvelope() var defaultLogger = logger.Should().BeOfType().Which; defaultLogger.Dispose(); - logger.LogTrace("Template string with arguments: {0}, {1}, {2}, {3}", ["string", true, 1, 2.2], ConfigureLog); + logger.LogTrace(ConfigureLog, "Template string with arguments: {0}, {1}, {2}, {3}", "string", true, 1, 2.2); _fixture.Hub.Received(0).CaptureEnvelope(Arg.Any()); var entry = _fixture.DiagnosticLogger.Dequeue(); @@ -278,15 +246,15 @@ private static void ConfigureLog(SentryLog log) } } -file static class AssertionExtensions +internal static class AssertionExtensions { - public static void AssertEnvelope(this Envelope envelope, SentryStructuredLoggerTests.Fixture fixture, SentryLogLevel level) + public static void AssertEnvelope(this SentryStructuredLoggerTests.Fixture fixture, Envelope envelope, SentryLogLevel level) { envelope.Header.Should().ContainSingle().Which.Key.Should().Be("sdk"); var item = envelope.Items.Should().ContainSingle().Which; var log = item.Payload.Should().BeOfType().Which.Source.Should().BeOfType().Which; - AssertLog(log, fixture, level); + AssertLog(fixture, log, level); Assert.Collection(item.Header, element => Assert.Equal(CreateHeader("type", "log"), element), @@ -294,14 +262,20 @@ public static void AssertEnvelope(this Envelope envelope, SentryStructuredLogger element => Assert.Equal(CreateHeader("content_type", "application/vnd.sentry.items.log+json"), element)); } - public static void AssertLog(this StructuredLog log, SentryStructuredLoggerTests.Fixture fixture, SentryLogLevel level) + public static void AssertEnvelopeWithoutAttributes(this SentryStructuredLoggerTests.Fixture fixture, Envelope envelope, SentryLogLevel level) + { + fixture.ExpectedAttributes.Clear(); + AssertEnvelope(fixture, envelope, level); + } + + public static void AssertLog(this SentryStructuredLoggerTests.Fixture fixture, StructuredLog log, SentryLogLevel level) { var items = log.Items; items.Length.Should().Be(1); - AssertLog(items[0], fixture, level); + AssertLog(fixture, items[0], level); } - public static void AssertLog(this SentryLog log, SentryStructuredLoggerTests.Fixture fixture, SentryLogLevel level) + public static void AssertLog(this SentryStructuredLoggerTests.Fixture fixture, SentryLog log, SentryLogLevel level) { log.Timestamp.Should().Be(fixture.Clock.GetUtcNow()); log.TraceId.Should().Be(fixture.TraceId); @@ -310,8 +284,12 @@ public static void AssertLog(this SentryLog log, SentryStructuredLoggerTests.Fix log.Template.Should().Be("Template string with arguments: {0}, {1}, {2}, {3}"); log.Parameters.Should().BeEquivalentTo(new KeyValuePair[] { new("0", "string"), new("1", true), new("2", 1), new("3", 2.2), }); log.ParentSpanId.Should().Be(fixture.ParentSpanId); - log.TryGetAttribute("attribute-key", out string? value).Should().BeTrue(); - value.Should().Be("attribute-value"); + + foreach (var expectedAttribute in fixture.ExpectedAttributes) + { + log.TryGetAttribute(expectedAttribute.Key, out string? value).Should().BeTrue(); + value.Should().Be(expectedAttribute.Value); + } } private static KeyValuePair CreateHeader(string name, object? value) @@ -319,33 +297,3 @@ public static void AssertLog(this SentryLog log, SentryStructuredLoggerTests.Fix return new KeyValuePair(name, value); } } - -file static class SentryStructuredLoggerExtensions -{ - public static void Log(this SentryStructuredLogger logger, SentryLogLevel level, string template, object[]? parameters, Action? configureLog) - { - switch (level) - { - case SentryLogLevel.Trace: - logger.LogTrace(template, parameters, configureLog); - break; - case SentryLogLevel.Debug: - logger.LogDebug(template, parameters, configureLog); - break; - case SentryLogLevel.Info: - logger.LogInfo(template, parameters, configureLog); - break; - case SentryLogLevel.Warning: - logger.LogWarning(template, parameters, configureLog); - break; - case SentryLogLevel.Error: - logger.LogError(template, parameters, configureLog); - break; - case SentryLogLevel.Fatal: - logger.LogFatal(template, parameters, configureLog); - break; - default: - throw new ArgumentOutOfRangeException(nameof(level), level, null); - } - } -} From fe3a33bfd17f7cc81402e4ea7536389c1c1cecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 25 Aug 2025 10:27:57 +0200 Subject: [PATCH 17/56] test: disabled win-x64 test on win-arm64 too (#4464) --- .../SamplingTransactionProfilerTests.cs | 2 +- test/Sentry.Testing/TestEnvironment.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 468ff45a62..f51986b4a0 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -119,7 +119,7 @@ public void Profiler_WithZeroStartupTimeout_CapturesAfterStartingAsynchronously( { if (TestEnvironment.IsGitHubActions) { - Skip.If(TestEnvironment.IsWinX64, "Flaky in CI on Windows X64."); + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Flaky in CI on Windows."); Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Flaky in CI on Linux."); } diff --git a/test/Sentry.Testing/TestEnvironment.cs b/test/Sentry.Testing/TestEnvironment.cs index 1c2763b439..e70c571fd8 100644 --- a/test/Sentry.Testing/TestEnvironment.cs +++ b/test/Sentry.Testing/TestEnvironment.cs @@ -1,5 +1,3 @@ -using System; - namespace Sentry.Testing; public static class TestEnvironment @@ -15,7 +13,4 @@ public static bool IsGitHubActions return isGitHubActions?.Equals("true", StringComparison.OrdinalIgnoreCase) == true; } } - - public static bool IsWinX64 => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - && RuntimeInformation.OSArchitecture == Architecture.X64; } From b3e91abf79e0dc51a36da0012680db796dd2c593 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 25 Aug 2025 21:50:45 +1200 Subject: [PATCH 18/56] chore: Added DebugLoggerLevel to DebugLogger delegate (#4457) Resolves #4382 - https://github.com/getsentry/sentry-dotnet/issues/4382 #skip-changelog --- .../AndroidAssemblyReaderFactory.cs | 14 ++++---- .../ArchiveUtils.cs | 2 +- .../DebugLogger.cs | 3 +- .../DebugLoggerLevel.cs | 32 +++++++++++++++++++ .../V1/AndroidAssemblyDirectoryReaderV1.cs | 4 +-- .../V1/AndroidAssemblyStoreReaderV1.cs | 18 +++++------ .../V2/AndroidAssemblyDirectoryReaderV2.cs | 22 ++++++------- .../V2/AndroidAssemblyStoreReaderV2.cs | 18 +++++------ .../V2/AssemblyStoreExplorer.cs | 2 +- .../V2/StoreReaderV2.cs | 6 ++-- .../Platforms/Android/AndroidHelpers.cs | 2 +- .../AndroidAssemblyReaderTests.cs | 2 +- ...piApprovalTests.Run.DotNet8_0.verified.txt | 10 +++++- ...piApprovalTests.Run.DotNet9_0.verified.txt | 10 +++++- 14 files changed, 97 insertions(+), 48 deletions(-) create mode 100644 src/Sentry.Android.AssemblyReader/DebugLoggerLevel.cs diff --git a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs index 62670899a9..e111a46f71 100644 --- a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs +++ b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs @@ -17,29 +17,29 @@ public static class AndroidAssemblyReaderFactory /// The reader public static IAndroidAssemblyReader Open(string apkPath, IList supportedAbis, DebugLogger? logger = null) { - logger?.Invoke("Opening APK: {0}", apkPath); + logger?.Invoke(DebugLoggerLevel.Debug, "Opening APK: {0}", apkPath); #if NET9_0 - logger?.Invoke("Reading files using V2 APK layout."); + logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V2 APK layout."); if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) { - logger?.Invoke("APK uses AssemblyStore V2"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V2"); return readerV2; } - logger?.Invoke("APK doesn't use AssemblyStore"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger); #else - logger?.Invoke("Reading files using V1 APK layout."); + logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V1 APK layout."); var zipArchive = ZipFile.OpenRead(apkPath); if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null) { - logger?.Invoke("APK uses AssemblyStore V1"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V1"); return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger); } - logger?.Invoke("APK doesn't use AssemblyStore"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger); #endif } diff --git a/src/Sentry.Android.AssemblyReader/ArchiveUtils.cs b/src/Sentry.Android.AssemblyReader/ArchiveUtils.cs index 0613eed1f9..d3b2f53e46 100644 --- a/src/Sentry.Android.AssemblyReader/ArchiveUtils.cs +++ b/src/Sentry.Android.AssemblyReader/ArchiveUtils.cs @@ -43,7 +43,7 @@ internal static MemoryStream Extract(this ZipArchiveEntry zipEntry) Debug.Assert(inputStream.Position == payloadOffset); var inputLength = (int)(inputStream.Length - payloadOffset); - logger?.Invoke("Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength); + logger?.Invoke(DebugLoggerLevel.Debug, "Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength); var outputStream = new MemoryStream(decompressedLength); diff --git a/src/Sentry.Android.AssemblyReader/DebugLogger.cs b/src/Sentry.Android.AssemblyReader/DebugLogger.cs index aff0db80ca..d1835c55fa 100644 --- a/src/Sentry.Android.AssemblyReader/DebugLogger.cs +++ b/src/Sentry.Android.AssemblyReader/DebugLogger.cs @@ -3,6 +3,7 @@ namespace Sentry.Android.AssemblyReader; /// /// Writes a log message for debugging. /// +/// The debug log level. /// The message string to write. /// Arguments for the formatted message string. -public delegate void DebugLogger(string message, params object?[] args); +public delegate void DebugLogger(DebugLoggerLevel level, string message, params object?[] args); diff --git a/src/Sentry.Android.AssemblyReader/DebugLoggerLevel.cs b/src/Sentry.Android.AssemblyReader/DebugLoggerLevel.cs new file mode 100644 index 0000000000..886bafdc57 --- /dev/null +++ b/src/Sentry.Android.AssemblyReader/DebugLoggerLevel.cs @@ -0,0 +1,32 @@ +namespace Sentry.Android.AssemblyReader; + +/// +/// Represents the level of debug logging. +/// +public enum DebugLoggerLevel : short +{ + /// + /// Debug level logging. + /// + Debug, + + /// + /// Information level logging. + /// + Info, + + /// + /// Warning level logging. + /// + Warning, + + /// + /// Error level logging. + /// + Error, + + /// + /// Fatal level logging. + /// + Fatal +} diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs index 30314a5546..7b5e1c1a7e 100644 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs +++ b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs @@ -18,11 +18,11 @@ public AndroidAssemblyDirectoryReaderV1(ZipArchive zip, IList supportedA var zipEntry = FindAssembly(name); if (zipEntry is null) { - Logger?.Invoke("Couldn't find assembly {0} in the APK", name); + Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name); return null; } - Logger?.Invoke("Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName); + Logger?.Invoke(DebugLoggerLevel.Debug, "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 = zipEntry.Extract(); diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs index 3073cc2095..60c947b23e 100644 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs +++ b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs @@ -16,16 +16,16 @@ public AndroidAssemblyStoreReaderV1(ZipArchive zip, IList supportedAbis, var assembly = TryFindAssembly(name); if (assembly is null) { - Logger?.Invoke("Couldn't find assembly {0} in the APK AssemblyStore", name); + Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name); return null; } - Logger?.Invoke("Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch); + Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch); var stream = assembly.GetImage(); if (stream is null) { - Logger?.Invoke("Couldn't access assembly {0} image stream", name); + Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name); return null; } @@ -119,7 +119,7 @@ private void ProcessStores() assembly.Hash64 = he.Hash; if (assembly.RuntimeIndex != he.MappingIndex) { - _logger?.Invoke( + _logger?.Invoke(DebugLoggerLevel.Debug, $"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})"); } @@ -127,18 +127,18 @@ private void ProcessStores() { if (string.IsNullOrEmpty(assembly.Name)) { - _logger?.Invoke( + _logger?.Invoke(DebugLoggerLevel.Debug, $"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?.Invoke( + _logger?.Invoke(DebugLoggerLevel.Debug, $"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?.Invoke( + _logger?.Invoke(DebugLoggerLevel.Debug, $"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}'"); } } @@ -176,7 +176,7 @@ void ProcessIndex(List index, string bitness, { if (!Stores.TryGetValue(he.StoreId, out var storeList)) { - _logger?.Invoke($"store with id {he.StoreId} not part of the set"); + _logger?.Invoke(DebugLoggerLevel.Debug, $"store with id {he.StoreId} not part of the set"); continue; } @@ -184,7 +184,7 @@ void ProcessIndex(List index, string bitness, { if (he.LocalStoreIndex >= (uint)store.Assemblies.Count) { - _logger?.Invoke( + _logger?.Invoke(DebugLoggerLevel.Debug, $"{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; } diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs index 7c92c70a26..b250640d1f 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs @@ -12,7 +12,7 @@ public AndroidAssemblyDirectoryReaderV2(string apkPath, IList supportedA Logger = logger; foreach (var abi in supportedAbis) { - logger?.Invoke("Adding {0} to supported architectures for Directory Reader", abi); + logger?.Invoke(DebugLoggerLevel.Debug, "Adding {0} to supported architectures for Directory Reader", abi); SupportedArchitectures.Add(abi.AbiToDeviceArchitecture()); } _archiveAssemblyHelper = new ArchiveAssemblyHelper(apkPath, logger, supportedAbis); @@ -26,21 +26,21 @@ public AndroidAssemblyDirectoryReaderV2(string apkPath, IList supportedA var stream = File.OpenRead(name); return new PEReader(stream); } - Logger?.Invoke("File {0} does not exist in the APK", name); + Logger?.Invoke(DebugLoggerLevel.Debug, "File {0} does not exist in the APK", name); foreach (var arch in SupportedArchitectures) { if (_archiveAssemblyHelper.ReadEntry($"assemblies/{name}", arch) is not { } memStream) { - Logger?.Invoke("Couldn't find entry {0} in the APK for the {1} architecture", name, arch); + Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find entry {0} in the APK for the {1} architecture", name, arch); continue; } - Logger?.Invoke("Resolved assembly {0} in the APK", name); + Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK", name); return ArchiveUtils.CreatePEReader(name, memStream, Logger); } - Logger?.Invoke("Couldn't find assembly {0} in the APK", name); + Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name); return null; } @@ -96,11 +96,11 @@ public ArchiveAssemblyHelper(string archivePath, DebugLogger? logger, IList $"Entry '{path}' does not contain the 'payload' section", _ => $"Unknown ELF payload section error for entry '{path}': {error}" }; - _logger?.Invoke(message); + _logger?.Invoke(DebugLoggerLevel.Debug, message); } else { - _logger?.Invoke($"Extracted content from ELF image '{path}'"); + _logger?.Invoke(DebugLoggerLevel.Debug, $"Extracted content from ELF image '{path}'"); } if (elfPayloadOffset == 0) @@ -142,14 +142,14 @@ public ArchiveAssemblyHelper(string archivePath, DebugLogger? logger, IList supportedAbis, D var (explorers, errorMessage) = AssemblyStoreExplorer.Open(inputFile, logger); if (explorers is null) { - logger?.Invoke("Unable to read store information for {0}: {1}", inputFile, errorMessage); + logger?.Invoke(DebugLoggerLevel.Debug, "Unable to read store information for {0}: {1}", inputFile, errorMessage); // Check for assembly stores in any device specific APKs foreach (var supportedAbi in supportedAbis) @@ -27,7 +27,7 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D var splitFilePath = inputFile.GetArchivePathForAbi(supportedAbi, logger); if (!File.Exists(splitFilePath)) { - logger?.Invoke("No split config detected at: '{0}'", splitFilePath); + logger?.Invoke(DebugLoggerLevel.Debug, "No split config detected at: '{0}'", splitFilePath); continue; } (explorers, errorMessage) = AssemblyStoreExplorer.Open(splitFilePath, logger); @@ -37,7 +37,7 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D } else { - logger?.Invoke("Unable to read store information for {0}: {1}", splitFilePath, errorMessage); + logger?.Invoke(DebugLoggerLevel.Debug, "Unable to read store information for {0}: {1}", splitFilePath, errorMessage); } } } @@ -62,7 +62,7 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D if (supportedExplorers.Count == 0) { - logger?.Invoke("Could not find V2 AssemblyStoreExplorer for the supported ABIs: {0}", string.Join(", ", supportedAbis)); + logger?.Invoke(DebugLoggerLevel.Debug, "Could not find V2 AssemblyStoreExplorer for the supported ABIs: {0}", string.Join(", ", supportedAbis)); reader = null; return false; } @@ -76,17 +76,17 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D var explorerAssembly = TryFindAssembly(name); if (explorerAssembly is null) { - _logger?.Invoke("Couldn't find assembly {0} in the APK AssemblyStore", name); + _logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name); return null; } var (explorer, storeItem) = explorerAssembly; - _logger?.Invoke("Resolved assembly {0} in the APK {1} AssemblyStore", name, storeItem.TargetArch); + _logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, storeItem.TargetArch); var stream = explorer.ReadImageData(storeItem, false); if (stream is null) { - _logger?.Invoke("Couldn't access assembly {0} image stream", name); + _logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name); return null; } @@ -127,12 +127,12 @@ private bool FindBestAssembly(string name, out ExplorerStoreItem? explorerAssemb { if (explorer.AssembliesByName?.TryGetValue(name, out var assembly) is true) { - _logger?.Invoke("Found best assembly {0} in APK AssemblyStore for target arch {1}", name, explorer.TargetArch); + _logger?.Invoke(DebugLoggerLevel.Debug, "Found best assembly {0} in APK AssemblyStore for target arch {1}", name, explorer.TargetArch); explorerAssembly = new(explorer, assembly); return true; } } - _logger?.Invoke("No best assembly for {0} in APK AssemblyStore", name); + _logger?.Invoke(DebugLoggerLevel.Warning, "No best assembly for {0} in APK AssemblyStore", name); explorerAssembly = null; return false; } diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs index 424109bcee..0ab9c04e2e 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs @@ -33,7 +33,7 @@ private AssemblyStoreExplorer(Stream storeStream, string path, DebugLogger? logg { foreach (var item in Assemblies) { - logger?.Invoke("Assembly {0} indexed from AssemblyStore {1}", item.Name, path); + logger?.Invoke(DebugLoggerLevel.Debug, "Assembly {0} indexed from AssemblyStore {1}", item.Name, path); dict.Add(item.Name, item); } } diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs index 6a40e05bc0..97bac95694 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs @@ -106,7 +106,7 @@ protected override bool IsSupported() ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section", _ => $"Unknown ELF payload section error for store '{StorePath}': {error}" }; - Logger?.Invoke(message); + Logger?.Invoke(DebugLoggerLevel.Debug, message); // Was originally: // ``` // } else if (elfOffset >= 0) { @@ -122,14 +122,14 @@ protected override bool IsSupported() if (magic != Utils.AssemblyStoreMagic) { - Logger?.Invoke("Store '{0}' has invalid header magic number.", StorePath); + Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has invalid header magic number.", StorePath); return false; } uint version = reader.ReadUInt32(); if (!supportedVersions.Contains(version)) { - Logger?.Invoke("Store '{0}' has unsupported version 0x{1:x}", StorePath, version); + Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has unsupported version 0x{1:x}", StorePath, version); return false; } diff --git a/src/Sentry/Platforms/Android/AndroidHelpers.cs b/src/Sentry/Platforms/Android/AndroidHelpers.cs index c33d404e08..41fa2c8b1f 100644 --- a/src/Sentry/Platforms/Android/AndroidHelpers.cs +++ b/src/Sentry/Platforms/Android/AndroidHelpers.cs @@ -44,7 +44,7 @@ public static IList GetSupportedAbis() { var supportedAbis = GetSupportedAbis(); return AndroidAssemblyReaderFactory.Open(apkPath, supportedAbis, - logger: (message, args) => logger?.Log(SentryLevel.Debug, message, args: args)); + logger: (level, message, args) => logger?.Log((SentryLevel)level, message, args: args)); } catch (Exception ex) { diff --git a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs index 4a9985f827..24344f2edd 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs +++ b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs @@ -39,7 +39,7 @@ private IAndroidAssemblyReader GetSut(bool isAot, bool isAssemblyStore, bool isC // Note: This needs to match the RID used when publishing the test APK string[] supportedAbis = { "x86_64" }; return AndroidAssemblyReaderFactory.Open(apkPath, supportedAbis, - logger: (message, args) => _output.WriteLine(message, args)); + logger: (_, message, args) => _output.WriteLine(message, args)); #endif } diff --git a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 42c0fac236..67bbca78b3 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -4,7 +4,15 @@ { public static Sentry.Android.AssemblyReader.IAndroidAssemblyReader Open(string apkPath, System.Collections.Generic.IList supportedAbis, Sentry.Android.AssemblyReader.DebugLogger? logger = null) { } } - public delegate void DebugLogger(string message, params object?[] args); + public delegate void DebugLogger(Sentry.Android.AssemblyReader.DebugLoggerLevel level, string message, params object?[] args); + public enum DebugLoggerLevel : short + { + Debug = 0, + Info = 1, + Warning = 2, + Error = 3, + Fatal = 4, + } public interface IAndroidAssemblyReader : System.IDisposable { System.Reflection.PortableExecutable.PEReader? TryReadAssembly(string name); diff --git a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 42c0fac236..67bbca78b3 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -4,7 +4,15 @@ { public static Sentry.Android.AssemblyReader.IAndroidAssemblyReader Open(string apkPath, System.Collections.Generic.IList supportedAbis, Sentry.Android.AssemblyReader.DebugLogger? logger = null) { } } - public delegate void DebugLogger(string message, params object?[] args); + public delegate void DebugLogger(Sentry.Android.AssemblyReader.DebugLoggerLevel level, string message, params object?[] args); + public enum DebugLoggerLevel : short + { + Debug = 0, + Info = 1, + Warning = 2, + Error = 3, + Fatal = 4, + } public interface IAndroidAssemblyReader : System.IDisposable { System.Reflection.PortableExecutable.PEReader? TryReadAssembly(string name); From bd9256ddcfc08bd2270f2901f77023da2dfaa593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:02:51 +1200 Subject: [PATCH 19/56] build(deps): bump github/codeql-action from 3.29.8 to 3.29.11 (#4469) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.8 to 3.29.11. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/76621b61decf072c1cee8dd1ce2d2a82d33c17ed...3c3833e0f8c1c83d449a7478aa59c036a9165498) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5a73102373..7f61fcb1cc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: uses: ./.github/actions/environment - name: Initialize CodeQL - uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # pin@v2 + uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # pin@v2 with: languages: csharp @@ -49,6 +49,6 @@ jobs: run: dotnet build Sentry-CI-CodeQL.slnf --no-restore --nologo - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # pin@v2 + uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # pin@v2 with: category: '/language:csharp' From b7da47e62643517959227459c6d14e1fa9b6be17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:03:28 +1200 Subject: [PATCH 20/56] build(deps): bump actions/create-github-app-token from 2.1.0 to 2.1.1 (#4470) Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/0f859bf9e69e887678d5bbfbee594437cb440ffe...a8d616148505b5069dccd32f177bb87d7f39123b) --- updated-dependencies: - dependency-name: actions/create-github-app-token dependency-version: 2.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b6ad85765..0ba422e99d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From a542f0c2629068542ca98149cb6aec619329a6af Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 27 Aug 2025 21:35:24 +1200 Subject: [PATCH 21/56] perf: Avoid shallow copies of DSC (#4453) Resolves #4381 - https://github.com/getsentry/sentry-dotnet/issues/4381 #skip-changelog --- src/Sentry.AspNet/HttpContextExtensions.cs | 2 +- .../SentryTracingMiddleware.cs | 2 +- src/Sentry/DynamicSamplingContext.cs | 39 +++----- src/Sentry/Internal/Hub.cs | 6 +- .../DynamicSamplingContextTests.cs | 2 +- test/Sentry.Tests/HubTests.cs | 97 +++++-------------- 6 files changed, 44 insertions(+), 104 deletions(-) diff --git a/src/Sentry.AspNet/HttpContextExtensions.cs b/src/Sentry.AspNet/HttpContextExtensions.cs index d3d9e46245..54aa715379 100644 --- a/src/Sentry.AspNet/HttpContextExtensions.cs +++ b/src/Sentry.AspNet/HttpContextExtensions.cs @@ -113,7 +113,7 @@ public static ITransactionTracer StartSentryTransaction(this HttpContext httpCon // See: // https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#freezing-dynamic-sampling-context // https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#unified-propagation-mechanism - dynamicSamplingContext = DynamicSamplingContext.Empty; + dynamicSamplingContext = DynamicSamplingContext.Empty(); } var transaction = SentrySdk.StartTransaction(transactionContext, customSamplingContext, dynamicSamplingContext); diff --git a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs index 96f5e5a95b..e2bdab58d7 100644 --- a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs +++ b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs @@ -75,7 +75,7 @@ public SentryTracingMiddleware( // See: // https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#freezing-dynamic-sampling-context // https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#unified-propagation-mechanism - dynamicSamplingContext = DynamicSamplingContext.Empty; + dynamicSamplingContext = DynamicSamplingContext.Empty(); } var transaction = _getHub().StartTransaction(transactionContext, customSamplingContext, dynamicSamplingContext); diff --git a/src/Sentry/DynamicSamplingContext.cs b/src/Sentry/DynamicSamplingContext.cs index e81550dadc..f39d7fa5a7 100644 --- a/src/Sentry/DynamicSamplingContext.cs +++ b/src/Sentry/DynamicSamplingContext.cs @@ -9,16 +9,18 @@ namespace Sentry; /// internal class DynamicSamplingContext { - public IReadOnlyDictionary Items { get; } + private readonly Dictionary _items; + + public IReadOnlyDictionary Items => _items; public bool IsEmpty => Items.Count == 0; - private DynamicSamplingContext(IReadOnlyDictionary items) => Items = items; + private DynamicSamplingContext(Dictionary items) => _items = items; /// /// Gets an empty that can be used to "freeze" the DSC on a transaction. /// - public static readonly DynamicSamplingContext Empty = new(new Dictionary().AsReadOnly()); + public static DynamicSamplingContext Empty() => new(new Dictionary()); private DynamicSamplingContext(SentryId traceId, string publicKey, @@ -93,39 +95,24 @@ private DynamicSamplingContext(SentryId traceId, items.Add("replay_id", replayId.ToString()); } - Items = items; + _items = items; } public BaggageHeader ToBaggageHeader() => BaggageHeader.Create(Items, useSentryPrefix: true); - public DynamicSamplingContext WithSampleRate(double sampleRate) + public void SetSampleRate(double sampleRate) { - if (Items.TryGetValue("sample_rate", out var dscSampleRate)) - { - if (double.TryParse(dscSampleRate, NumberStyles.Float, CultureInfo.InvariantCulture, out var rate)) - { - if (Math.Abs(rate - sampleRate) > double.Epsilon) - { - var items = Items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - items["sample_rate"] = sampleRate.ToString(CultureInfo.InvariantCulture); - return new DynamicSamplingContext(items); - } - } - } - - return this; + _items["sample_rate"] = sampleRate.ToString(CultureInfo.InvariantCulture); } - public DynamicSamplingContext WithReplayId(IReplaySession? replaySession) + internal DynamicSamplingContext Clone() => new(new Dictionary(_items)); + + public void SetReplayId(IReplaySession? replaySession) { - if (replaySession?.ActiveReplayId is not { } replayId || replayId == SentryId.Empty) + if (replaySession?.ActiveReplayId is { } replayId && replayId != SentryId.Empty) { - return this; + _items["replay_id"] = replayId.ToString(); } - - var items = Items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - items["replay_id"] = replayId.ToString(); - return new DynamicSamplingContext(items); } public static DynamicSamplingContext? CreateFromBaggageHeader(BaggageHeader baggage, IReplaySession? replaySession) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 1c0d6ebf8b..ec92169cd3 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -193,7 +193,7 @@ internal ITransactionTracer StartTransaction( isSampled = SampleRandHelper.IsSampled(sampleRand, samplerSampleRate); // Ensure the actual sampleRate is set on the provided DSC (if any) when the TracesSampler reached a sampling decision - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(samplerSampleRate); + dynamicSamplingContext?.SetSampleRate(samplerSampleRate); } } @@ -207,12 +207,12 @@ internal ITransactionTracer StartTransaction( if (context.IsSampled is null && _options.TracesSampleRate is not null) { // Ensure the actual sampleRate is set on the provided DSC (if any) when not IsSampled upstream but the TracesSampleRate reached a sampling decision - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(_options.TracesSampleRate.Value); + dynamicSamplingContext?.SetSampleRate(_options.TracesSampleRate.Value); } } // Make sure there is a replayId (if available) on the provided DSC (if any). - dynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession); + dynamicSamplingContext?.SetReplayId(_replaySession); if (isSampled is false) { diff --git a/test/Sentry.Tests/DynamicSamplingContextTests.cs b/test/Sentry.Tests/DynamicSamplingContextTests.cs index 4c9f4ff3ae..bcc9329e24 100644 --- a/test/Sentry.Tests/DynamicSamplingContextTests.cs +++ b/test/Sentry.Tests/DynamicSamplingContextTests.cs @@ -25,7 +25,7 @@ public Fixture() [Fact] public void EmptyContext() { - var dsc = DynamicSamplingContext.Empty; + var dsc = DynamicSamplingContext.Empty(); Assert.True(dsc.IsEmpty); } diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 1f7e94ee13..2df8394504 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -743,6 +743,7 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSamp {"sentry-sample_rate", "0.5"}, {"sentry-sample_rand", "0.1234"}, }).CreateDynamicSamplingContext(); + var originalDsc = dsc?.Clone(); _fixture.Options.TracesSampler = _ => tracesSampler; _fixture.Options.TracesSampleRate = tracesSampleRate; @@ -759,12 +760,11 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSamp transactionTracer.SampleRate.Should().Be(expectedSampleRate); if (expectedDscOverwritten) { - transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(expectedSampleRate)); } else { - transactionTracer.DynamicSamplingContext.Should().BeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc); } } else @@ -773,12 +773,11 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSamp unsampledTransaction.SampleRate.Should().Be(expectedSampleRate); if (expectedDscOverwritten) { - unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); - unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(expectedSampleRate)); } else { - unsampledTransaction.DynamicSamplingContext.Should().BeSameAs(dsc); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc); } } } @@ -814,12 +813,12 @@ public void StartTransaction_DynamicSamplingContextWithReplayId_UsesActiveReplay transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.DynamicSamplingContext.Should().NotBeNull(); - var expectedDsc = dsc.ReplaceSampleRate(_fixture.Options.TracesSampleRate.Value); + var expectedDsc = dsc.CloneWithSampleRate(_fixture.Options.TracesSampleRate.Value); if (replaySessionIsActive) { // We overwrite the replay_id when we have an active replay session // Otherwise we propagate whatever was in the baggage header - expectedDsc = expectedDsc.ReplaceReplayId(_fixture.ReplaySession); + expectedDsc = expectedDsc.CloneWithReplayId(_fixture.ReplaySession); } transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(expectedDsc); } @@ -881,7 +880,7 @@ public void StartTransaction_DynamicSamplingContextWithoutSampleRand_SampleRandN var hub = _fixture.GetSut(); // Act - var transaction = hub.StartTransaction(transactionContext, new Dictionary(), DynamicSamplingContext.Empty); + var transaction = hub.StartTransaction(transactionContext, new Dictionary(), DynamicSamplingContext.Empty()); // Assert var transactionTracer = transaction.Should().BeOfType().Subject; @@ -905,6 +904,7 @@ public void StartTransaction_DynamicSamplingContextWithSampleRand_InheritsSample {"sentry-sample_rate", "0.5"}, // Required in the baggage header, but ignored by sampling logic {"sentry-sample_rand", "0.1234"} }).CreateDynamicSamplingContext(dummyReplaySession); + var originalDsc = dsc.Clone(); _fixture.Options.TracesSampleRate = 0.4; var hub = _fixture.GetSut(); @@ -917,8 +917,7 @@ public void StartTransaction_DynamicSamplingContextWithSampleRand_InheritsSample transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(0.4); transactionTracer.SampleRand.Should().Be(0.1234); - transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(0.4)); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(0.4)); } [Theory] @@ -937,6 +936,7 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool {"sentry-sample_rate", "0.5"}, {"sentry-sample_rand", "0.1234"} }).CreateDynamicSamplingContext(_fixture.ReplaySession); + var originalDsc = dsc.Clone(); _fixture.Options.TracesSampler = _ => sampleRate; var hub = _fixture.GetSut(); @@ -951,8 +951,7 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(sampleRate); transactionTracer.SampleRand.Should().Be(0.1234); - transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(sampleRate)); } else { @@ -960,8 +959,7 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool unsampledTransaction.IsSampled.Should().BeFalse(); unsampledTransaction.SampleRate.Should().Be(sampleRate); unsampledTransaction.SampleRand.Should().Be(0.1234); - unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); - unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(sampleRate)); } } @@ -981,6 +979,7 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo {"sentry-sample_rate", "0.5"}, // Static sampling ignores this and uses options.TracesSampleRate instead {"sentry-sample_rand", "0.1234"} }).CreateDynamicSamplingContext(dummyReplaySession); + var originalDsc = dsc.Clone(); _fixture.Options.TracesSampleRate = sampleRate; var hub = _fixture.GetSut(); @@ -995,8 +994,7 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(sampleRate); transactionTracer.SampleRand.Should().Be(0.1234); - transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(sampleRate)); } else { @@ -1004,8 +1002,7 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo unsampledTransaction.IsSampled.Should().BeFalse(); unsampledTransaction.SampleRate.Should().Be(sampleRate); unsampledTransaction.SampleRand.Should().Be(0.1234); - unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); - unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(originalDsc.CloneWithSampleRate(sampleRate)); } } @@ -1255,7 +1252,7 @@ public void GetBaggage_SpanActive_ReturnsBaggageFromSpan(bool isSampled) baggage.Members.Should().Equal([ new KeyValuePair("sentry-trace_id", "43365712692146d08ee11a729dfbcaca"), new KeyValuePair("sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"), - new KeyValuePair("sentry-sample_rate", isSampled ? "1" : "0.0"), + new KeyValuePair("sentry-sample_rate", isSampled ? "1" : "0"), new KeyValuePair("sentry-sample_rand", sampleRand!.Value.ToString(CultureInfo.InvariantCulture)), ]); } @@ -2409,63 +2406,19 @@ internal partial class HubTestsJsonContext : JsonSerializerContext #nullable enable file static class DynamicSamplingContextExtensions { - public static DynamicSamplingContext ReplaceSampleRate(this DynamicSamplingContext? dsc, double sampleRate) + public static DynamicSamplingContext CloneWithSampleRate(this DynamicSamplingContext? dsc, double sampleRate) { Assert.NotNull(dsc); - - var value = sampleRate.ToString(CultureInfo.InvariantCulture); - return dsc.Replace("sentry-sample_rate", value); + var newDsc = dsc.Clone(); + newDsc.SetSampleRate(sampleRate); + return newDsc; } - public static DynamicSamplingContext ReplaceReplayId(this DynamicSamplingContext? dsc, IReplaySession replaySession) + public static DynamicSamplingContext CloneWithReplayId(this DynamicSamplingContext? dsc, IReplaySession replaySession) { Assert.NotNull(dsc); - - if (!replaySession.ActiveReplayId.HasValue) - { - throw new InvalidOperationException($"No {nameof(IReplaySession.ActiveReplayId)}."); - } - - var value = replaySession.ActiveReplayId.Value.ToString(); - return dsc.Replace("sentry-replay_id", value); - } - - private static DynamicSamplingContext Replace(this DynamicSamplingContext dsc, string key, string newValue) - { - var items = dsc.ToBaggageHeader().Members.ToList(); - var index = items.FindSingleIndex(key); - - var oldValue = items[index].Value; - if (oldValue == newValue) - { - throw new InvalidOperationException($"{key} already is {oldValue}."); - } - - items[index] = new KeyValuePair(key, newValue); - - var baggage = BaggageHeader.Create(items); - var dynamicSamplingContext = DynamicSamplingContext.CreateFromBaggageHeader(baggage, null); - if (dynamicSamplingContext is null) - { - throw new InvalidOperationException($"Invalid {nameof(BaggageHeader)}: {baggage}"); - } - - return dynamicSamplingContext; - } - - private static int FindSingleIndex(this List> items, string key) - { - var index = items.FindIndex(item => item.Key == key); - if (index == -1) - { - throw new InvalidOperationException($"{key} not found."); - } - - if (items.FindLastIndex(item => item.Key == key) != index) - { - throw new InvalidOperationException($"Duplicate {key} found."); - } - - return index; + var newDsc = dsc.Clone(); + newDsc.SetReplayId(replaySession); + return newDsc; } } From e4f49213738c4702c8816bcf8a7253ccbc81e083 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 28 Aug 2025 11:51:37 +0200 Subject: [PATCH 22/56] alpine: add gpg for codecov/codecov-action 5.5 (#4476) --- .github/alpine/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/alpine/Dockerfile b/.github/alpine/Dockerfile index d2d4fd8520..6718812e71 100644 --- a/.github/alpine/Dockerfile +++ b/.github/alpine/Dockerfile @@ -5,7 +5,7 @@ FROM ${BASE} RUN apk update # common -RUN apk add bash build-base cmake curl git icu lsb-release-minimal nodejs npm sudo tar wget +RUN apk add bash build-base cmake curl git gpg icu lsb-release-minimal nodejs npm sudo tar wget # sentry-native RUN apk add curl-dev docker-cli libunwind-dev libunwind-static linux-headers openssl-dev zlib-dev xz-dev xz-static From 6eedb714440bfd4b9f8267eb700f976019b67ff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 21:55:05 +1200 Subject: [PATCH 23/56] build(deps): bump actions/checkout from 4 to 5 (#4471) * build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J-P Nurmi --- .github/alpine/setup-node.sh | 10 +++++----- .github/workflows/alpine.yml | 2 +- .github/workflows/build.yml | 20 +++++++++++--------- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/device-tests-android.yml | 4 ++-- .github/workflows/device-tests-ios.yml | 2 +- .github/workflows/format-code.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/vulnerabilities.yml | 2 +- 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.github/alpine/setup-node.sh b/.github/alpine/setup-node.sh index bde0d9d300..f0e942f58a 100755 --- a/.github/alpine/setup-node.sh +++ b/.github/alpine/setup-node.sh @@ -1,12 +1,12 @@ #!/bin/sh -[ -n "$1" ] || { echo "Usage: $0 "; exit 1; } - # A workaround for "JavaScript Actions in Alpine containers are only supported on x64 Linux runners." # https://github.com/actions/runner/blob/8a9b96806d12343f7d123c669e29c629138023dd/src/Runner.Worker/Handlers/StepHost.cs#L283-L290 if [ "$(uname -m)" != "x86_64" ]; then - mkdir -p $1 - ln -s /usr/bin/node $1 - ln -s /usr/bin/npm $1 + for node in /__e/node*; do + mkdir -p $node/bin + ln -s /usr/bin/node $node/bin/node + ln -s /usr/bin/npm $node/bin/npm + done sed -i 's/ID=alpine/ID=unknown/' /usr/lib/os-release fi diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml index b871bf31c6..207f8e2c1f 100644 --- a/.github/workflows/alpine.yml +++ b/.github/workflows/alpine.yml @@ -23,7 +23,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: docker/login-action@v3 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d438557fce..1b1f585ad7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,7 @@ jobs: image: ghcr.io/getsentry/sentry-dotnet-alpine:3.21 volumes: - /tmp/node20:/__e/node20 + - /tmp/node24:/__e/node24 - os: macos-15 # Pin macos to get the version of Xcode that we need: https://github.com/actions/runner-images/issues/10703 rid: macos # universal (osx-arm64 + osx-x64) - os: windows-latest @@ -44,10 +45,10 @@ jobs: - name: Initialize Alpine Linux if: ${{ contains(matrix.container.image, 'alpine') }} run: | - curl -sSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/.github/alpine/setup-node.sh | sudo bash /dev/stdin /__e/node20/bin/ + curl -sSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/.github/alpine/setup-node.sh | sudo bash /dev/stdin - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - run: git submodule update --init modules/sentry-native @@ -101,6 +102,7 @@ jobs: image: ghcr.io/getsentry/sentry-dotnet-alpine:3.21 volumes: - /tmp/node20:/__e/node20 + - /tmp/node24:/__e/node24 - /var/run/docker.sock:/var/run/docker.sock - os: macos-15 # Pin macos to get the version of Xcode that we need: https://github.com/actions/runner-images/issues/10703 rid: macos # universal (osx-arm64 + osx-x64) @@ -116,14 +118,14 @@ jobs: - name: Initialize Alpine Linux if: ${{ contains(matrix.container.image, 'alpine') }} run: | - curl -sSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/.github/alpine/setup-node.sh | sudo bash /dev/stdin /__e/node20/bin/ + curl -sSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/.github/alpine/setup-node.sh | sudo bash /dev/stdin - name: Cancel Previous Runs if: github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # Tag: 0.12.1 - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 2 # default is 1 and codecov needs > 1 @@ -247,7 +249,7 @@ jobs: - name: Sparse checkout if: env.CI_PUBLISHING_BUILD == 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # We only check out what is absolutely necessary to reduce a chance of local files impacting # integration tests, e.g. Directory.Build.props, nuget.config, ... @@ -274,7 +276,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -327,7 +329,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -357,7 +359,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 2 # default is 1 and codecov needs > 1 @@ -396,7 +398,7 @@ jobs: if: ${{ !startsWith(github.ref_name, 'release/') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7f61fcb1cc..01d7a03e2a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # Tag: 0.12.1 - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/device-tests-android.yml b/.github/workflows/device-tests-android.yml index d58729dea9..62c85e5eeb 100644 --- a/.github/workflows/device-tests-android.yml +++ b/.github/workflows/device-tests-android.yml @@ -26,7 +26,7 @@ jobs: uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # Tag: 0.12.1 - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -73,7 +73,7 @@ jobs: sudo udevadm trigger --name-match=kvm - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download test app artifact uses: actions/download-artifact@v5 diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index 3474a210c5..c56226bdb6 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -23,7 +23,7 @@ jobs: uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # Tag: 0.12.1 - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index b0cf8ecf90..ede195cf6a 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-15 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ba422e99d..1f9b66c130 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} - name: Check out current commit (${{ github.sha }}) - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: token: ${{ steps.token.outputs.token }} fetch-depth: 0 diff --git a/.github/workflows/vulnerabilities.yml b/.github/workflows/vulnerabilities.yml index d7f1d994f5..8197efdd86 100644 --- a/.github/workflows/vulnerabilities.yml +++ b/.github/workflows/vulnerabilities.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive From a6f3b68b2307a9222df0d1188cf2e8cec39bb127 Mon Sep 17 00:00:00 2001 From: Stefan Jandl Date: Thu, 28 Aug 2025 22:13:37 +0200 Subject: [PATCH 24/56] Updated logmessage (#4477) --- src/Sentry/Internal/AotHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Internal/AotHelper.cs b/src/Sentry/Internal/AotHelper.cs index fe5156ab8f..762ae63fce 100644 --- a/src/Sentry/Internal/AotHelper.cs +++ b/src/Sentry/Internal/AotHelper.cs @@ -39,7 +39,7 @@ internal static bool CheckIsTrimmed(IDiagnosticLogger? logger = null) } // fallback check - logger?.LogDebug("Stacktrace fallback"); + logger?.LogDebug("Falling back to stack trace check for trimming detection"); var stackTrace = new StackTrace(false); return stackTrace.GetFrame(0)?.GetMethod() is null; } From 3d461f5bde60d4108d92384cbbb3addf7747147d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 10:26:02 +1200 Subject: [PATCH 25/56] build(deps): bump codecov/codecov-action from 5.4.3 to 5.5.0 (#4472) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.3 to 5.5.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/18283e04ce6e62d37312384ff67231eb8fd56d24...fdcc8476540edceab3de004e990f80d881c6cc00) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b1f585ad7..54fbf5bb41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -225,7 +225,7 @@ jobs: run: dotnet test ${{ matrix.slnf }} -c Release --no-build --nologo -l GitHubActions -l "trx;LogFilePrefix=testresults_${{ runner.os }}" --collect "XPlat Code Coverage" - name: Upload code coverage - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 + uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 - name: Upload build and test outputs if: failure() From 42216b27f670ad810ff439021b9f2d5309704443 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 31 Aug 2025 23:50:47 +0200 Subject: [PATCH 26/56] build(deps): bump sentry-cocoa from 8.46.0 to 8.53.2 (#4483) --- modules/sentry-cocoa.properties | 2 +- scripts/generate-cocoa-bindings.ps1 | 5 +- src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 101 ++++++++++++------ .../SwiftApiDefinitions.cs | 51 +++++++++ .../SwiftStructsAndEnums.cs | 7 ++ 5 files changed, 129 insertions(+), 37 deletions(-) diff --git a/modules/sentry-cocoa.properties b/modules/sentry-cocoa.properties index 9d8498fef2..f3c920b1e0 100644 --- a/modules/sentry-cocoa.properties +++ b/modules/sentry-cocoa.properties @@ -1,2 +1,2 @@ -version = 8.46.0 +version = 8.53.2 repo = https://github.com/getsentry/sentry-cocoa diff --git a/scripts/generate-cocoa-bindings.ps1 b/scripts/generate-cocoa-bindings.ps1 index 90b3d76a51..d06ebea472 100644 --- a/scripts/generate-cocoa-bindings.ps1 +++ b/scripts/generate-cocoa-bindings.ps1 @@ -96,6 +96,8 @@ Write-Output "iPhoneSdkVersion: $iPhoneSdkVersion" ## Imports in the various header files are provided in the "new" style of: # `#import ` +# ...or: +# `#import SENTRY_HEADER(SentryHeader)` # ...instead of: # `#import "SomeHeader.h"` # This causes sharpie to fail resolve those headers @@ -106,6 +108,7 @@ foreach ($file in $filesToPatch) { $content = Get-Content -Path $file -Raw $content = $content -replace ']+)>', '"$1"' + $content = $content -replace '#import SENTRY_HEADER\(([^)]+)\)', '#import "$1.h"' Set-Content -Path $file -Value $content } else @@ -206,7 +209,7 @@ $Text = $Text -replace '\bISentrySerializable\b', 'SentrySerializable' $Text = $Text -replace ': INSCopying,', ':' -replace '\s?[:,] INSCopying', '' # Remove iOS attributes like [iOS (13, 0)] -$Text = $Text -replace '\[iOS \(13, 0\)\]\n?', '' +$Text = $Text -replace '\[iOS \(13,\s?0\)\]\n?', '' # Fix delegate argument names $Text = $Text -replace '(NSError) arg\d', '$1 error' diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index cf38a5e4b4..3b563d3c4f 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -384,7 +384,7 @@ interface SentryDsn [Export ("getHash")] string Hash { get; } - // -(NSURL * _Nonnull)getStoreEndpoint; + // -(NSURL * _Nonnull)getStoreEndpoint __attribute__((deprecated("This endpoint is no longer used"))); [Export ("getStoreEndpoint")] NSUrl StoreEndpoint { get; } @@ -412,6 +412,10 @@ interface SentryEnvelopeItemHeader : SentrySerializable [Export ("initWithType:length:filenname:contentType:")] NativeHandle Constructor (string type, nuint length, string filename, string contentType); + // -(instancetype _Nonnull)initWithType:(NSString * _Nonnull)type length:(NSUInteger)length contentType:(NSString * _Nonnull)contentType itemCount:(NSNumber * _Nonnull)itemCount; + [Export ("initWithType:length:contentType:itemCount:")] + NativeHandle Constructor (string type, nuint length, string contentType, NSNumber itemCount); + // @property (readonly, copy, nonatomic) NSString * _Nonnull type; [Export ("type")] string Type { get; } @@ -427,6 +431,14 @@ interface SentryEnvelopeItemHeader : SentrySerializable // @property (readonly, copy, nonatomic) NSString * _Nullable contentType; [NullAllowed, Export ("contentType")] string ContentType { get; } + + // @property (readonly, copy, nonatomic) NSNumber * _Nullable itemCount; + [NullAllowed, Export ("itemCount", ArgumentSemantic.Copy)] + NSNumber ItemCount { get; } + + // @property (copy, nonatomic) NSString * _Nullable platform; + [NullAllowed, Export ("platform")] + string Platform { get; set; } } partial interface Constants @@ -559,13 +571,6 @@ interface SentryEvent : SentrySerializable NativeHandle Constructor (NSError error); } -// @interface SentryEventDecodable : SentryEvent -[BaseType (typeof(SentryEvent))] -[Internal] -interface SentryEventDecodable -{ -} - // @interface SentryException : NSObject [BaseType (typeof(NSObject))] [DisableDefaultCtor] @@ -601,6 +606,20 @@ interface SentryException : SentrySerializable NativeHandle Constructor (string value, string type); } +// @interface SentryFeedbackAPI : NSObject +[BaseType (typeof(NSObject))] +[Internal] +interface SentryFeedbackAPI +{ + // -(void)showWidget __attribute__((availability(ios, introduced=13.0))); + [Export ("showWidget")] + void ShowWidget (); + + // -(void)hideWidget __attribute__((availability(ios, introduced=13.0))); + [Export ("hideWidget")] + void HideWidget (); +} + // @interface SentryFrame : NSObject [BaseType (typeof(NSObject))] [Internal] @@ -1024,6 +1043,23 @@ interface SentryHub void Close (); } +// @protocol SentryIntegrationProtocol +[Protocol] +[BaseType (typeof(NSObject))] +[Internal] +interface SentryIntegrationProtocol +{ + // @required -(BOOL)installWithOptions:(SentryOptions * _Nonnull)options __attribute__((swift_name("install(with:)"))); + [Abstract] + [Export ("installWithOptions:")] + bool InstallWithOptions (SentryOptions options); + + // @required -(void)uninstall; + [Abstract] + [Export ("uninstall")] + void Uninstall (); +} + // @interface SentryMeasurementUnit : NSObject [BaseType (typeof(NSObject))] [DisableDefaultCtor] @@ -1522,6 +1558,10 @@ interface SentryOptions [Export ("enableCoreDataTracing")] bool EnableCoreDataTracing { get; set; } + // @property (copy, nonatomic) SentryProfilingConfigurationBlock _Nullable configureProfiling; + [NullAllowed, Export ("configureProfiling", ArgumentSemantic.Copy)] + SentryProfilingConfigurationBlock ConfigureProfiling { get; set; } + // @property (assign, nonatomic) BOOL enableAppLaunchProfiling; [Export ("enableAppLaunchProfiling")] bool EnableAppLaunchProfiling { get; set; } @@ -1605,11 +1645,19 @@ interface SentryOptions [Export ("spotlightUrl")] string SpotlightUrl { get; set; } + // @property (readonly, nonatomic) NSObject * _Nonnull _swiftExperimentalOptions; + [Export ("_swiftExperimentalOptions")] + NSObject _swiftExperimentalOptions { get; } + // @property (copy, nonatomic) API_AVAILABLE(ios(13.0)) SentryUserFeedbackConfigurationBlock configureUserFeedback __attribute__((availability(ios, introduced=13.0))); [Export ("configureUserFeedback", ArgumentSemantic.Copy)] SentryUserFeedbackConfigurationBlock ConfigureUserFeedback { get; set; } } +// typedef void (^SentryProfilingConfigurationBlock)(SentryProfileOptions * _Nonnull); +[Internal] +delegate void SentryProfilingConfigurationBlock (SentryProfileOptions arg0); + // @interface SentryReplayApi : NSObject [BaseType (typeof(NSObject))] [Internal] @@ -1817,6 +1865,11 @@ interface SentrySDK [Export ("captureFeedback:")] void CaptureFeedback (SentryFeedback feedback); + // @property (readonly, nonatomic, class) API_AVAILABLE(ios(13.0)) SentryFeedbackAPI * feedback __attribute__((availability(ios, introduced=13.0))); + [Static] + [Export ("feedback")] + SentryFeedbackAPI Feedback { get; } + // +(void)addBreadcrumb:(SentryBreadcrumb * _Nonnull)crumb __attribute__((swift_name("addBreadcrumb(_:)"))); [Static] [Export ("addBreadcrumb:")] @@ -2325,33 +2378,6 @@ interface SentryUser : SentrySerializable nuint Hash { get; } } -// @interface SentryUserFeedback : NSObject -[BaseType (typeof(NSObject))] -[DisableDefaultCtor] -[Internal] -interface SentryUserFeedback : SentrySerializable -{ - // -(instancetype _Nonnull)initWithEventId:(SentryId * _Nonnull)eventId; - [Export ("initWithEventId:")] - NativeHandle Constructor (SentryId eventId); - - // @property (readonly, nonatomic, strong) SentryId * _Nonnull eventId; - [Export ("eventId", ArgumentSemantic.Strong)] - SentryId EventId { get; } - - // @property (copy, nonatomic) NSString * _Nonnull name; - [Export ("name")] - string Name { get; set; } - - // @property (copy, nonatomic) NSString * _Nonnull email; - [Export ("email")] - string Email { get; set; } - - // @property (copy, nonatomic) NSString * _Nonnull comments; - [Export ("comments")] - string Comments { get; set; } -} - // @interface SentryScreenFrames : NSObject [BaseType (typeof(NSObject))] [DisableDefaultCtor] @@ -2436,6 +2462,11 @@ interface PrivateSentrySDKOnly [Export ("getExtraContext")] NSDictionary ExtraContext { get; } + // +(void)setTrace:(SentryId * _Nonnull)traceId spanId:(SentrySpanId * _Nonnull)spanId; + [Static] + [Export ("setTrace:spanId:")] + void SetTrace (SentryId traceId, SentrySpanId spanId); + // +(uint64_t)startProfilerForTrace:(SentryId * _Nonnull)traceId; [Static] [Export ("startProfilerForTrace:")] diff --git a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs index 61fa7556a0..77c2373975 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs @@ -66,6 +66,30 @@ interface SentryId nuint Hash { get; } } +// @interface SentryProfileOptions : NSObject +[BaseType(typeof(NSObject), Name = "_TtC6Sentry20SentryProfileOptions")] +[DisableDefaultCtor] +[Internal] +interface SentryProfileOptions +{ + // @property(nonatomic) enum SentryProfileLifecycle lifecycle; + [Export("lifecycle", ArgumentSemantic.Assign)] + SentryProfileLifecycle Lifecycle { get; set; } + + // @property(nonatomic) float sessionSampleRate; + [Export("sessionSampleRate")] + float SessionSampleRate { get; set; } + + // @property(nonatomic) BOOL profileAppStarts; + [Export("profileAppStarts")] + bool ProfileAppStarts { get; set; } + + // - (nonnull instancetype) init OBJC_DESIGNATED_INITIALIZER; + [Export("init")] + [DesignatedInitializer] + NativeHandle Constructor(); +} + // @interface SentrySessionReplayIntegration : SentryBaseIntegration [BaseType (typeof(NSObject))] [Internal] @@ -224,6 +248,33 @@ interface SentryRRWebEvent : SentrySerializable new NSDictionary Serialize(); } +// @interface SentryUserFeedback : NSObject +[BaseType(typeof(NSObject))] +[DisableDefaultCtor] +[Internal] +interface SentryUserFeedback : SentrySerializable +{ + // @property (nonatomic, readonly, strong) SentryId * _Nonnull eventId; + [Export("eventId", ArgumentSemantic.Strong)] + SentryId EventId { get; } + + // @property (nonatomic, copy) NSString * _Nonnull name; + [Export("name")] + string Name { get; set; } + + // @property (nonatomic, copy) NSString * _Nonnull email; + [Export("email")] + string Email { get; set; } + + // @property (nonatomic, copy) NSString * _Nonnull comments; + [Export("comments")] + string Comments { get; set; } + + // - (nonnull instancetype)initWithEventId:(SentryId * _Nonnull)eventId OBJC_DESIGNATED_INITIALIZER; + [Export("initWithEventId:")] + NativeHandle Constructor(SentryId eventId); +} + [BaseType(typeof(NSObject), Name = "_TtC6Sentry31SentryUserFeedbackConfiguration")] [DisableDefaultCtor] [Internal] diff --git a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs index efd86efee2..5cc5cc81c7 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs @@ -29,6 +29,13 @@ internal enum SentryLevel : ulong Fatal = 5 } +[Native] +internal enum SentryProfileLifecycle : long +{ + Manual = 0, + Trace = 1 +} + [Native] internal enum SentryReplayQuality : long { From 89fe0878972189e05d3e699457ec0f357624e53f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:08:43 +1200 Subject: [PATCH 27/56] chore: update scripts/update-cli.ps1 to 2.53.0 (#4486) Co-authored-by: GitHub --- CHANGELOG.md | 3 +++ Directory.Build.props | 2 +- src/Sentry/Sentry.csproj | 14 +++++++------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b47a5320e..04e9fed741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ - Bump Native SDK from v0.9.1 to v0.10.0 ([#4436](https://github.com/getsentry/sentry-dotnet/pull/4436)) - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0100) - [diff](https://github.com/getsentry/sentry-native/compare/0.9.1...0.10.0) +- Bump CLI from v2.52.0 to v2.53.0 ([#4486](https://github.com/getsentry/sentry-dotnet/pull/4486)) + - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2530) + - [diff](https://github.com/getsentry/sentry-cli/compare/2.52.0...2.53.0) ## 5.14.1 diff --git a/Directory.Build.props b/Directory.Build.props index d7fbc4e99e..545ec22801 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -86,7 +86,7 @@ - 2.52.0 + 2.53.0 $(MSBuildThisFileDirectory)tools\sentry-cli\$(SentryCLIVersion)\ diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 033e256622..f00de450ff 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -113,13 +113,13 @@ <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - - - - - - - + + + + + + + From af44ea2007ab9215d1c0d7d60944a18a53753f44 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:03:44 +1200 Subject: [PATCH 28/56] chore: update modules/sentry-native to 0.10.1 (#4492) Co-authored-by: GitHub --- CHANGELOG.md | 6 +++--- modules/sentry-native | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04e9fed741..54582acde9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,9 @@ - Reapply "Bump Cocoa SDK from v8.39.0 to v8.46.0 (#4103)" ([#4442](https://github.com/getsentry/sentry-dotnet/pull/4442)) - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8460) - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.39.0...8.46.0) -- Bump Native SDK from v0.9.1 to v0.10.0 ([#4436](https://github.com/getsentry/sentry-dotnet/pull/4436)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0100) - - [diff](https://github.com/getsentry/sentry-native/compare/0.9.1...0.10.0) +- Bump Native SDK from v0.9.1 to v0.10.1 ([#4436](https://github.com/getsentry/sentry-dotnet/pull/4436), [#4492](https://github.com/getsentry/sentry-dotnet/pull/4492)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0101) + - [diff](https://github.com/getsentry/sentry-native/compare/0.9.1...0.10.1) - Bump CLI from v2.52.0 to v2.53.0 ([#4486](https://github.com/getsentry/sentry-dotnet/pull/4486)) - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2530) - [diff](https://github.com/getsentry/sentry-cli/compare/2.52.0...2.53.0) diff --git a/modules/sentry-native b/modules/sentry-native index b3320bb360..11f94efc64 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit b3320bb36003d4737d332158211811693cb0fbf1 +Subproject commit 11f94efc64d55e90aef9456ce01716c846ae1732 From 282b5c6f6403bf5bea72ddcbce859ab3ba1a306d Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 Sep 2025 10:04:54 +1200 Subject: [PATCH 29/56] Only register MauiSessionReplayMaskControlsOfTypeBinder when relevant (#4445) Resolves https://github.com/getsentry/sentry-dotnet/issues/4439 --- CHANGELOG.md | 1 + samples/Sentry.Samples.Maui/MauiProgram.cs | 7 +- src/Sentry.Maui/Internal/MauiEventsBinder.cs | 8 +- ...iSessionReplayMaskControlsOfTypeBinder.cs} | 12 +- .../SentryMauiAppBuilderExtensions.cs | 5 +- src/Sentry/Platforms/Android/NativeOptions.cs | 25 ++++ .../MauiCustomSessionReplayMaskBinderTests.cs | 119 ++++++++++++++++++ .../MauiEventsBinderTests.cs | 36 ++++++ .../MauiVisualElementEventsBinderTests.cs | 59 --------- 9 files changed, 202 insertions(+), 70 deletions(-) rename src/Sentry.Maui/Internal/{MauiVisualElementEventsBinder.cs => MauiSessionReplayMaskControlsOfTypeBinder.cs} (81%) create mode 100644 test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs delete mode 100644 test/Sentry.Maui.Tests/MauiVisualElementEventsBinderTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 54582acde9..abeff7e4fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) - Blocking calls are no longer treated as unhandled crashes ([#4458](https://github.com/getsentry/sentry-dotnet/pull/4458)) +- Only apply Session Replay masks to specific control types when necessary to avoid performance issues in MAUI apps with complex UIs ([#4445](https://github.com/getsentry/sentry-dotnet/pull/4445)) ### Dependencies diff --git a/samples/Sentry.Samples.Maui/MauiProgram.cs b/samples/Sentry.Samples.Maui/MauiProgram.cs index 909851b7d4..1a8a6a30a7 100644 --- a/samples/Sentry.Samples.Maui/MauiProgram.cs +++ b/samples/Sentry.Samples.Maui/MauiProgram.cs @@ -45,15 +45,18 @@ public static MauiApp CreateMauiApp() options.AddCommunityToolkitIntegration(); #if __ANDROID__ - // Currently experimental support is only available on Android + // Currently, experimental support is only available on Android options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; // Mask all images and text by default. This can be overridden for individual view elements via the // sentry:SessionReplay.Mask XML attribute (see MainPage.xaml for an example) options.Native.ExperimentalOptions.SessionReplay.MaskAllImages = true; options.Native.ExperimentalOptions.SessionReplay.MaskAllText = true; - // Alternatively the masking behaviour for entire classes of VisualElements can be configured here as + // Alternatively, the masking behaviour for entire classes of VisualElements can be configured here as // an exception to the default behaviour. + // WARNING: In apps with complex user interfaces, consisting of hundreds of visual controls on a single + // page, this option may cause performance issues. In such cases, consider applying the + // sentry:SessionReplay.Mask="Unmask" attribute to individual controls instead. options.Native.ExperimentalOptions.SessionReplay.UnmaskControlsOfType