-
-
Notifications
You must be signed in to change notification settings - Fork 803
Expand file tree
/
Copy pathActivityTestHelper.cs
More file actions
155 lines (136 loc) · 5.49 KB
/
ActivityTestHelper.cs
File metadata and controls
155 lines (136 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
using System.Diagnostics;
using System.Text.RegularExpressions;
using HotChocolate.Utilities;
namespace HotChocolate.Diagnostics;
public static partial class ActivityTestHelper
{
[GeneratedRegex(@" in (?<path>.+?):line (?<line>\d+)", RegexOptions.CultureInvariant)]
private static partial Regex StackTracePathRegex();
[GeneratedRegex(@"lambda_method\d+", RegexOptions.CultureInvariant)]
private static partial Regex LambdaMethodRegex();
public static IDisposable CaptureActivities(out object activities)
{
var sync = new object();
var listener = new ActivityListener();
var root = new OrderedDictionary<string, object?>();
var lookup = new Dictionary<Activity, OrderedDictionary<string, object?>>();
var spanLookup = new Dictionary<ActivitySpanId, OrderedDictionary<string, object?>>();
Activity rootActivity = null!;
listener.ShouldListenTo = source => source.Name.EqualsOrdinal("HotChocolate.Diagnostics");
listener.ActivityStarted = a =>
{
lock (sync)
{
if (a.Parent is null
&& a.OperationName.EqualsOrdinal("ExecuteHttpRequest")
&& lookup.TryGetValue(rootActivity, out var parentData))
{
RegisterActivity(a, parentData);
lookup[a] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
}
if (a.Parent is not null
&& lookup.TryGetValue(a.Parent, out parentData))
{
RegisterActivity(a, parentData);
lookup[a] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
spanLookup[a.SpanId] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
return;
}
if (a.Parent is null
&& a.ParentSpanId != default
&& spanLookup.TryGetValue(a.ParentSpanId, out parentData))
{
RegisterActivity(a, parentData);
lookup[a] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
spanLookup[a.SpanId] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
}
}
};
listener.ActivityStopped = SerializeActivity;
listener.Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllData;
ActivitySource.AddActivityListener(listener);
rootActivity = HotChocolateActivitySource.Source.StartActivity()!;
rootActivity.SetCustomProperty("test.data", root);
lookup[rootActivity] = root;
spanLookup[rootActivity.SpanId] = root;
activities = root;
return new Session(rootActivity, listener);
}
private static void RegisterActivity(
Activity activity,
OrderedDictionary<string, object?> parent)
{
if (!(parent.TryGetValue("activities", out var value) && value is List<object> children))
{
children = [];
parent["activities"] = children;
}
var data = new OrderedDictionary<string, object?>();
activity.SetCustomProperty("test.data", data);
SerializeActivity(activity);
children.Add(data);
}
private static void SerializeActivity(Activity activity)
{
var data = (OrderedDictionary<string, object?>?)activity.GetCustomProperty("test.data");
if (data is null)
{
return;
}
data["OperationName"] = activity.OperationName;
data["DisplayName"] = activity.DisplayName;
data["Kind"] = activity.Kind;
data["Status"] = activity.Status;
data["tags"] = activity.TagObjects;
data["event"] = activity.Events.Select(t => new
{
t.Name,
Tags = ScrubEventTags(t.Tags)
});
}
private static IEnumerable<KeyValuePair<string, object?>> ScrubEventTags(
IEnumerable<KeyValuePair<string, object?>>? tags)
{
if (tags is null)
{
yield break;
}
foreach (var tag in tags)
{
if (tag.Value is string stackTrace
&& (tag.Key.Equals("exception.stacktrace", StringComparison.Ordinal)
|| tag.Key.EndsWith(".stacktrace", StringComparison.Ordinal)))
{
var scrubbedStackTrace = StackTracePathRegex().Replace(stackTrace, match =>
{
var fileName = System.IO.Path.GetFileName(match.Groups["path"].Value);
var lineNumber = match.Groups["line"].Value;
return $" in {fileName}:line {lineNumber}";
});
yield return new KeyValuePair<string, object?>(
tag.Key,
LambdaMethodRegex().Replace(scrubbedStackTrace, "lambda_method"));
}
else
{
yield return tag;
}
}
}
private sealed class Session : IDisposable
{
private readonly Activity _activity;
private readonly ActivityListener _listener;
public Session(Activity activity, ActivityListener listener)
{
_activity = activity;
_listener = listener;
}
public void Dispose()
{
_activity.Dispose();
_listener.Dispose();
}
}
}