From 8ba1b3e4b92763973a65a2ad0dad892c9516cfff Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 11 May 2026 13:19:10 +1200 Subject: [PATCH 1/5] fix: populate user attributes on logs from current scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #5209 — user.id, user.name, and user.email were missing from structured logs even when user info was set on the scope. Co-Authored-By: Claude Sonnet 4.6 --- .../Internal/DefaultSentryStructuredLogger.cs | 2 +- src/Sentry/SentryLog.cs | 18 ++++++- .../SentryStructuredLoggerTests.cs | 52 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs index d65f3ca9aa..4e664c813b 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -76,7 +76,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template } var scope = _hub.GetScope(); - log.SetDefaultAttributes(_options, scope?.Sdk ?? SdkVersion.Instance); + log.SetDefaultAttributes(_options, scope?.Sdk ?? SdkVersion.Instance, scope); CaptureLog(log); } diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index 3cb503cdba..e1046c06a8 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -172,7 +172,7 @@ internal void SetAttribute(string key, int value) _attributes[key] = new SentryAttribute(value, "integer"); } - internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) + internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk, Scope? scope = null) { var environment = options.SettingLocator.GetEnvironment(); SetAttribute("sentry.environment", environment); @@ -191,6 +191,22 @@ internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) { SetAttribute("sentry.sdk.version", version); } + + if (scope?.User is { } user) + { + if (user.Id is { } userId) + { + SetAttribute("user.id", userId); + } + if (user.Username is { } username) + { + SetAttribute("user.name", username); + } + if (user.Email is { } email) + { + SetAttribute("user.email", email); + } + } } internal void SetOrigin(string origin) diff --git a/test/Sentry.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Tests/SentryStructuredLoggerTests.cs index 3fbca3197a..3228330ec7 100644 --- a/test/Sentry.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Tests/SentryStructuredLoggerTests.cs @@ -245,6 +245,58 @@ public void Dispose_BeforeLog_DoesNotCaptureEnvelope() entry.Args.Should().BeEquivalentTo([nameof(SentryLog)]); } + [Fact] + public void Log_WithScopeUser_SetsUserAttributes() + { + var scope = new Scope(); + scope.User = new SentryUser { Id = "user-id", Username = "user-name", Email = "user@example.com" }; + _fixture.Hub.SubstituteConfigureScope(scope); + + SentryLog capturedLog = null!; + _fixture.Options.EnableLogs = true; + _fixture.Options.SetBeforeSendLog((SentryLog log) => + { + capturedLog = log; + return log; + }); + var logger = _fixture.GetSut(); + + logger.LogInfo("A message"); + logger.Flush(); + + capturedLog.Should().NotBeNull(); + capturedLog.TryGetAttribute("user.id", out string? userId).Should().BeTrue(); + userId.Should().Be("user-id"); + capturedLog.TryGetAttribute("user.name", out string? userName).Should().BeTrue(); + userName.Should().Be("user-name"); + capturedLog.TryGetAttribute("user.email", out string? userEmail).Should().BeTrue(); + userEmail.Should().Be("user@example.com"); + } + + [Fact] + public void Log_WithoutScopeUser_DoesNotSetUserAttributes() + { + var scope = new Scope(); + _fixture.Hub.SubstituteConfigureScope(scope); + + SentryLog capturedLog = null!; + _fixture.Options.EnableLogs = true; + _fixture.Options.SetBeforeSendLog((SentryLog log) => + { + capturedLog = log; + return log; + }); + var logger = _fixture.GetSut(); + + logger.LogInfo("A message"); + logger.Flush(); + + capturedLog.Should().NotBeNull(); + capturedLog.TryGetAttribute("user.id", out object? _).Should().BeFalse(); + capturedLog.TryGetAttribute("user.name", out object? _).Should().BeFalse(); + capturedLog.TryGetAttribute("user.email", out object? _).Should().BeFalse(); + } + private static void ConfigureLog(SentryLog log) { log.SetAttribute("attribute-key", "attribute-value"); From 3c73a92b4b8c5b2288950b864859160b49de3341 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 11 May 2026 14:13:15 +1200 Subject: [PATCH 2/5] review: simplify SetDefaultAttributes API and add server attributes - Replace redundant SdkVersion parameter with Scope (SDK is on scope) - Add server.address attribute (from options.ServerName or Environment.MachineName when SendDefaultPii is enabled) - Integrations (Serilog, Extensions.Logging) now mirror their SDK identity onto the scope before logging Co-Authored-By: Claude Opus 4.7 --- .../SentryStructuredLogger.cs | 8 +- src/Sentry.Serilog/SentrySink.Structured.cs | 8 +- .../Internal/DefaultSentryStructuredLogger.cs | 3 +- src/Sentry/SentryLog.cs | 15 +++- .../SentryStructuredLoggerProviderTests.cs | 1 + .../SentryStructuredLoggerTests.cs | 1 + test/Sentry.Tests/SentryLogTests.cs | 89 ++++++++++++++++--- 7 files changed, 110 insertions(+), 15 deletions(-) diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index e4f5b500d9..f6907dffb2 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -87,7 +87,13 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except SpanId = spanId, }; - log.SetDefaultAttributes(_options, _sdk); + var scope = _hub.GetScope(); + if (scope?.Sdk is { } scopeSdk) + { + scopeSdk.Name = _sdk.Name; + scopeSdk.Version = _sdk.Version; + } + log.SetDefaultAttributes(_options, scope); log.SetOrigin("auto.log.extensions_logging"); if (_categoryName is not null) diff --git a/src/Sentry.Serilog/SentrySink.Structured.cs b/src/Sentry.Serilog/SentrySink.Structured.cs index 535c4d1656..2fdaba0d42 100644 --- a/src/Sentry.Serilog/SentrySink.Structured.cs +++ b/src/Sentry.Serilog/SentrySink.Structured.cs @@ -17,7 +17,13 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve SpanId = spanId, }; - log.SetDefaultAttributes(options, Sdk); + var scope = hub.GetScope(); + if (scope?.Sdk is { } scopeSdk) + { + scopeSdk.Name = Sdk.Name; + scopeSdk.Version = Sdk.Version; + } + log.SetDefaultAttributes(options, scope); log.SetOrigin("auto.log.serilog"); foreach (var attribute in attributes) diff --git a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs index 4e664c813b..fabeb326d2 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -75,8 +75,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template return; } - var scope = _hub.GetScope(); - log.SetDefaultAttributes(_options, scope?.Sdk ?? SdkVersion.Instance, scope); + log.SetDefaultAttributes(_options, _hub.GetScope()); CaptureLog(log); } diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index e1046c06a8..bfa71a4771 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -172,8 +172,9 @@ internal void SetAttribute(string key, int value) _attributes[key] = new SentryAttribute(value, "integer"); } - internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk, Scope? scope = null) + internal void SetDefaultAttributes(SentryOptions options, Scope? scope) { + // Core Attributes var environment = options.SettingLocator.GetEnvironment(); SetAttribute("sentry.environment", environment); @@ -183,6 +184,7 @@ internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk, Scope? SetAttribute("sentry.release", release); } + var sdk = scope?.Sdk ?? SdkVersion.Instance; if (sdk.Name is { } name) { SetAttribute("sentry.sdk.name", name); @@ -192,6 +194,17 @@ internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk, Scope? SetAttribute("sentry.sdk.version", version); } + // Server Attributes + if (!string.IsNullOrEmpty(options.ServerName)) + { + SetAttribute("server.address", options.ServerName!); + } + else if (options.SendDefaultPii) + { + SetAttribute("server.address", Environment.MachineName); + } + + // User Attributes if (scope?.User is { } user) { if (user.Id is { } userId) diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs index 2342e43e06..0740d3d286 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs @@ -22,6 +22,7 @@ public Fixture() Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); Hub = Substitute.For(); + Hub.SubstituteConfigureScope(new Scope(loggingOptions)); Clock = new MockClock(); Sdk = new SdkVersion { diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs index 05e43b9e31..cbdf432ad8 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs @@ -31,6 +31,7 @@ public Fixture() CategoryName = nameof(CategoryName); Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); Hub = Substitute.For(); + Hub.SubstituteConfigureScope(new Scope(loggingOptions)); Clock = new MockClock(new DateTimeOffset(2025, 04, 22, 14, 51, 00, 789, TimeSpan.FromHours(2))); Sdk = new SdkVersion { diff --git a/test/Sentry.Tests/SentryLogTests.cs b/test/Sentry.Tests/SentryLogTests.cs index 5abc5664fc..12712daebb 100644 --- a/test/Sentry.Tests/SentryLogTests.cs +++ b/test/Sentry.Tests/SentryLogTests.cs @@ -44,11 +44,9 @@ public void Protocol_Default_VerifyAttributes() Environment = "my-environment", Release = "my-release", }; - var sdk = new SdkVersion - { - Name = "Sentry.Test.SDK", - Version = "1.2.3-test+Sentry", - }; + var scope = new Scope(options); + scope.Sdk.Name = "Sentry.Test.SDK"; + scope.Sdk.Version = "1.2.3-test+Sentry"; var log = new SentryLog(Timestamp, TraceId, (SentryLogLevel)24, "message") { @@ -57,7 +55,7 @@ public void Protocol_Default_VerifyAttributes() SpanId = SpanId, }; log.SetAttribute("attribute", "value"); - log.SetDefaultAttributes(options, sdk); + log.SetDefaultAttributes(options, scope); log.Timestamp.Should().Be(Timestamp); log.TraceId.Should().Be(TraceId); @@ -77,13 +75,81 @@ public void Protocol_Default_VerifyAttributes() log.TryGetAttribute("sentry.release", out string release).Should().BeTrue(); release.Should().Be(options.Release); log.TryGetAttribute("sentry.sdk.name", out string name).Should().BeTrue(); - name.Should().Be(sdk.Name); + name.Should().Be(scope.Sdk.Name); log.TryGetAttribute("sentry.sdk.version", out string version).Should().BeTrue(); - version.Should().Be(sdk.Version); + version.Should().Be(scope.Sdk.Version); log.TryGetAttribute("not-found", out object notFound).Should().BeFalse(); notFound.Should().BeNull(); } + [Fact] + public void SetDefaultAttributes_OptionsServerName_SetsServerAddress() + { + var options = new SentryOptions { ServerName = "my-server" }; + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, new Scope(options)); + + log.TryGetAttribute("server.address", out string address).Should().BeTrue(); + address.Should().Be("my-server"); + } + + [Fact] + public void SetDefaultAttributes_SendDefaultPii_SetsServerAddressToMachineName() + { + var options = new SentryOptions { SendDefaultPii = true }; + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, new Scope(options)); + + log.TryGetAttribute("server.address", out string address).Should().BeTrue(); + address.Should().Be(Environment.MachineName); + } + + [Fact] + public void SetDefaultAttributes_NoServerNameNoPii_OmitsServerAddress() + { + var options = new SentryOptions(); + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, new Scope(options)); + + log.TryGetAttribute("server.address", out object _).Should().BeFalse(); + } + + [Fact] + public void SetDefaultAttributes_ScopeUser_SetsUserAttributes() + { + var options = new SentryOptions(); + var scope = new Scope(options) + { + User = new SentryUser { Id = "user-id", Username = "user-name", Email = "user@example.com" }, + }; + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, scope); + + log.TryGetAttribute("user.id", out string id).Should().BeTrue(); + id.Should().Be("user-id"); + log.TryGetAttribute("user.name", out string username).Should().BeTrue(); + username.Should().Be("user-name"); + log.TryGetAttribute("user.email", out string email).Should().BeTrue(); + email.Should().Be("user@example.com"); + } + + [Fact] + public void SetDefaultAttributes_NoScopeUser_OmitsUserAttributes() + { + var options = new SentryOptions(); + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, new Scope(options)); + + log.TryGetAttribute("user.id", out object _).Should().BeFalse(); + log.TryGetAttribute("user.name", out object _).Should().BeFalse(); + log.TryGetAttribute("user.email", out object _).Should().BeFalse(); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -118,7 +184,7 @@ public void WriteTo_Envelope_MinimalSerializedSentryLog() }; var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Trace, "message"); - log.SetDefaultAttributes(options, new SdkVersion()); + log.SetDefaultAttributes(options, new Scope(options)); var envelope = Envelope.FromLog(new StructuredLog([log])); @@ -196,7 +262,10 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog() log.SetAttribute("boolean-attribute", true); log.SetAttribute("integer-attribute", 3); log.SetAttribute("double-attribute", 4.4); - log.SetDefaultAttributes(options, new SdkVersion { Name = "Sentry.Test.SDK", Version = "1.2.3-test+Sentry" }); + var scope = new Scope(options); + scope.Sdk.Name = "Sentry.Test.SDK"; + scope.Sdk.Version = "1.2.3-test+Sentry"; + log.SetDefaultAttributes(options, scope); var envelope = EnvelopeItem.FromLog(new StructuredLog([log])); From d51bf0a2830a8e8b92a587f398754238ecd6e752 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 11 May 2026 15:53:55 +1200 Subject: [PATCH 3/5] Refactor --- src/Sentry.Extensions.Logging/SentryStructuredLogger.cs | 7 +------ src/Sentry.Serilog/SentrySink.Structured.cs | 7 +------ src/Sentry/Internal/DefaultSentryStructuredLogger.cs | 3 ++- src/Sentry/SentryLog.cs | 7 +++---- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index f6907dffb2..43cd81315e 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -88,12 +88,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except }; var scope = _hub.GetScope(); - if (scope?.Sdk is { } scopeSdk) - { - scopeSdk.Name = _sdk.Name; - scopeSdk.Version = _sdk.Version; - } - log.SetDefaultAttributes(_options, scope); + log.SetDefaultAttributes(_options, scope, _sdk); log.SetOrigin("auto.log.extensions_logging"); if (_categoryName is not null) diff --git a/src/Sentry.Serilog/SentrySink.Structured.cs b/src/Sentry.Serilog/SentrySink.Structured.cs index 2fdaba0d42..29e534bdfb 100644 --- a/src/Sentry.Serilog/SentrySink.Structured.cs +++ b/src/Sentry.Serilog/SentrySink.Structured.cs @@ -18,12 +18,7 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve }; var scope = hub.GetScope(); - if (scope?.Sdk is { } scopeSdk) - { - scopeSdk.Name = Sdk.Name; - scopeSdk.Version = Sdk.Version; - } - log.SetDefaultAttributes(options, scope); + log.SetDefaultAttributes(options, scope, Sdk); log.SetOrigin("auto.log.serilog"); foreach (var attribute in attributes) diff --git a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs index fabeb326d2..792205f863 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -75,7 +75,8 @@ private protected override void CaptureLog(SentryLogLevel level, string template return; } - log.SetDefaultAttributes(_options, _hub.GetScope()); + var scope = _hub.GetScope(); + log.SetDefaultAttributes(_options, scope, scope?.Sdk); CaptureLog(log); } diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index bfa71a4771..2f126a894f 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -172,7 +172,7 @@ internal void SetAttribute(string key, int value) _attributes[key] = new SentryAttribute(value, "integer"); } - internal void SetDefaultAttributes(SentryOptions options, Scope? scope) + internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion? sdk) { // Core Attributes var environment = options.SettingLocator.GetEnvironment(); @@ -184,12 +184,11 @@ internal void SetDefaultAttributes(SentryOptions options, Scope? scope) SetAttribute("sentry.release", release); } - var sdk = scope?.Sdk ?? SdkVersion.Instance; - if (sdk.Name is { } name) + if (sdk?.Name is { } name) { SetAttribute("sentry.sdk.name", name); } - if (sdk.Version is { } version) + if (sdk?.Version is { } version) { SetAttribute("sentry.sdk.version", version); } From bf1911eceacd5a7b2fb9bc841ee794418f554790 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 11 May 2026 15:57:52 +1200 Subject: [PATCH 4/5] Micro tweak --- src/Sentry/Internal/DefaultSentryStructuredLogger.cs | 2 +- src/Sentry/SentryLog.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs index 792205f863..f798e33ede 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -76,7 +76,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template } var scope = _hub.GetScope(); - log.SetDefaultAttributes(_options, scope, scope?.Sdk); + log.SetDefaultAttributes(_options, scope); CaptureLog(log); } diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index 2f126a894f..317653d067 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -172,7 +172,7 @@ internal void SetAttribute(string key, int value) _attributes[key] = new SentryAttribute(value, "integer"); } - internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion? sdk) + internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion? sdk = null) { // Core Attributes var environment = options.SettingLocator.GetEnvironment(); @@ -184,6 +184,7 @@ internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersi SetAttribute("sentry.release", release); } + sdk ??= scope?.Sdk; if (sdk?.Name is { } name) { SetAttribute("sentry.sdk.name", name); From 1a5d694e854206160cb81fd8b19e272ea9b52467 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 11 May 2026 16:27:55 +1200 Subject: [PATCH 5/5] Fix fallback (preserves previous behaviour) --- src/Sentry/SentryLog.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index 317653d067..1770c96daa 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -184,12 +184,12 @@ internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersi SetAttribute("sentry.release", release); } - sdk ??= scope?.Sdk; - if (sdk?.Name is { } name) + sdk ??= scope?.Sdk ?? SdkVersion.Instance; + if (sdk.Name is { } name) { SetAttribute("sentry.sdk.name", name); } - if (sdk?.Version is { } version) + if (sdk.Version is { } version) { SetAttribute("sentry.sdk.version", version); }