Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ Public API diffs are stored as Verify snapshot files. After any public API chang
## Changelog

- If the change is not user-facing, add `#skip-changelog` to the PR description
- Otherwise generated automatically from [Commit message conventions](https://develop.sentry.dev/engineering-practices/commit-messages/)
- Otherwise generated automatically from [Commit message conventions](https://develop.sentry.dev/engineering-practices/commit-messages/).
- Do **NOT** add changelogs manually.
Comment thread
jamescrosswell marked this conversation as resolved.

```sh
# Get PR number for current branch
Expand Down
2 changes: 1 addition & 1 deletion modules/sentry-cocoa
Submodule sentry-cocoa updated 62 files
+1 −1 .github/last-release-runid
+2 −2 .github/workflows/analyze-language-trends.yml
+2 −2 .github/workflows/assemble-xcframework-variant.yml
+3 −3 .github/workflows/auto-update-tools.yml
+3 −3 .github/workflows/benchmarking.yml
+2 −2 .github/workflows/build-xcframework-variant-slices.yml
+4 −4 .github/workflows/build.yml
+1 −1 .github/workflows/changelog-preview.yml
+2 −2 .github/workflows/changes-in-high-risk-code.yml
+2 −2 .github/workflows/integration-test.yml
+1 −1 .github/workflows/lint-cocoapods-specs.yml
+1 −1 .github/workflows/nightly-test.yml
+2 −2 .github/workflows/objc-conversion-analysis.yml
+1 −1 .github/workflows/ready-to-merge-workflow.yml
+1 −1 .github/workflows/release-upload-xcframework.yml
+19 −3 .github/workflows/release.yml
+1 −1 .github/workflows/size-analysis.yml
+2 −2 .github/workflows/test-3rd-party-integrations.yml
+1 −1 .github/workflows/test-cross-platform.yml
+11 −8 .github/workflows/test.yml
+2 −2 .github/workflows/testflight.yml
+4 −4 .github/workflows/ui-tests-common.yml
+2 −2 .github/workflows/ui-tests-critical.yml
+5 −5 .github/workflows/unit-test-common.yml
+1 −1 3rd-party-integrations/SentryCocoaLumberjack/Package.swift
+1 −1 3rd-party-integrations/SentryPulse/Package.swift
+1 −1 3rd-party-integrations/SentrySwiftLog/Package.swift
+1 −1 3rd-party-integrations/SentrySwiftyBeaver/Package.swift
+21 −1 CHANGELOG.md
+10 −10 Package.swift
+10 −10 Package@swift-6.1.swift
+123 −0 Package@swift-6.2.swift
+1 −1 Sentry.podspec
+2 −2 SentrySwiftUI.podspec
+7 −3 SentryTestUtils/Sources/TestNSNotificationCenterWrapper.swift
+6 −0 SentryTestUtils/Sources/TestSentryNSProcessInfoWrapper.swift
+1 −1 Sources/Configuration/SDK.xcconfig
+1 −1 Sources/Configuration/SentrySwiftUI.xcconfig
+1 −1 Sources/Configuration/Versioning.xcconfig
+1 −1 Sources/Sentry/SentryMeta.m
+11 −11 Sources/Sentry/SentryScope.m
+11 −0 Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m
+77 −1 Sources/Swift/Core/Tools/ViewCapture/SentryUIRedactBuilder.swift
+4 −0 Sources/Swift/Helper/SentryExtraContextProvider.swift
+15 −0 Sources/Swift/Helper/SentryMobileProvisionParser.swift
+4 −1 Sources/Swift/Helper/SentryProcessInfo.swift
+48 −5 Sources/Swift/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbs.swift
+47 −0 Sources/Swift/Integrations/SentryCrash/SentryCrashIntegration.swift
+10 −0 Sources/Swift/Integrations/SentryHangTrackingIntegration.swift
+14 −6 Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift
+1 −1 Sources/Swift/Integrations/SessionReplay/SentrySessionReplayIntegration.swift
+2 −2 Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationScopeObserver.swift
+20 −0 Tests/SentryTests/Helper/SentryExtraContextProviderTests.swift
+100 −0 Tests/SentryTests/Helper/SentryMobileProvisionParserTests.swift
+3 −0 Tests/SentryTests/Integrations/ANR/SentryHangTrackingIntegrationTests.swift
+56 −0 Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift
+43 −0 Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift
+1 −3 Tests/SentryTests/OptionsInSyncWithDocs/SentryOptionsDocumentationSyncTests.swift
+135 −1 Tests/SentryTests/SentryScopeSwiftTests.swift
+347 −0 Tests/SentryTests/ViewCapture/SentryUIRedactBuilderTests+SwiftUI.swift
+1 −0 Utils/VersionBump/main.swift
+1 −1 scripts/.clang-format-version
2 changes: 1 addition & 1 deletion modules/sentry-native
Submodule sentry-native updated 58 files
+30 −2 .github/workflows/ci.yml
+2 −2 .github/workflows/codeql.yml
+1 −1 .github/workflows/console-check.yml
+1 −1 .github/workflows/release.yml
+21 −0 CHANGELOG.md
+12 −1 examples/example.c
+1 −1 external/crashpad
+20 −2 include/sentry.h
+1 −1 ndk/gradle.properties
+6 −2 src/CMakeLists.txt
+6 −3 src/backends/native/minidump/sentry_minidump_format.h
+63 −1 src/backends/native/minidump/sentry_minidump_linux.c
+5 −1 src/backends/native/minidump/sentry_minidump_macos.c
+7 −1 src/backends/native/minidump/sentry_minidump_windows.c
+5 −0 src/backends/native/sentry_crash_context.h
+73 −21 src/backends/native/sentry_crash_daemon.c
+26 −4 src/backends/native/sentry_crash_ipc.c
+5 −0 src/backends/native/sentry_crash_ipc.h
+8 −6 src/backends/sentry_backend_breakpad.cpp
+47 −2 src/backends/sentry_backend_crashpad.cpp
+1 −26 src/backends/sentry_backend_inproc.c
+28 −5 src/backends/sentry_backend_native.c
+6 −10 src/modulefinder/sentry_modulefinder_linux.c
+5 −9 src/sentry_batcher.c
+0 −1 src/sentry_batcher.h
+28 −39 src/sentry_core.c
+6 −2 src/sentry_core.h
+40 −3 src/sentry_database.c
+17 −0 src/sentry_database.h
+25 −0 src/sentry_logger.h
+2 −2 src/sentry_logs.c
+2 −2 src/sentry_metrics.c
+12 −1 src/sentry_options.c
+0 −1 src/sentry_options.h
+4 −0 src/sentry_retry.c
+1 −1 src/sentry_session.c
+28 −0 src/sentry_utils.h
+1 −1 tests/__init__.py
+10 −0 tests/build_config.py
+2 −0 tests/conditions.py
+4 −5 tests/test_build_static.py
+17 −3 tests/test_dotnet_signals.py
+3 −0 tests/test_embedded_info.py
+87 −12 tests/test_integration_cache.py
+61 −5 tests/test_integration_crashpad.py
+16 −6 tests/test_integration_http.py
+5 −5 tests/test_integration_logger.py
+3 −3 tests/test_integration_logs.py
+3 −3 tests/test_integration_metrics.py
+48 −2 tests/test_integration_native.py
+10 −6 tests/test_integration_stdout.py
+78 −0 tests/unit/test_cache.c
+6 −0 tests/unit/test_client_report.c
+46 −0 tests/unit/test_retry.c
+4 −0 tests/unit/test_unwinder.c
+3 −0 tests/unit/tests.inc
+32 −1 vendor/libunwind/CMakeLists.txt
+2 −2 vendor/libunwind/VENDORING.md
2 changes: 2 additions & 0 deletions src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal class BindableSentryAspNetCoreOptions : BindableSentryLoggingOptions
public bool? AdjustStandardEnvironmentNameCasing { get; set; }
public bool? AutoRegisterTracing { get; set; }
public bool? CaptureBlockingCalls { get; set; }
public bool? AlwaysCallTransactionNameProvider { get; set; }

public void ApplyTo(SentryAspNetCoreOptions options)
{
Expand All @@ -28,5 +29,6 @@ public void ApplyTo(SentryAspNetCoreOptions options)
options.AdjustStandardEnvironmentNameCasing = AdjustStandardEnvironmentNameCasing ?? options.AdjustStandardEnvironmentNameCasing;
options.AutoRegisterTracing = AutoRegisterTracing ?? options.AutoRegisterTracing;
options.CaptureBlockingCalls = CaptureBlockingCalls ?? options.CaptureBlockingCalls;
options.AlwaysCallTransactionNameProvider = AlwaysCallTransactionNameProvider ?? options.AlwaysCallTransactionNameProvider;
}
}
7 changes: 5 additions & 2 deletions src/Sentry.AspNetCore/Extensions/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ internal static class HttpContextExtensions
: null;
}

internal static string? TryGetCustomTransactionName(this HttpContext context) =>
context.Features.Get<TransactionNameProvider>()?.Invoke(context);
internal static string? TryGetCustomTransactionName(this HttpContext context)
{
var customName = context.Features.Get<TransactionNameProvider>()?.Invoke(context);
return !string.IsNullOrEmpty(customName) ? customName : null;
}

public static string? TryGetTransactionName(this HttpContext context)
{
Expand Down
13 changes: 13 additions & 0 deletions src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ public class SentryAspNetCoreOptions : SentryLoggingOptions
/// </remarks>
public TransactionNameProvider? TransactionNameProvider { get; set; }

/// <summary>
/// Controls whether <see cref="TransactionNameProvider"/> runs on every request, or only
/// when routing fails to resolve a template.
/// </summary>
/// <remarks>
/// When <c>false</c> (default), the provider is only invoked as a fallback for unresolved routes —
/// matching the legacy behaviour. When <c>true</c>, the provider is invoked for every request
/// after routing; a non-empty return value overrides the route-derived name. If the provider
/// returns <c>null</c> or <see cref="string.Empty"/>, the routed name is preserved (or the URL-path
/// fallback applies when no route was resolved).
/// </remarks>
public bool AlwaysCallTransactionNameProvider { get; set; }
Comment thread
Flash0ver marked this conversation as resolved.
Outdated
Comment thread
jamescrosswell marked this conversation as resolved.
Outdated

/// <summary>
/// Controls whether the casing of the standard (Production, Development and Staging) environment name supplied by <see cref="Microsoft.AspNetCore.Hosting.IHostingEnvironment" />
/// is adjusted when setting the Sentry environment. Defaults to true.
Expand Down
8 changes: 5 additions & 3 deletions src/Sentry.AspNetCore/SentryTracingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,15 @@ public async Task InvokeAsync(HttpContext context)
var status = SpanStatusConverter.FromHttpStatusCode(context.Response.StatusCode);

// If no Name was found for Transaction, then we don't have the route.
if (transaction.Name == string.Empty)
// Also, run this block if the caller has opted into always calling the TransactionNameProvider.
var customName = new Lazy<string?>(context.TryGetCustomTransactionName);
var forceCustomName = _options.AlwaysCallTransactionNameProvider && customName.Value is not null;
if (transaction.Name.Length == 0 || forceCustomName)
Comment thread
jamescrosswell marked this conversation as resolved.
Outdated
Comment thread
jamescrosswell marked this conversation as resolved.
Outdated
{
var method = context.Request.Method.ToUpperInvariant();

// If we've set a TransactionNameProvider, use that here
var customTransactionName = context.TryGetCustomTransactionName();
if (!string.IsNullOrEmpty(customTransactionName))
if (customName.Value is { } customTransactionName)
{
transaction.Name = $"{method} {customTransactionName}";
tracer.NameSource = TransactionNameSource.Custom;
Comment thread
jamescrosswell marked this conversation as resolved.
Outdated
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
Comment thread
Flash0ver marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AlwaysCallTransactionNameProvider { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool CaptureBlockingCalls { get; set; }
public bool FlushOnCompletedRequest { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AlwaysCallTransactionNameProvider { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool CaptureBlockingCalls { get; set; }
public bool FlushOnCompletedRequest { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AlwaysCallTransactionNameProvider { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool CaptureBlockingCalls { get; set; }
public bool FlushOnCompletedRequest { get; set; }
Expand Down
130 changes: 130 additions & 0 deletions test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -752,4 +752,134 @@ public async Task Transaction_TransactionNameProviderSetUnset_TransactionNameSet
transaction.Name.Should().Be("GET /person/13.bmp");
transaction.NameSource.Should().Be(TransactionNameSource.Url);
}

[Fact]
public async Task Transaction_TransactionNameProviderSetForRoutedRequest_ProviderOverridesRouteName()
{
// Arrange
SentryTransaction transaction = null;

const string expectedName = "My custom name";

var sentryClient = Substitute.For<ISentryClient>();
sentryClient.When(x => x.CaptureTransaction(Arg.Any<SentryTransaction>(), Arg.Any<Scope>(), Arg.Any<SentryHint>()))
.Do(callback => transaction = callback.Arg<SentryTransaction>());

var hub = new Hub(new SentryOptions { Dsn = ValidDsn, TracesSampleRate = 1 }, sentryClient);

var server = new TestServer(new WebHostBuilder()
.UseDefaultServiceProvider(di => di.EnableValidation())
.UseSentry(aspNetOptions =>
{
aspNetOptions.TransactionNameProvider = _ => expectedName;
aspNetOptions.AlwaysCallTransactionNameProvider = true;
})
.ConfigureServices(services =>
{
services.AddRouting();

services.RemoveAll(typeof(Func<IHub>));
services.AddSingleton<Func<IHub>>(() => hub);
})
.Configure(app =>
{
app.UseRouting();

app.UseEndpoints(routes => routes.Map("/person/{id}", _ => Task.CompletedTask));
}));

var client = server.CreateClient();

// Act
await client.GetStringAsync("/person/13");

// Assert
transaction.Should().NotBeNull();
transaction.Name.Should().Be($"GET {expectedName}");
transaction.NameSource.Should().Be(TransactionNameSource.Custom);
}

[Fact]
public async Task Transaction_TransactionNameProviderSetForRoutedRequest_DefaultsToRouteBehaviour()
{
// Arrange
SentryTransaction transaction = null;

var sentryClient = Substitute.For<ISentryClient>();
sentryClient.When(x => x.CaptureTransaction(Arg.Any<SentryTransaction>(), Arg.Any<Scope>(), Arg.Any<SentryHint>()))
.Do(callback => transaction = callback.Arg<SentryTransaction>());

var hub = new Hub(new SentryOptions { Dsn = ValidDsn, TracesSampleRate = 1 }, sentryClient);

var server = new TestServer(new WebHostBuilder()
.UseDefaultServiceProvider(di => di.EnableValidation())
.UseSentry(aspNetOptions => aspNetOptions.TransactionNameProvider = _ => "My custom name")
.ConfigureServices(services =>
{
services.AddRouting();

services.RemoveAll(typeof(Func<IHub>));
services.AddSingleton<Func<IHub>>(() => hub);
})
.Configure(app =>
{
app.UseRouting();

app.UseEndpoints(routes => routes.Map("/person/{id}", _ => Task.CompletedTask));
}));

var client = server.CreateClient();

// Act
await client.GetStringAsync("/person/13");

// Assert
transaction.Should().NotBeNull();
transaction.Name.Should().Be("GET /person/{id}");
transaction.NameSource.Should().Be(TransactionNameSource.Route);
}

[Fact]
public async Task Transaction_AlwaysCallTransactionNameProviderWithNullReturn_PreservesRouteName()
{
// Arrange
SentryTransaction transaction = null;

var sentryClient = Substitute.For<ISentryClient>();
sentryClient.When(x => x.CaptureTransaction(Arg.Any<SentryTransaction>(), Arg.Any<Scope>(), Arg.Any<SentryHint>()))
.Do(callback => transaction = callback.Arg<SentryTransaction>());

var hub = new Hub(new SentryOptions { Dsn = ValidDsn, TracesSampleRate = 1 }, sentryClient);

var server = new TestServer(new WebHostBuilder()
.UseDefaultServiceProvider(di => di.EnableValidation())
.UseSentry(aspNetOptions =>
{
aspNetOptions.TransactionNameProvider = _ => null;
aspNetOptions.AlwaysCallTransactionNameProvider = true;
})
.ConfigureServices(services =>
{
services.AddRouting();

services.RemoveAll(typeof(Func<IHub>));
services.AddSingleton<Func<IHub>>(() => hub);
})
.Configure(app =>
{
app.UseRouting();

app.UseEndpoints(routes => routes.Map("/person/{id}", _ => Task.CompletedTask));
}));

var client = server.CreateClient();

// Act
await client.GetStringAsync("/person/13");

// Assert
transaction.Should().NotBeNull();
transaction.Name.Should().Be("GET /person/{id}");
transaction.NameSource.Should().Be(TransactionNameSource.Route);
}
}