-
-
Notifications
You must be signed in to change notification settings - Fork 206
/
SentryContexts.cs
264 lines (220 loc) · 8.43 KB
/
SentryContexts.cs
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Internal.Extensions;
using Sentry.Protocol;
using OperatingSystem = Sentry.Protocol.OperatingSystem;
using Trace = Sentry.Protocol.Trace;
namespace Sentry;
/// <summary>
/// Represents Sentry's structured Context.
/// </summary>
/// <seealso href="https://develop.sentry.dev/sdk/event-payloads/contexts/" />
public sealed class SentryContexts : IDictionary<string, object>, ISentryJsonSerializable
{
private readonly ConcurrentDictionary<string, object> _innerDictionary = new(StringComparer.Ordinal);
/// <summary>
/// Describes the application.
/// </summary>
public App App => _innerDictionary.GetOrCreate<App>(App.Type);
/// <summary>
/// Describes the browser.
/// </summary>
public Browser Browser => _innerDictionary.GetOrCreate<Browser>(Browser.Type);
/// <summary>
/// Describes the device.
/// </summary>
public Device Device => _innerDictionary.GetOrCreate<Device>(Device.Type);
/// <summary>
/// Defines the operating system.
/// </summary>
/// <remarks>
/// In web contexts, this is the operating system of the browser (normally pulled from the User-Agent string).
/// </remarks>
public OperatingSystem OperatingSystem => _innerDictionary.GetOrCreate<OperatingSystem>(OperatingSystem.Type);
/// <summary>
/// Response interface that contains information on any HTTP response related to the event.
/// </summary>
public Response Response => _innerDictionary.GetOrCreate<Response>(Response.Type);
/// <summary>
/// This describes a runtime in more detail.
/// </summary>
public Runtime Runtime => _innerDictionary.GetOrCreate<Runtime>(Runtime.Type);
/// <summary>
/// This describes a GPU of the device.
/// </summary>
public Gpu Gpu => _innerDictionary.GetOrCreate<Gpu>(Gpu.Type);
/// <summary>
/// This describes trace information.
/// </summary>
public Trace Trace => _innerDictionary.GetOrCreate<Trace>(Trace.Type);
/// <summary>
/// Initializes an instance of <see cref="SentryContexts"/>.
/// </summary>
public SentryContexts() { }
/// <summary>
/// Creates a deep clone of this context.
/// </summary>
internal SentryContexts Clone()
{
var context = new SentryContexts();
CopyTo(context);
return context;
}
/// <summary>
/// Copies the items of the context while cloning the known types.
/// </summary>
internal void CopyTo(SentryContexts to)
{
foreach (var kv in this)
{
to._innerDictionary.AddOrUpdate(kv.Key,
addValueFactory: _ =>
kv.Value is ICloneable<object> cloneable
? cloneable.Clone()
: kv.Value,
updateValueFactory: (_, existing) =>
{
if (existing is IUpdatable updatable)
{
updatable.UpdateFrom(kv.Value);
}
else if (kv.Value is IDictionary<string, object?> source &&
existing is IDictionary<string, object?> target)
{
foreach (var item in source)
{
if (!target.TryGetValue(item.Key, out var value))
{
target.Add(item);
}
else if (value is null)
{
target[item.Key] = item.Value;
}
}
}
return existing;
});
}
}
/// <inheritdoc />
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
var contexts = this.OrderBy(x => x.Key, StringComparer.Ordinal);
writer.WriteDictionaryValue(contexts!, logger, includeNullValues: false);
}
/// <summary>
/// Parses from JSON.
/// </summary>
public static SentryContexts FromJson(JsonElement json)
{
var result = new SentryContexts();
foreach (var (name, value) in json.EnumerateObject())
{
var type = value.GetPropertyOrNull("type")?.GetString() ?? name;
// Handle known context types
if (string.Equals(type, App.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = App.FromJson(value);
}
else if (string.Equals(type, Browser.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = Browser.FromJson(value);
}
else if (string.Equals(type, Device.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = Device.FromJson(value);
}
else if (string.Equals(type, OperatingSystem.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = OperatingSystem.FromJson(value);
}
else if (string.Equals(type, Response.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = Response.FromJson(value);
}
else if (string.Equals(type, Runtime.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = Runtime.FromJson(value);
}
else if (string.Equals(type, Gpu.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = Gpu.FromJson(value);
}
else if (string.Equals(type, Trace.Type, StringComparison.OrdinalIgnoreCase))
{
result[name] = Trace.FromJson(value);
}
else
{
// Unknown context - parse as dictionary
var dynamicContext = value.GetDynamicOrNull();
if (dynamicContext is not null)
{
result[name] = dynamicContext;
}
}
}
return result;
}
internal void ReplaceWith(SentryContexts? contexts)
{
Clear();
if (contexts == null)
{
return;
}
foreach (var context in contexts)
{
this[context.Key] = context.Value;
}
}
internal SentryContexts? NullIfEmpty() => _innerDictionary.IsEmpty ? null : this;
/// <inheritdoc/>
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _innerDictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_innerDictionary).GetEnumerator();
/// <inheritdoc/>
public void Add(KeyValuePair<string, object> item)
=> ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Add(item);
/// <inheritdoc/>
public void Clear() => _innerDictionary.Clear();
/// <inheritdoc/>
public bool Contains(KeyValuePair<string, object> item) => _innerDictionary.Contains(item);
/// <inheritdoc/>
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
=> ((ICollection<KeyValuePair<string, object>>)_innerDictionary).CopyTo(array, arrayIndex);
/// <inheritdoc/>
public bool Remove(KeyValuePair<string, object> item)
=> ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Remove(item);
/// <inheritdoc/>
public int Count => _innerDictionary.Count;
/// <inheritdoc/>
public bool IsReadOnly => ((ICollection<KeyValuePair<string, object>>)_innerDictionary).IsReadOnly;
/// <inheritdoc/>
public void Add(string key, object value) => _innerDictionary.Add(key, value);
/// <inheritdoc/>
public bool ContainsKey(string key) => _innerDictionary.ContainsKey(key);
/// <inheritdoc/>
public bool Remove(string key) => ((IDictionary<string, object>)_innerDictionary).Remove(key);
/// <inheritdoc/>
public bool TryGetValue(string key, out object value)
{
if (_innerDictionary.TryGetValue(key, out var innerValue))
{
value = innerValue;
return true;
}
value = default!;
return false;
}
/// <inheritdoc/>
public object this[string key]
{
get => _innerDictionary[key];
set => _innerDictionary[key] = value;
}
/// <inheritdoc/>
public ICollection<string> Keys => _innerDictionary.Keys;
/// <inheritdoc/>
public ICollection<object> Values => _innerDictionary.Values;
}