diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f1d0ef539..1d451aa11a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - Common tags such as `Environment` and `Release` and custom event processors are all now correctly applied to CaptureFeedback events ([#4942](https://github.com/getsentry/sentry-dotnet/pull/4942)) +- Include `Data` set via `ITransactionTracer` in `SentryTransaction` ([#4148](https://github.com/getsentry/sentry-dotnet/pull/4148)) ## 6.2.0 diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs index 96b28bf81b..8ccd5c2960 100644 --- a/src/Sentry/Internal/Extensions/JsonExtensions.cs +++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs @@ -145,6 +145,23 @@ public static void Deconstruct(this JsonProperty jsonProperty, out string name, return result; } + public static ConcurrentDictionary? GetConcurrentDictionaryOrNull(this JsonElement json) + { + if (json.ValueKind != JsonValueKind.Object) + { + return null; + } + + var result = new ConcurrentDictionary(); + + foreach (var (name, value) in json.EnumerateObject()) + { + result[name] = value.GetDynamicOrNull(); + } + + return result; + } + public static Dictionary? GetStringDictionaryOrNull(this JsonElement json) { if (json.ValueKind != JsonValueKind.Object) diff --git a/src/Sentry/Protocol/Trace.cs b/src/Sentry/Protocol/Trace.cs index 8878aab170..7fe0410f14 100644 --- a/src/Sentry/Protocol/Trace.cs +++ b/src/Sentry/Protocol/Trace.cs @@ -50,7 +50,7 @@ internal set /// public bool? IsSampled { get; internal set; } - private Dictionary _data = new(); + private ConcurrentDictionary _data = new(); /// /// Get the metadata @@ -79,7 +79,7 @@ public void SetData(string key, object? value) Origin = Origin, Status = Status, IsSampled = IsSampled, - _data = _data.ToDict() + _data = new ConcurrentDictionary(_data), }; /// @@ -137,7 +137,7 @@ public static Trace FromJson(JsonElement json) var description = json.GetPropertyOrNull("description")?.GetString(); var status = json.GetPropertyOrNull("status")?.GetString()?.Replace("_", "").ParseEnum(); var isSampled = json.GetPropertyOrNull("sampled")?.GetBoolean(); - var data = json.GetPropertyOrNull("data")?.GetDictionaryOrNull() ?? new(); + var data = json.GetPropertyOrNull("data")?.GetConcurrentDictionaryOrNull() ?? new(); return new Trace { @@ -149,7 +149,7 @@ public static Trace FromJson(JsonElement json) Description = description, Status = status, IsSampled = isSampled, - _data = data + _data = data, }; } } diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index 905173a9b4..54464668d8 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -153,15 +153,12 @@ public IReadOnlyList Fingerprint /// public IReadOnlyCollection Breadcrumbs => _breadcrumbs; - private readonly ConcurrentDictionary _data = new(); - /// [Obsolete("Use Data")] - public IReadOnlyDictionary Extra => _data; + public IReadOnlyDictionary Extra => _contexts.Trace.Data; /// - public IReadOnlyDictionary Data => _data; - + public IReadOnlyDictionary Data => _contexts.Trace.Data; private readonly ConcurrentDictionary _tags = new(); @@ -271,10 +268,10 @@ internal TransactionTracer(IHub hub, ITransactionContext context, TimeSpan? idle /// [Obsolete("Use SetData")] - public void SetExtra(string key, object? value) => _data[key] = value; + public void SetExtra(string key, object? value) => _contexts.Trace.SetData(key, value); /// - public void SetData(string key, object? value) => _data[key] = value; + public void SetData(string key, object? value) => _contexts.Trace.SetData(key, value); /// public void SetTag(string key, string value) => _tags[key] = value; diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt index 019cc1fb2d..fcb2b33a90 100644 --- a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet10_0.verified.txt @@ -22,7 +22,11 @@ Origin: auto.http.aspnetcore, Description: , Status: Ok, - IsSampled: true + IsSampled: true, + Data: { + http.request.method: GET, + http.response.status_code: 200 + } } }, User: { @@ -80,7 +84,11 @@ route.controller: Version, route.version: 1.1 }, - IsFinished: true + IsFinished: true, + Data: { + http.request.method: GET, + http.response.status_code: 200 + } } } ] diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet8_0.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet8_0.verified.txt index 019cc1fb2d..fcb2b33a90 100644 --- a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet8_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet8_0.verified.txt @@ -22,7 +22,11 @@ Origin: auto.http.aspnetcore, Description: , Status: Ok, - IsSampled: true + IsSampled: true, + Data: { + http.request.method: GET, + http.response.status_code: 200 + } } }, User: { @@ -80,7 +84,11 @@ route.controller: Version, route.version: 1.1 }, - IsFinished: true + IsFinished: true, + Data: { + http.request.method: GET, + http.response.status_code: 200 + } } } ] diff --git a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet9_0.verified.txt b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet9_0.verified.txt index 019cc1fb2d..fcb2b33a90 100644 --- a/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet9_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/WebIntegrationTests.Versioning.DotNet9_0.verified.txt @@ -22,7 +22,11 @@ Origin: auto.http.aspnetcore, Description: , Status: Ok, - IsSampled: true + IsSampled: true, + Data: { + http.request.method: GET, + http.response.status_code: 200 + } } }, User: { @@ -80,7 +84,11 @@ route.controller: Version, route.version: 1.1 }, - IsFinished: true + IsFinished: true, + Data: { + http.request.method: GET, + http.response.status_code: 200 + } } } ] diff --git a/test/Sentry.Tests/Protocol/Context/TraceTests.cs b/test/Sentry.Tests/Protocol/Context/TraceTests.cs index 27372d0d9a..8aebb4e999 100644 --- a/test/Sentry.Tests/Protocol/Context/TraceTests.cs +++ b/test/Sentry.Tests/Protocol/Context/TraceTests.cs @@ -38,6 +38,7 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject() SpanId = SpanId.Parse("2000000000000000"), TraceId = SentryId.Parse("75302ac48a024bde9a3b3734a82e36c8") }; + trace.SetData("route", "home"); // Act var actual = trace.ToJsonString(_testOutputLogger, indented: true); @@ -52,7 +53,10 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject() "trace_id": "75302ac48a024bde9a3b3734a82e36c8", "op": "op123", "origin": "auto.abc.def.ghi", - "status": "aborted" + "status": "aborted", + "data": { + "route": "home" + } } """, actual); @@ -72,6 +76,7 @@ public void Clone_CopyValues() SpanId = SpanId.Parse("2000000000000000"), TraceId = SentryId.Parse("75302ac48a024bde9a3b3734a82e36c8") }; + trace.SetData("previous_route", "home"); // Act var clone = trace.Clone(); @@ -84,6 +89,8 @@ public void Clone_CopyValues() Assert.Equal(trace.ParentSpanId, clone.ParentSpanId); Assert.Equal(trace.SpanId, clone.SpanId); Assert.Equal(trace.TraceId, clone.TraceId); + Assert.Equal(trace.Data, clone.Data); + Assert.NotSame(trace.Data, clone.Data); } [Fact] diff --git a/test/Sentry.Tests/Protocol/SentryTransactionTests.cs b/test/Sentry.Tests/Protocol/SentryTransactionTests.cs index eb1eaf1bbf..604e1cdda7 100644 --- a/test/Sentry.Tests/Protocol/SentryTransactionTests.cs +++ b/test/Sentry.Tests/Protocol/SentryTransactionTests.cs @@ -258,7 +258,7 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject() // Act var finalTransaction = new SentryTransaction(transaction); - var actualString = finalTransaction.ToJsonString(_testOutputLogger); + var actualString = finalTransaction.ToJsonString(_testOutputLogger, indented: true); var actual = Json.Parse(actualString, SentryTransaction.FromJson); // Assert @@ -278,6 +278,30 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject() return o; }); + + Assert.Contains($$""" + "contexts": { + ".NET Framework": { + ".NET Framework": "\u0022v2.0.50727\u0022, \u0022v3.0\u0022, \u0022v3.5\u0022", + ".NET Framework Client": "\u0022v4.8\u0022, \u0022v4.0.0.0\u0022", + ".NET Framework Full": "\u0022v4.8\u0022" + }, + "context_key": "context_value", + "trace": { + "type": "trace", + "span_id": "{{context.SpanId}}", + "parent_span_id": "{{context.ParentSpanId}}", + "trace_id": "{{context.TraceId}}", + "op": "op123", + "origin": "auto.serialize.transaction", + "description": "desc123", + "status": "aborted", + "data": { + "extra_key": "extra_value" + } + } + } + """, actualString); } [Fact]