diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index e4f5b500d9..43cd81315e 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -87,7 +87,8 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except SpanId = spanId, }; - log.SetDefaultAttributes(_options, _sdk); + var scope = _hub.GetScope(); + 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 535c4d1656..29e534bdfb 100644 --- a/src/Sentry.Serilog/SentrySink.Structured.cs +++ b/src/Sentry.Serilog/SentrySink.Structured.cs @@ -17,7 +17,8 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve SpanId = spanId, }; - log.SetDefaultAttributes(options, Sdk); + var scope = hub.GetScope(); + 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 d65f3ca9aa..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?.Sdk ?? SdkVersion.Instance); + log.SetDefaultAttributes(_options, scope); CaptureLog(log); } diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index 3cb503cdba..1770c96daa 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) + internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion? sdk = null) { + // Core Attributes var environment = options.SettingLocator.GetEnvironment(); SetAttribute("sentry.environment", environment); @@ -183,6 +184,7 @@ internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) SetAttribute("sentry.release", release); } + sdk ??= scope?.Sdk ?? SdkVersion.Instance; if (sdk.Name is { } name) { SetAttribute("sentry.sdk.name", name); @@ -191,6 +193,33 @@ internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) { 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) + { + 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.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])); 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");