-
Notifications
You must be signed in to change notification settings - Fork 295
Expand file tree
/
Copy pathExceptionTelemetry.cs
More file actions
301 lines (263 loc) · 11.9 KB
/
ExceptionTelemetry.cs
File metadata and controls
301 lines (263 loc) · 11.9 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
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
namespace Microsoft.ApplicationInsights.DataContracts
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationInsights.Channel;
/// <summary>
/// Telemetry type used to track exceptions. This will capture TypeName, Message, and CallStack.
/// <a href="https://go.microsoft.com/fwlink/?linkid=723596">Learn more</a>
/// </summary>
/// <remarks>
/// Additional exception details will need to be tracked manually.
/// </remarks>
public sealed class ExceptionTelemetry : ITelemetry, ISupportProperties
{
internal const string EtwEnvelopeName = "Exception";
internal string EnvelopeName = "AppExceptions";
internal ExceptionInfo Data = null;
private TelemetryContext context;
private Exception exception;
private IDictionary<string, string> properties;
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionTelemetry"/> class with empty properties.
/// </summary>
public ExceptionTelemetry()
{
this.properties = new Dictionary<string, string>();
this.Data = new ExceptionInfo(new List<ExceptionDetailsInfo>(), null, null, this.properties);
this.context = new TelemetryContext();
}
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionTelemetry"/> class with empty properties.
/// </summary>
/// <param name="exception">Exception instance.</param>
public ExceptionTelemetry(Exception exception)
: this()
{
this.Exception = exception;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionTelemetry"/> class.
/// </summary>
/// <param name="exceptionDetailsInfoList">Exception info.</param>
/// <param name="severityLevel">Severity level.</param>
/// <param name="problemId">Problem id.</param>
/// <param name="properties">Properties.</param>
public ExceptionTelemetry(IEnumerable<ExceptionDetailsInfo> exceptionDetailsInfoList, SeverityLevel? severityLevel, string problemId,
IDictionary<string, string> properties)
{
this.properties = properties ?? new Dictionary<string, string>();
this.Data = new ExceptionInfo(exceptionDetailsInfoList, severityLevel, problemId, this.properties);
this.context = new TelemetryContext();
}
/// <summary>
/// Gets or sets date and time when telemetry was recorded.
/// </summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Gets the context associated with the current telemetry item.
/// </summary>
public TelemetryContext Context
{
get { return this.context; }
}
/// <summary>
/// Gets or sets the problemId.
/// </summary>
public string ProblemId
{
get
{
return this.Data.ProblemId;
}
set
{
this.Data.ProblemId = value;
}
}
/// <summary>
/// Gets or sets the original exception tracked by this <see cref="ITelemetry"/>.
/// </summary>
public Exception Exception
{
get
{
if (this.exception == null && this.Data?.ExceptionDetailsInfoList != null && this.Data.ExceptionDetailsInfoList.Count > 0)
{
// Lazy-load: reconstruct exception from ExceptionDetailsInfoList
this.exception = ReconstructExceptionFromDetails(this.Data.ExceptionDetailsInfoList);
}
return this.exception;
}
set
{
this.exception = value;
// Populate ExceptionDetailsInfoList from the exception
if (value != null)
{
var exceptionDetailsList = new List<ExceptionDetailsInfo>();
ConvertExceptionTree(value, null, exceptionDetailsList);
this.Data = new ExceptionInfo(exceptionDetailsList, this.Data?.SeverityLevel, this.Data?.ProblemId, this.properties ?? new Dictionary<string, string>());
}
else
{
this.Data = new ExceptionInfo(new List<ExceptionDetailsInfo>(), this.Data?.SeverityLevel, this.Data?.ProblemId, this.properties ?? new Dictionary<string, string>());
}
}
}
/// <summary>
/// Gets or sets ExceptionTelemetry message.
/// </summary>
public string Message
{
get;
set;
}
/// <summary>
/// Gets the list of <see cref="ExceptionDetailsInfo"/>. User can modify the contents of individual object, but
/// not the list itself.
/// </summary>
public IReadOnlyList<ExceptionDetailsInfo> ExceptionDetailsInfoList => this.Data.ExceptionDetailsInfoList;
/// <summary>
/// Gets a dictionary of application-defined property names and values providing additional information about this exception.
/// <a href="https://go.microsoft.com/fwlink/?linkid=525722#properties">Learn more</a>
/// </summary>
public IDictionary<string, string> Properties
{
get
{
return this.properties ?? (this.properties = new Dictionary<string, string>());
}
}
/// <summary>
/// Gets or sets Exception severity level.
/// </summary>
public SeverityLevel? SeverityLevel
{
get => this.Data.SeverityLevel;
set => this.Data.SeverityLevel = value;
}
/// <summary>
/// Set parsedStack from an array of StackFrame objects.
/// </summary>
/// <param name="frames">Array of System.Diagnostics.StackFrame to convert to parsed stack.</param>
public void SetParsedStack(System.Diagnostics.StackFrame[] frames)
{
if (this.Data?.ExceptionDetailsInfoList != null && this.Data.ExceptionDetailsInfoList.Count > 0)
{
if (frames != null && frames.Length > 0)
{
const int MaxParsedStackLength = 32768;
int stackLength = 0;
var parsedStack = new List<StackFrame>();
bool hasFullStack = true;
for (int level = 0; level < frames.Length; level++)
{
var frame = frames[level];
#pragma warning disable IL2026 // Suppressed for backward compatibility - method metadata might be incomplete when trimmed
var method = frame.GetMethod();
#pragma warning restore IL2026
var sf = new StackFrame(
assembly: method?.DeclaringType?.Assembly?.FullName,
fileName: frame.GetFileName(),
level: level,
line: frame.GetFileLineNumber(),
method: method?.Name);
// Approximate stack frame length
stackLength += (sf.Assembly?.Length ?? 0) + (sf.FileName?.Length ?? 0) + (sf.Method?.Length ?? 0) + 20;
if (stackLength > MaxParsedStackLength)
{
hasFullStack = false;
break;
}
parsedStack.Add(sf);
}
// Update the first exception details with parsed stack
var firstException = this.Data.ExceptionDetailsInfoList[0] as ExceptionDetailsInfo;
if (firstException != null)
{
firstException.ParsedStack = parsedStack;
firstException.HasFullStack = hasFullStack;
}
}
}
}
/// <summary>
/// Converts exception tree to a list of ExceptionDetailsInfo objects.
/// </summary>
private static void ConvertExceptionTree(Exception exception, int? parentId, List<ExceptionDetailsInfo> detailsList)
{
// Limit to prevent infinite loops
if (exception == null || detailsList.Count >= 10)
{
return;
}
int currentId = detailsList.Count;
var exceptionDetail = new ExceptionDetailsInfo(
id: currentId,
outerId: parentId ?? -1,
typeName: exception.GetType().FullName,
message: exception.Message,
hasFullStack: exception.StackTrace != null,
stack: exception.StackTrace,
parsedStack: System.Array.Empty<StackFrame>());
detailsList.Add(exceptionDetail);
// Handle AggregateException specially
if (exception is AggregateException aggregateException && aggregateException.InnerExceptions != null)
{
foreach (var innerException in aggregateException.InnerExceptions)
{
ConvertExceptionTree(innerException, currentId, detailsList);
}
}
else if (exception.InnerException != null)
{
ConvertExceptionTree(exception.InnerException, currentId, detailsList);
}
}
/// <summary>
/// Reconstructs an exception chain from ExceptionDetailsInfo list using id/outerId structure.
/// </summary>
private static Exception ReconstructExceptionFromDetails(IReadOnlyList<ExceptionDetailsInfo> detailsList)
{
if (detailsList == null || detailsList.Count == 0)
{
return null;
}
// Build a dictionary of id -> Exception
// We need to build from the innermost to outermost
var exceptionDict = new Dictionary<int, Exception>();
// Sort by id descending so we build inner exceptions first
var sortedDetails = detailsList.OrderByDescending(d => d.Id).ToList();
foreach (var detail in sortedDetails)
{
var message = detail.Message ?? "<no message>";
Exception innerException = null;
// Find all children (exceptions that have this as outerId)
var children = detailsList.Where(d => d.OuterId == detail.Id).ToList();
if (children.Count == 1)
{
// Single inner exception
innerException = exceptionDict[children[0].Id];
}
else if (children.Count > 1)
{
// Multiple inner exceptions - create AggregateException
var innerExceptions = children.Select(c => exceptionDict[c.Id]).ToList();
innerException = new AggregateException(message, innerExceptions);
exceptionDict[detail.Id] = innerException;
continue;
}
// Create exception with inner if it exists
#pragma warning disable CA2201 // Exception reconstruction requires generic Exception type
var exception = innerException != null ? new Exception(message, innerException) : new Exception(message);
#pragma warning restore CA2201
exceptionDict[detail.Id] = exception;
}
// Find the root exception (one with outerId == -1)
var rootDetail = detailsList.FirstOrDefault(d => d.OuterId == -1);
return rootDetail != null ? exceptionDict[rootDetail.Id] : exceptionDict[detailsList[0].Id];
}
}
}