Skip to content

Commit 7d4fec8

Browse files
committed
fix how we collect values for nullable types
1 parent 2b0da8e commit 7d4fec8

5 files changed

Lines changed: 126 additions & 7 deletions

File tree

tracer/src/Datadog.Trace/Debugger/Snapshots/Redaction.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,10 @@ private Redaction()
193193

194194
internal static bool IsSafeToCallToString(Type type)
195195
{
196-
return TypeExtensions.IsSimple(type) ||
197-
AllowedTypesSafeToCallToString.Contains(type) ||
196+
var effectiveType = Nullable.GetUnderlyingType(type) ?? type;
197+
198+
return TypeExtensions.IsSimple(effectiveType) ||
199+
AllowedTypesSafeToCallToString.Contains(effectiveType) ||
198200
IsSupportedCollection(type);
199201
}
200202

tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSnapshotCreatorTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,29 @@ public async Task SpecialType_StringBuilder()
172172
await ValidateSingleValue(new StringBuilder("hi from stringbuilder"));
173173
}
174174

175+
[Fact]
176+
public void SpecialType_NullableSafeToStringTypes_DoNotRecurseIntoFields()
177+
{
178+
var expectedDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
179+
var expectedDuration = TimeSpan.FromSeconds(3);
180+
var snapshot = JObject.Parse(SnapshotHelper.GenerateSnapshot(new NullableSafeToStringHolder(), prettify: false));
181+
182+
var dateToken = snapshot.SelectToken("debugger.snapshot.captures.return.locals.local0.fields.Date");
183+
Assert.NotNull(dateToken);
184+
Assert.Equal("Nullable`1", dateToken["type"]?.Value<string>());
185+
Assert.Equal(expectedDate.ToString(), dateToken["value"]?.Value<string>());
186+
Assert.Null(dateToken["fields"]);
187+
188+
var durationToken = snapshot.SelectToken("debugger.snapshot.captures.return.locals.local0.fields.Duration");
189+
Assert.NotNull(durationToken);
190+
Assert.Equal("Nullable`1", durationToken["type"]?.Value<string>());
191+
Assert.Equal(expectedDuration.ToString(), durationToken["value"]?.Value<string>());
192+
Assert.Null(durationToken["fields"]);
193+
194+
Assert.Equal("Bar", snapshot.SelectToken("logger.name")?.Value<string>());
195+
Assert.Equal("Foo", snapshot.SelectToken("logger.method")?.Value<string>());
196+
}
197+
175198
[Fact]
176199
public async Task SpecialType_LazyUninitialized()
177200
{
@@ -1220,6 +1243,13 @@ private class ClassWithLotsOFields
12201243
private readonly int _numField1000 = 1000;
12211244
}
12221245

1246+
private class NullableSafeToStringHolder
1247+
{
1248+
public DateTime? Date { get; } = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
1249+
1250+
public TimeSpan? Duration { get; } = TimeSpan.FromSeconds(3);
1251+
}
1252+
12231253
private class CollectionAtMaxDepth
12241254
{
12251255
public NestedCollectionLevel1 Inner { get; } = new();

tracer/test/Datadog.Trace.Tests/Debugger/DynamicInstrumentationTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,14 +790,14 @@ public void Dispose()
790790

791791
private class SnapshotUploaderMock : UploaderMock, ISnapshotUploader
792792
{
793-
public void Add(string probeId, string snapshot)
793+
public void Add(string probeId, string? snapshot)
794794
{
795795
}
796796
}
797797

798798
private class LogUploaderMock : UploaderMock, ISnapshotUploader
799799
{
800-
public void Add(string probeId, string snapshot)
800+
public void Add(string probeId, string? snapshot)
801801
{
802802
}
803803
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// <copyright file="SnapshotExplorationTestSinkTests.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System;
7+
using System.IO;
8+
using System.Linq;
9+
using Datadog.Trace.Debugger;
10+
using Datadog.Trace.Debugger.Sink;
11+
using Datadog.Trace.Debugger.Snapshots;
12+
using FluentAssertions;
13+
using Xunit;
14+
15+
namespace Datadog.Trace.Tests.Debugger
16+
{
17+
public class SnapshotExplorationTestSinkTests
18+
{
19+
[Fact]
20+
public void Add_UsesRootLogger_WhenSnapshotContainsNestedLoggerProperty()
21+
{
22+
var reportDirectory = Path.Combine(Path.GetTempPath(), nameof(SnapshotExplorationTestSinkTests), Guid.NewGuid().ToString("N"));
23+
Directory.CreateDirectory(reportDirectory);
24+
25+
try
26+
{
27+
const string snapshot = """
28+
{
29+
"debugger": {
30+
"snapshot": {
31+
"captures": {
32+
"return": {
33+
"locals": {
34+
"local0": {
35+
"fields": {
36+
"logger": {
37+
"type": "Nested.Logger",
38+
"value": "ignored"
39+
}
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}
46+
},
47+
"logger": {
48+
"name": "Top.Level.Type",
49+
"method": "DoWork"
50+
}
51+
}
52+
""";
53+
54+
using (var sink = new SnapshotExplorationTestSink(reportDirectory, new SnapshotSlicer(DebuggerSettings.DefaultMaxDepthToSerialize, 1024 * 1024)))
55+
{
56+
sink.Add("probe-id", snapshot);
57+
}
58+
59+
var csvPath = Directory.GetFiles(reportDirectory, "*_SnapshotExplorationTestReport.csv").Single();
60+
var lines = File.ReadAllLines(csvPath);
61+
62+
lines.Should().ContainInOrder(
63+
"Probe ID,Type,Method,Is valid",
64+
"probe-id,Top.Level.Type,DoWork,True");
65+
}
66+
finally
67+
{
68+
if (Directory.Exists(reportDirectory))
69+
{
70+
Directory.Delete(reportDirectory, recursive: true);
71+
}
72+
}
73+
}
74+
}
75+
}

tracer/test/Datadog.Trace.Tests/Debugger/SupportedTypesServiceTests.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,26 @@ namespace Datadog.Trace.Tests.Debugger
1515
{
1616
public class SupportedTypesServiceTests
1717
{
18-
private static readonly object[] Objects = { 3, DateTime.MinValue, TimeSpan.FromSeconds(3), DateTimeOffset.MinValue, Guid.Empty, "Hello", new int?(5), new DateTime?(DateTime.MinValue), ConsoleColor.Blue };
18+
private static readonly Type[] Types =
19+
{
20+
typeof(int),
21+
typeof(DateTime),
22+
typeof(TimeSpan),
23+
typeof(DateTimeOffset),
24+
typeof(Guid),
25+
typeof(string),
26+
typeof(int?),
27+
typeof(DateTime?),
28+
typeof(TimeSpan?),
29+
typeof(DateTimeOffset?),
30+
typeof(ConsoleColor)
31+
};
1932

2033
[Fact]
2134
public void TestCanCallToString()
2235
{
23-
foreach (var obj in Objects)
36+
foreach (var type in Types)
2437
{
25-
var type = obj.GetType();
2638
Redaction.IsSafeToCallToString(type).Should().BeTrue($"Type {type} should be safe to call ToString on");
2739
}
2840
}

0 commit comments

Comments
 (0)