Skip to content

Commit dd39fae

Browse files
committed
fix(logs): incorrectly serializing attributes
1 parent 6822b23 commit dd39fae

File tree

4 files changed

+248
-10
lines changed

4 files changed

+248
-10
lines changed

src/Sentry/Protocol/SentryAttribute.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ private static void WriteAttributeValue(Utf8JsonWriter writer, object value)
8080
writer.WriteBoolean("value", boolean);
8181
writer.WriteString("type", "boolean");
8282
}
83+
else if (value is int int32)
84+
{
85+
writer.WriteNumber("value", int32);
86+
writer.WriteString("type", "integer");
87+
}
8388
else if (value is long int64)
8489
{
8590
writer.WriteNumber("value", int64);

src/Sentry/Protocol/SentryLog.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,6 @@ internal void SetAttributes(SentryOptions options)
219219
SetAttribute("sentry.release", release);
220220
}
221221

222-
if (ParentSpanId.HasValue)
223-
{
224-
SetAttribute("sentry.trace.parent_span_id", ParentSpanId.Value.ToString());
225-
}
226-
227222
SetAttribute("sentry.sdk.name", Constants.SdkName);
228223
if (SdkVersion.Instance.Version is { } version)
229224
{
@@ -239,17 +234,20 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
239234
writer.WriteStartObject();
240235

241236
writer.WriteNumber("timestamp", Timestamp.ToUnixTimeSeconds());
242-
writer.WriteString("trace_id", TraceId);
243237

244238
var (severityText, severityNumber) = Level.ToSeverityTextAndOptionalSeverityNumber();
245239
writer.WriteString("level", severityText);
240+
241+
writer.WriteString("body", Message);
242+
243+
writer.WritePropertyName("trace_id");
244+
TraceId.WriteTo(writer, logger);
245+
246246
if (severityNumber.HasValue)
247247
{
248248
writer.WriteNumber("severity_number", severityNumber.Value);
249249
}
250250

251-
writer.WriteString("body", Message);
252-
253251
writer.WritePropertyName("attributes");
254252
writer.WriteStartObject();
255253

@@ -262,7 +260,7 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
262260
{
263261
for (var index = 0; index < Parameters.Length; index++)
264262
{
265-
SentryAttributeSerializer.WriteAttribute(writer, $"sentry.message.parameters.{index}", Parameters[index]);
263+
SentryAttributeSerializer.WriteAttribute(writer, $"sentry.message.parameter.{index}", Parameters[index]);
266264
}
267265
}
268266

@@ -271,6 +269,16 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
271269
SentryAttributeSerializer.WriteAttribute(writer, attribute.Key, attribute.Value);
272270
}
273271

272+
if (ParentSpanId.HasValue)
273+
{
274+
writer.WritePropertyName("sentry.trace.parent_span_id");
275+
writer.WriteStartObject();
276+
writer.WritePropertyName("value");
277+
ParentSpanId.Value.WriteTo(writer, logger);
278+
writer.WriteString("type", "string");
279+
writer.WriteEndObject();
280+
}
281+
274282
writer.WriteEndObject();
275283

276284
writer.WriteEndObject();

src/Sentry/SentryStructuredLogger.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ private void CaptureLog(SentryLogLevel level, string template, object[]? paramet
134134
Parameters = ImmutableArray.Create(parameters),
135135
ParentSpanId = parentSpanId,
136136
};
137-
log.SetAttributes(_options);
138137

139138
try
140139
{
@@ -148,6 +147,8 @@ private void CaptureLog(SentryLogLevel level, string template, object[]? paramet
148147
return;
149148
}
150149

150+
log.SetAttributes(_options);
151+
151152
var configuredLog = log;
152153
if (_options.BeforeSendLogInternal is { } beforeSendLog)
153154
{
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
using System.Text.Encodings.Web;
2+
3+
namespace Sentry.Tests.Protocol;
4+
5+
/// <summary>
6+
/// <see href="https://develop.sentry.dev/sdk/telemetry/logs/"/>
7+
/// </summary>
8+
public class SentryLogTests
9+
{
10+
private static readonly DateTimeOffset Timestamp = new(2025, 04, 22, 14, 51, 00, TimeSpan.FromHours(2));
11+
private static readonly SentryId TraceId = SentryId.Create();
12+
private static readonly SpanId? ParentSpanId = SpanId.Create();
13+
14+
private static readonly ISystemClock Clock = new MockClock(Timestamp);
15+
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
16+
{
17+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
18+
WriteIndented = true,
19+
};
20+
21+
private readonly IDiagnosticLogger _output;
22+
23+
public SentryLogTests(ITestOutputHelper output)
24+
{
25+
_output = new TestOutputDiagnosticLogger(output);
26+
}
27+
28+
[Fact]
29+
public void WriteTo_Envelope_MinimalSerializedSentryLog()
30+
{
31+
var options = new SentryOptions
32+
{
33+
Environment = "my-environment",
34+
Release = "my-release",
35+
};
36+
37+
var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Trace, "message");
38+
log.SetAttributes(options);
39+
40+
var envelope = Envelope.FromLog(log);
41+
42+
using var stream = new MemoryStream();
43+
envelope.Serialize(stream, _output, Clock);
44+
stream.Seek(0, SeekOrigin.Begin);
45+
using var reader = new StreamReader(stream);
46+
var header = JsonDocument.Parse(reader.ReadLine()!);
47+
var item = JsonDocument.Parse(reader.ReadLine()!);
48+
var payload = JsonDocument.Parse(reader.ReadLine()!);
49+
50+
reader.EndOfStream.Should().BeTrue();
51+
52+
JsonSerializer.Serialize(header, JsonSerializerOptions).Should().Be($$"""
53+
{
54+
"sdk": {
55+
"name": "{{SdkVersion.Instance.Name}}",
56+
"version": "{{SdkVersion.Instance.Version}}"
57+
},
58+
"sent_at": "{{Timestamp.Format()}}"
59+
}
60+
""");
61+
62+
JsonSerializer.Serialize(item, JsonSerializerOptions).Should().Match("""
63+
{
64+
"type": "log",
65+
"item_count": 1,
66+
"content_type": "application/vnd.sentry.items.log+json",
67+
"length": ?*
68+
}
69+
""");
70+
71+
JsonSerializer.Serialize(payload, JsonSerializerOptions).Should().Be($$"""
72+
{
73+
"items": [
74+
{
75+
"timestamp": {{Timestamp.ToUnixTimeSeconds()}},
76+
"level": "trace",
77+
"body": "message",
78+
"trace_id": "{{TraceId.ToString()}}",
79+
"attributes": {
80+
"sentry.environment": {
81+
"value": "my-environment",
82+
"type": "string"
83+
},
84+
"sentry.release": {
85+
"value": "my-release",
86+
"type": "string"
87+
},
88+
"sentry.sdk.name": {
89+
"value": "{{SdkVersion.Instance.Name}}",
90+
"type": "string"
91+
},
92+
"sentry.sdk.version": {
93+
"value": "{{SdkVersion.Instance.Version}}",
94+
"type": "string"
95+
}
96+
}
97+
}
98+
]
99+
}
100+
""");
101+
}
102+
103+
[Fact]
104+
public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog()
105+
{
106+
var options = new SentryOptions
107+
{
108+
Environment = "my-environment",
109+
Release = "my-release",
110+
};
111+
112+
var log = new SentryLog(Timestamp, TraceId, (SentryLogLevel)24, "message")
113+
{
114+
Template = "template",
115+
Parameters = ImmutableArray.Create<object>("string", false, 1, 2.2),
116+
ParentSpanId = ParentSpanId,
117+
};
118+
log.SetAttribute("string-attribute", "string-value");
119+
log.SetAttribute("boolean-attribute", true);
120+
log.SetAttribute("integer-attribute", 3);
121+
log.SetAttribute("double-attribute", 4.4);
122+
log.SetAttributes(options);
123+
124+
var envelope = EnvelopeItem.FromLog(log);
125+
126+
using var stream = new MemoryStream();
127+
envelope.Serialize(stream, _output);
128+
stream.Seek(0, SeekOrigin.Begin);
129+
using var reader = new StreamReader(stream);
130+
var item = JsonDocument.Parse(reader.ReadLine()!);
131+
var payload = JsonDocument.Parse(reader.ReadLine()!);
132+
133+
reader.EndOfStream.Should().BeTrue();
134+
135+
JsonSerializer.Serialize(item, JsonSerializerOptions).Should().Match("""
136+
{
137+
"type": "log",
138+
"item_count": 1,
139+
"content_type": "application/vnd.sentry.items.log+json",
140+
"length": ?*
141+
}
142+
""");
143+
144+
JsonSerializer.Serialize(payload, JsonSerializerOptions).Should().Be($$"""
145+
{
146+
"items": [
147+
{
148+
"timestamp": {{Timestamp.ToUnixTimeSeconds()}},
149+
"level": "fatal",
150+
"body": "message",
151+
"trace_id": "{{TraceId.ToString()}}",
152+
"severity_number": 24,
153+
"attributes": {
154+
"sentry.message.template": {
155+
"value": "template",
156+
"type": "string"
157+
},
158+
"sentry.message.parameter.0": {
159+
"value": "string",
160+
"type": "string"
161+
},
162+
"sentry.message.parameter.1": {
163+
"value": false,
164+
"type": "boolean"
165+
},
166+
"sentry.message.parameter.2": {
167+
"value": 1,
168+
"type": "integer"
169+
},
170+
"sentry.message.parameter.3": {
171+
"value": 2.2,
172+
"type": "double"
173+
},
174+
"string-attribute": {
175+
"value": "string-value",
176+
"type": "string"
177+
},
178+
"boolean-attribute": {
179+
"value": true,
180+
"type": "boolean"
181+
},
182+
"integer-attribute": {
183+
"value": 3,
184+
"type": "integer"
185+
},
186+
"double-attribute": {
187+
"value": 4.4,
188+
"type": "double"
189+
},
190+
"sentry.environment": {
191+
"value": "my-environment",
192+
"type": "string"
193+
},
194+
"sentry.release": {
195+
"value": "my-release",
196+
"type": "string"
197+
},
198+
"sentry.sdk.name": {
199+
"value": "{{SdkVersion.Instance.Name}}",
200+
"type": "string"
201+
},
202+
"sentry.sdk.version": {
203+
"value": "{{SdkVersion.Instance.Version}}",
204+
"type": "string"
205+
},
206+
"sentry.trace.parent_span_id": {
207+
"value": "{{ParentSpanId.ToString()}}",
208+
"type": "string"
209+
}
210+
}
211+
}
212+
]
213+
}
214+
""");
215+
}
216+
}
217+
218+
file static class JsonFormatterExtensions
219+
{
220+
public static string Format(this DateTimeOffset value)
221+
{
222+
return value.ToString("yyyy-MM-ddTHH:mm:sszzz", DateTimeFormatInfo.InvariantInfo);
223+
}
224+
}

0 commit comments

Comments
 (0)