Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
482 changes: 482 additions & 0 deletions sentry-dotnet/.gitignore

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions sentry-dotnet/4819/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using Sentry;
using Sentry.Serilog;
using Serilog;
using Serilog.Events;

// Configure Serilog with Sentry sink
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.Sentry(o =>
{
o.Dsn = Environment.GetEnvironmentVariable("SENTRY_DSN");
o.Debug = true;
o.MinimumBreadcrumbLevel = LogEventLevel.Debug;
o.MinimumEventLevel = LogEventLevel.Information;
})
.CreateLogger();

var builder = WebApplication.CreateBuilder(args);

// Configure Serilog for ASP.NET Core
builder.Host.UseSerilog();

// Configure Sentry for ASP.NET Core
builder.WebHost.UseSentry(options =>
{
options.Dsn = Environment.GetEnvironmentVariable("SENTRY_DSN");
options.Debug = true;
options.TracesSampleRate = 1.0d;
options.Environment = "development";

// Enable logs
options.EnableLogs = true;

// Add a no-op SetBeforeSendLog callback for debugging
options.SetBeforeSendLog((log) =>
{
// Breakpoint here to inspect the log before it's sent to Sentry
return log;
});
});

var app = builder.Build();

// Add test endpoint that demonstrates the bug
app.MapGet("/test", (ILogger<Program> logger) =>
{
var queueHandlerForScope = "JobProcessorFromScope";
var queueHandlerForLogs = "JobProcessor";

// Log 1: Before using scope, with the parameter
logger.LogInformation("Testing some log before using with the handlerName {Queue.HandlerName}", queueHandlerForLogs);

// Create a scope with both simple and dot-separated parameter names
using (logger.BeginScope(new Dictionary<string, object?>
{
{ "Queue.HandlerName", queueHandlerForScope },
{ "SimpleParam", "SimpleValue" }
}))
{
// Log 2: After using scope, with the same parameter name
// BUG: This causes the property.Queue.HandlerName to be removed from Sentry
logger.LogInformation("Testing some log after using with the handlerName {Queue.HandlerName}", queueHandlerForLogs);

// Log 3: After using scope, without providing the parameter again
// This should have the scope property present
logger.LogInformation("Testing some log after using with no handlerName");

// Log 4: Using simple parameter name with scope
logger.LogInformation("Testing with simple param {SimpleParam}", "DifferentValue");
}

return Results.Ok(new
{
Message = "Logs sent to Sentry. Check Sentry dashboard to see the bug.",
ExpectedBehavior = "Log 2 should have property.Queue.HandlerName set to 'JobProcessorFromScope'",
ActualBehavior = "Log 2 has property.Queue.HandlerName removed by Sentry SDK",
Impact = "Cannot filter/search logs by structured data properties when the same parameter name is used in both scope and log message"
});
});

// Add another endpoint with more examples
app.MapGet("/test-detailed", (ILogger<Program> logger) =>
{
logger.LogInformation("=== Starting detailed test ===");

// Scenario 1: Simple parameter name
using (logger.BeginScope(new Dictionary<string, object?>
{
{ "UserId", "user-from-scope" }
}))
{
logger.LogInformation("Logging with UserId {UserId}", "user-from-log");
logger.LogInformation("Logging without providing UserId");
}

logger.LogInformation("=== Scenario 1 complete ===");

// Scenario 2: Dot-separated parameter name (from issue)
using (logger.BeginScope(new Dictionary<string, object?>
{
{ "Queue.HandlerName", "ScopeHandler" }
}))
{
logger.LogInformation("Handler is {Queue.HandlerName}", "LogHandler");
logger.LogInformation("Handler from scope only");
}

logger.LogInformation("=== Scenario 2 complete ===");

// Scenario 3: Multiple dot-separated parameters
using (logger.BeginScope(new Dictionary<string, object?>
{
{ "Request.Id", "req-123-scope" },
{ "Request.Method", "GET" }
}))
{
logger.LogInformation("Processing request {Request.Id} with method {Request.Method}",
"req-456-log", "POST");
}

logger.LogInformation("=== Test complete ===");

return Results.Ok("Check Sentry for logged events");
});

app.Run();
23 changes: 23 additions & 0 deletions sentry-dotnet/4819/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:4819",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:4820;http://localhost:4819",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
96 changes: 96 additions & 0 deletions sentry-dotnet/4819/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Reproduction for sentry-dotnet#4819

**Issue:** https://github.com/getsentry/sentry-dotnet/issues/4819

## Description

This reproduction demonstrates a bug where Sentry removes structured logging properties from scopes when the same parameter name is used in both `BeginScope()` and in a log message parameter.

When using `ILogger.BeginScope()` to define structured data properties (e.g., `Queue.HandlerName`) and then logging a message with the same parameter name, Sentry incorrectly removes the property from the scope data. This breaks the ability to filter and search logs by these structured data properties.

## The Bug

**What should happen:**
- Scope property `Queue.HandlerName` should remain in `property.Queue.HandlerName`
- Log message parameter `{Queue.HandlerName}` should appear in `message.parameters.Queue.HandlerName`
- Both values should be preserved

**What actually happens:**
- When a log message uses the same parameter name as a scope property, Sentry removes the property from the scope data entirely
- Only the message parameter is preserved
- This breaks searching/filtering by structured data properties

## Steps to Reproduce

### 1. Set up Sentry DSN (optional)

If you want to see the logs in Sentry:
```bash
export SENTRY_DSN="your-dsn-here"
```

If you leave it empty, the logs will still be visible in the console output showing the bug behavior.

### 2. Build and run the application

```bash
cd SentryLoggingRepro
dotnet build
dotnet run
```

The application will start on http://localhost:5000 (or check the console output for the actual port).

### 3. Trigger the reproduction

Open your browser or use curl to hit the test endpoints:

```bash
# Basic test (matches the original issue)
curl http://localhost:5000/test

# Detailed test with multiple scenarios
curl http://localhost:5000/test-detailed
```

### 4. Observe the bug

Check the console output or your Sentry dashboard. You'll see that:

- **Log 1** (before scope): `property.Queue.HandlerName` is NOT present (expected, no scope yet)
- **Log 2** (inside scope with same param name): `property.Queue.HandlerName` is MISSING (BUG! Should be "JobProcessorFromScope")
- **Log 3** (inside scope without param): `property.Queue.HandlerName` is present with "JobProcessorFromScope" (correct)

The bug is in Log 2 - when you log with a parameter that has the same name as a scope property, Sentry removes the scope property entirely.

## Expected Behavior

All logs within the scope should have `property.Queue.HandlerName` set to `"JobProcessorFromScope"`, regardless of whether the log message also uses `{Queue.HandlerName}` as a parameter.

The scope properties should be used for filtering and searching, while message parameters are for the rendered message template.

## Actual Behavior

Sentry's deduplication logic incorrectly removes scope properties when a log message parameter has the same name. This breaks the structured logging workflow where:
1. You set up a scope with contextual properties (e.g., handler name, request ID)
2. You log messages that may reference those same properties

## Environment

- **.NET SDK:** 10.0.102
- **Sentry.AspNetCore:** 6.0.0
- **Sentry.Serilog:** 6.0.0
- **Serilog.AspNetCore:** 10.0.0
- **OS:** macOS (should reproduce on any OS)

## Impact

This bug prevents using structured logging properties from scopes for filtering/searching in Sentry when the log message parameters happen to use the same names. This is a common pattern where:
- Scopes define contextual data for a block of code (e.g., `Queue.HandlerName`)
- Individual log messages reference that context in their message templates

Users expect scope properties to always be available for filtering, regardless of message parameters.

## Related Issue Comments

The reproduction steps are based on this comment: https://github.com/getsentry/sentry-dotnet/issues/4819#issuecomment-3834832076
17 changes: 17 additions & 0 deletions sentry-dotnet/4819/SentryLoggingRepro.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.2" />
<PackageReference Include="Sentry.AspNetCore" Version="6.0.0" />
<PackageReference Include="Sentry.Serilog" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions sentry-dotnet/4819/SentryLoggingRepro.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@SentryLoggingRepro_HostAddress = http://localhost:4819

GET {{SentryLoggingRepro_HostAddress}}/test/
Accept: application/json

###

GET {{SentryLoggingRepro_HostAddress}}/test-detailed/
Accept: application/json

###
3 changes: 3 additions & 0 deletions sentry-dotnet/4819/SentryLoggingRepro.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="SentryLoggingRepro.csproj" />
</Solution>
8 changes: 8 additions & 0 deletions sentry-dotnet/4819/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions sentry-dotnet/4819/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
3 changes: 3 additions & 0 deletions sentry-dotnet/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- stop multi-level merging: https://learn.microsoft.com/visualstudio/msbuild/customize-by-directory -->
</Project>
3 changes: 3 additions & 0 deletions sentry-dotnet/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- stop multi-level merging: https://learn.microsoft.com/visualstudio/msbuild/customize-by-directory -->
</Project>
8 changes: 8 additions & 0 deletions sentry-dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<!-- stop multi-level merging: https://learn.microsoft.com/visualstudio/msbuild/customize-by-directory -->
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
</Project>
Loading