diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index 6567811762..58d52e5327 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -87,7 +87,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except SpanId = spanId, }; - log.SetDefaultAttributes(_options, _sdk); + log.SetDefaultAttributes(_options, _hub.GetScope(), _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 cdff9166ac..bf25ffb692 100644 --- a/src/Sentry.Serilog/SentrySink.Structured.cs +++ b/src/Sentry.Serilog/SentrySink.Structured.cs @@ -17,7 +17,7 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve SpanId = spanId, }; - log.SetDefaultAttributes(options, Sdk); + log.SetDefaultAttributes(options, hub.GetScope(), 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 3e42cc7005..d3b1326a2a 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -69,7 +69,12 @@ private protected override void CaptureLog(SentryLogLevel level, string template } var scope = _hub.GetScope(); - log.SetDefaultAttributes(_options, scope?.Sdk ?? SdkVersion.Instance); + var sdk = scope?.Sdk; + if (sdk is null || (sdk.Name is null && sdk.Version is null)) + { + sdk = SdkVersion.Instance; + } + log.SetDefaultAttributes(_options, scope, sdk); CaptureLog(log); } diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index c57a08a84d..26e64aaae1 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -24,8 +24,8 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel le TraceId = traceId; Level = level; Message = message; - // 7 is the number of built-in attributes, so we start with that. - _attributes = new Dictionary(7); + // we currently set up to 18 default attributes, the next prime number is 23 + _attributes = new Dictionary(23); } /// @@ -163,7 +163,12 @@ 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) + { + SetDefaultAttributes(options, scope, scope.Sdk); + } + + internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion sdk) { var environment = options.SettingLocator.GetEnvironment(); SetAttribute("sentry.environment", environment); @@ -182,6 +187,72 @@ internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) { SetAttribute("sentry.sdk.version", version); } + + if (scope is null) + { + return; + } + + if (scope.PropagationContext._dynamicSamplingContext is { } dynamicSamplingContext) + { + if (dynamicSamplingContext.Items.TryGetValue("replay_id", out var replayId)) + { + SetAttribute("sentry.replay_id", replayId); + } + } + + if (scope.User.Id is { } userId) + { + SetAttribute("user.id", userId); + } + if (scope.User.Username is { } userName) + { + SetAttribute("user.name", userName); + } + if (scope.User.Email is { } userEmail) + { + SetAttribute("user.email", userEmail); + } + + if (scope.Contexts.Browser.Name is { } browserName) + { + SetAttribute("browser.name", browserName); + } + if (scope.Contexts.Browser.Version is { } browserVersion) + { + SetAttribute("browser.version", browserVersion); + } + + var serverAddress = options.ServerName; + if (!string.IsNullOrEmpty(serverAddress)) + { + SetAttribute("server.address", serverAddress); + } + else if (options.SendDefaultPii) + { + SetAttribute("server.address", Environment.MachineName); + } + + if (scope.Contexts.OperatingSystem.Name is { } osName) + { + SetAttribute("os.name", osName); + } + if (scope.Contexts.OperatingSystem.Version is { } osVersion) + { + SetAttribute("os.version", osVersion); + } + if (scope.Contexts.Device.Brand is { } deviceBrand) + { + SetAttribute("device.brand", deviceBrand); + } + if (scope.Contexts.Device.Model is { } deviceModel) + { + SetAttribute("device.model", deviceModel); + } + if (scope.Contexts.Device.Family is { } deviceFamily) + { + SetAttribute("device.family", deviceFamily); + } } internal void SetOrigin(string origin) diff --git a/test/Sentry.Tests/SentryLogTests.cs b/test/Sentry.Tests/SentryLogTests.cs index fe14705ad1..b5971aa5d8 100644 --- a/test/Sentry.Tests/SentryLogTests.cs +++ b/test/Sentry.Tests/SentryLogTests.cs @@ -30,10 +30,13 @@ public void Protocol_Default_VerifyAttributes() Environment = "my-environment", Release = "my-release", }; - var sdk = new SdkVersion + var scope = new Scope(options) { - Name = "Sentry.Test.SDK", - Version = "1.2.3-test+Sentry" + Sdk = + { + Name = "Sentry.Test.SDK", + Version = "1.2.3-test+Sentry", + } }; var log = new SentryLog(Timestamp, TraceId, (SentryLogLevel)24, "message") @@ -43,7 +46,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); @@ -63,13 +66,87 @@ 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(); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Protocol_Default_VerifyAdditionalAttributes(bool hasAdditionalDefaultAttributes) + { + var options = new SentryOptions + { + Environment = "my-environment", + Release = "my-release", + }; + var scope = new Scope(options); + + if (hasAdditionalDefaultAttributes) + { + options.Dsn = ValidDsn; + + var replaySession = Substitute.For(); + replaySession.ActiveReplayId.Returns(SentryId.Parse("f18176ecbb544e549fd23fbbe39064cc")); + _ = scope.PropagationContext.GetOrCreateDynamicSamplingContext(options, replaySession); + + scope.User = new SentryUser + { + Id = "my-user-id", + Username = "my-user-name", + Email = "my-user-email", + }; + scope.Contexts.Browser.Name = "my-browser-name"; + scope.Contexts.Browser.Version = "my-browser-version"; + options.ServerName = "my-server-address"; + scope.Contexts.OperatingSystem.Name = "my-os-name"; + scope.Contexts.OperatingSystem.Version = "my-os-version"; + scope.Contexts.Device.Brand = "my-device-brand"; + scope.Contexts.Device.Model = "my-device-model"; + scope.Contexts.Device.Family = "my-device-family"; + } + + var log = new SentryLog(Timestamp, TraceId, (SentryLogLevel)24, "message") + { + Template = "template", + Parameters = ImmutableArray.Create(new KeyValuePair("param", "params")), + SpanId = SpanId, + }; + log.SetDefaultAttributes(options, scope); + + log.TryGetAttribute("sentry.replay_id", out string replayId).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("user.id", out string userId).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("user.name", out string userName).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("user.email", out string userEmail).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("browser.name", out string browserName).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("browser.version", out string browserVersion).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("server.address", out string serverAddress).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("os.name", out string osName).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("os.version", out string osVersion).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("device.brand", out string deviceBrand).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("device.model", out string deviceModel).Should().Be(hasAdditionalDefaultAttributes); + log.TryGetAttribute("device.family", out string deviceFamily).Should().Be(hasAdditionalDefaultAttributes); + + if (hasAdditionalDefaultAttributes) + { + replayId.Should().Be("f18176ecbb544e549fd23fbbe39064cc"); + userId.Should().Be("my-user-id"); + userName.Should().Be("my-user-name"); + userEmail.Should().Be("my-user-email"); + browserName.Should().Be("my-browser-name"); + browserVersion.Should().Be("my-browser-version"); + serverAddress.Should().Be("my-server-address"); + osName.Should().Be("my-os-name"); + osVersion.Should().Be("my-os-version"); + deviceBrand.Should().Be("my-device-brand"); + deviceModel.Should().Be("my-device-model"); + deviceFamily.Should().Be("my-device-family"); + } + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -101,10 +178,12 @@ public void WriteTo_Envelope_MinimalSerializedSentryLog() { Environment = "my-environment", Release = "my-release", + SendDefaultPii = false, }; + var scope = new Scope(options); var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Trace, "message"); - log.SetDefaultAttributes(options, new SdkVersion()); + log.SetDefaultAttributes(options, scope); var envelope = Envelope.FromLog(new StructuredLog([log])); @@ -170,6 +249,46 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog() { Environment = "my-environment", Release = "my-release", + ServerName = "my-server-address", + }; + var replaySession = Substitute.For(); + var replayId = SentryId.Create(); + replaySession.ActiveReplayId.Returns(replayId); + var dynamicSamplingContext = DynamicSamplingContext.Empty(); + dynamicSamplingContext.SetReplayId(replaySession); + var propagationContext = new SentryPropagationContext(TraceId, SpanId!.Value, dynamicSamplingContext); + var scope = new Scope(options, propagationContext) + { + Sdk = + { + Name = "Sentry.Test.SDK", + Version = "1.2.3-test+Sentry", + }, + User = new SentryUser + { + Id = "my-user-id", + Username = "my-user-name", + Email = "my-user-email", + }, + Contexts = new SentryContexts + { + Browser = + { + Name = "my-browser-name", + Version = "my-browser-version", + }, + OperatingSystem = + { + Name = "my-os-name", + Version = "my-os-version", + }, + Device = + { + Brand = "my-device-brand", + Model = "my-device-model", + Family = "my-device-family", + }, + }, }; var log = new SentryLog(Timestamp, TraceId, (SentryLogLevel)24, "message") @@ -182,7 +301,8 @@ 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" }); + log.SetDefaultAttributes(options, scope); + log.SetOrigin("auto.log.sentry_tests"); var envelope = EnvelopeItem.FromLog(new StructuredLog([log])); @@ -266,6 +386,58 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog() "sentry.sdk.version": { "value": "1.2.3-test+Sentry", "type": "string" + }, + "sentry.replay_id": { + "value": "{{replayId.ToString()}}", + "type": "string" + }, + "user.id": { + "value": "my-user-id", + "type": "string" + }, + "user.name": { + "value": "my-user-name", + "type": "string" + }, + "user.email": { + "value": "my-user-email", + "type": "string" + }, + "browser.name": { + "value": "my-browser-name", + "type": "string" + }, + "browser.version": { + "value": "my-browser-version", + "type": "string" + }, + "server.address": { + "value": "my-server-address", + "type": "string" + }, + "os.name": { + "value": "my-os-name", + "type": "string" + }, + "os.version": { + "value": "my-os-version", + "type": "string" + }, + "device.brand": { + "value": "my-device-brand", + "type": "string" + }, + "device.model": { + "value": "my-device-model", + "type": "string" + }, + "device.family": { + "value": "my-device-family", + "type": "string" + }, + "sentry.origin": { + "value": "auto.log.sentry_tests", + "type": "string" } } }