-
-
Notifications
You must be signed in to change notification settings - Fork 207
/
SentryStackFrame.cs
251 lines (217 loc) · 10.5 KB
/
SentryStackFrame.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
using Sentry.Extensibility;
using Sentry.Internal.Extensions;
namespace Sentry;
/// <summary>
/// A frame of a stacktrace.
/// </summary>
/// <see href="https://develop.sentry.dev/sdk/event-payloads/stacktrace/"/>
public sealed class SentryStackFrame : IJsonSerializable
{
internal List<string>? InternalPreContext { get; private set; }
internal List<string>? InternalPostContext { get; private set; }
internal Dictionary<string, string>? InternalVars { get; private set; }
internal List<int>? InternalFramesOmitted { get; private set; }
/// <summary>
/// The relative file path to the call.
/// </summary>
public string? FileName { get; set; }
/// <summary>
/// The name of the function being called.
/// </summary>
public string? Function { get; set; }
/// <summary>
/// Platform-specific module path.
/// </summary>
public string? Module { get; set; }
// Optional fields
/// <summary>
/// The line number of the call.
/// </summary>
public int? LineNumber { get; set; }
/// <summary>
/// The column number of the call.
/// </summary>
public int? ColumnNumber { get; set; }
/// <summary>
/// The absolute path to filename.
/// </summary>
public string? AbsolutePath { get; set; }
/// <summary>
/// Source code in filename at line number.
/// </summary>
public string? ContextLine { get; set; }
/// <summary>
/// A list of source code lines before context_line (in order) – usually [lineno - 5:lineno].
/// </summary>
public IList<string> PreContext => InternalPreContext ??= new List<string>();
/// <summary>
/// A list of source code lines after context_line (in order) – usually [lineno + 1:lineno + 5].
/// </summary>
public IList<string> PostContext => InternalPostContext ??= new List<string>();
/// <summary>
/// Signifies whether this frame is related to the execution of the relevant code in this stacktrace.
/// </summary>
/// <example>
/// For example, the frames that might power the framework’s web server of your app are probably not relevant,
/// however calls to the framework’s library once you start handling code likely are.
/// </example>
public bool? InApp { get; set; }
/// <summary>
/// A mapping of variables which were available within this frame (usually context-locals).
/// </summary>
public IDictionary<string, string> Vars => InternalVars ??= new Dictionary<string, string>();
/// <summary>
/// Which frames were omitted, if any.
/// </summary>
/// <remarks>
/// If the list of frames is large, you can explicitly tell the system that you’ve omitted a range of frames.
/// The frames_omitted must be a single tuple two values: start and end.
/// </remarks>
/// <example>
/// If you only removed the 8th frame, the value would be (8, 9), meaning it started at the 8th frame,
/// and went until the 9th (the number of frames omitted is end-start).
/// The values should be based on a one-index.
/// </example>
public IList<int> FramesOmitted => InternalFramesOmitted ??= new List<int>();
/// <summary>
/// The assembly where the code resides.
/// </summary>
public string? Package { get; set; }
/// <summary>
/// This can override the platform for a single frame. Otherwise the platform of the event is assumed.
/// </summary>
public string? Platform { get; set; }
/// <summary>
/// Optionally an address of the debug image to reference.
/// If this is set and a known image is defined by debug_meta then symbolication can take place.
/// </summary>
public long ImageAddress { get; set; }
/// <summary>
/// An optional address that points to a symbol.
/// We actually use the instruction address for symbolication but this can be used to calculate an instruction offset automatically.
/// </summary>
public long? SymbolAddress { get; set; }
/// <summary>
/// An optional instruction address for symbolication.<br/>
/// This should be a string with a hexadecimal number that includes a <b>0x</b> prefix.<br/>
/// If this is set and a known image is defined in the <see href="https://develop.sentry.dev/sdk/event-payloads/debugmeta/">Debug Meta Interface</see>, then symbolication can take place.<br/>
/// </summary>
public string? InstructionAddress { get; set; }
/// <summary>
/// The instruction offset.
/// </summary>
/// <remarks>
/// The official docs refer to it as 'The difference between instruction address and symbol address in bytes.'
/// In .NET this means the IL Offset within the assembly.
/// </remarks>
public long? InstructionOffset { get; set; }
/// <summary>
/// Optionally changes the addressing mode. The default value is the same as
/// `"abs"` which means absolute referencing. This can also be set to
/// `"rel:DEBUG_ID"` or `"rel:IMAGE_INDEX"` to make addresses relative to an
/// object referenced by debug id or index.
/// </summary>
public string? AddressMode { get; set; }
/// <summary>
/// The optional Function Id.<br/>
/// This is derived from the `MetadataToken`, and should be the record id
/// of a `MethodDef`.<br/>
/// This should be a string with a hexadecimal number that includes a <b>0x</b> prefix.<br/>
/// </summary>
public string? FunctionId { get; set; }
/// <inheritdoc />
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
writer.WriteStartObject();
writer.WriteStringArrayIfNotEmpty("pre_context", InternalPreContext);
writer.WriteStringArrayIfNotEmpty("post_context", InternalPostContext);
writer.WriteStringDictionaryIfNotEmpty("vars", InternalVars!);
writer.WriteArrayIfNotEmpty("frames_omitted", InternalFramesOmitted?.Cast<object>(), logger);
writer.WriteStringIfNotWhiteSpace("filename", FileName);
writer.WriteStringIfNotWhiteSpace("function", Function);
writer.WriteStringIfNotWhiteSpace("module", Module);
writer.WriteNumberIfNotNull("lineno", LineNumber);
writer.WriteNumberIfNotNull("colno", ColumnNumber);
writer.WriteStringIfNotWhiteSpace("abs_path", AbsolutePath);
writer.WriteStringIfNotWhiteSpace("context_line", ContextLine);
writer.WriteBooleanIfNotNull("in_app", InApp);
writer.WriteStringIfNotWhiteSpace("package", Package);
writer.WriteStringIfNotWhiteSpace("platform", Platform);
writer.WriteStringIfNotWhiteSpace("image_addr", ImageAddress.NullIfDefault()?.ToHexString());
writer.WriteStringIfNotWhiteSpace("symbol_addr", SymbolAddress?.ToHexString());
writer.WriteStringIfNotWhiteSpace("instruction_addr", InstructionAddress);
writer.WriteNumberIfNotNull("instruction_offset", InstructionOffset);
writer.WriteStringIfNotWhiteSpace("addr_mode", AddressMode);
writer.WriteStringIfNotWhiteSpace("function_id", FunctionId);
writer.WriteEndObject();
}
/// <summary>
/// Configures <see cref="InApp"/> based on the <see cref="SentryOptions.InAppInclude"/> and <see cref="SentryOptions.InAppExclude"/> or <paramref name="options"/>.
/// </summary>
/// <param name="options">The Sentry options.</param>
/// <remarks><see cref="InApp"/> will remain with the same value if previously set.</remarks>
public void ConfigureAppFrame(SentryOptions options)
{
if (InApp != null)
{
return;
}
var parameterName = Module ?? Function;
if (string.IsNullOrEmpty(parameterName))
{
InApp = true;
return;
}
InApp = options.InAppInclude?.Any(include => parameterName.StartsWith(include, StringComparison.Ordinal)) == true ||
options.InAppExclude?.Any(exclude => parameterName.StartsWith(exclude, StringComparison.Ordinal)) != true;
}
/// <summary>
/// Parses from JSON.
/// </summary>
public static SentryStackFrame FromJson(JsonElement json)
{
var preContext = json.GetPropertyOrNull("pre_context")?.EnumerateArray().Select(j => j.GetString()).ToList();
var postContext = json.GetPropertyOrNull("post_context")?.EnumerateArray().Select(j => j.GetString()).ToList();
var vars = json.GetPropertyOrNull("vars")?.GetStringDictionaryOrNull();
var framesOmitted = json.GetPropertyOrNull("frames_omitted")?.EnumerateArray().Select(j => j.GetInt32()).ToList();
var filename = json.GetPropertyOrNull("filename")?.GetString();
var function = json.GetPropertyOrNull("function")?.GetString();
var module = json.GetPropertyOrNull("module")?.GetString();
var lineNumber = json.GetPropertyOrNull("lineno")?.GetInt32();
var columnNumber = json.GetPropertyOrNull("colno")?.GetInt32();
var absolutePath = json.GetPropertyOrNull("abs_path")?.GetString();
var contextLine = json.GetPropertyOrNull("context_line")?.GetString();
var inApp = json.GetPropertyOrNull("in_app")?.GetBoolean();
var package = json.GetPropertyOrNull("package")?.GetString();
var platform = json.GetPropertyOrNull("platform")?.GetString();
var imageAddress = json.GetPropertyOrNull("image_addr")?.GetAddressAsLong() ?? 0;
var symbolAddress = json.GetPropertyOrNull("symbol_addr")?.GetAddressAsLong();
var instructionAddress = json.GetPropertyOrNull("instruction_addr")?.GetString();
var instructionOffset = json.GetPropertyOrNull("instruction_offset")?.GetInt64();
var addressMode = json.GetPropertyOrNull("addr_mode")?.GetString();
var functionId = json.GetPropertyOrNull("function_id")?.GetString();
return new SentryStackFrame
{
InternalPreContext = preContext!,
InternalPostContext = postContext!,
InternalVars = vars?.WhereNotNullValue().ToDictionary(),
InternalFramesOmitted = framesOmitted,
FileName = filename,
Function = function,
Module = module,
LineNumber = lineNumber,
ColumnNumber = columnNumber,
AbsolutePath = absolutePath,
ContextLine = contextLine,
InApp = inApp,
Package = package,
Platform = platform,
ImageAddress = imageAddress,
SymbolAddress = symbolAddress,
InstructionAddress = instructionAddress,
InstructionOffset = instructionOffset,
AddressMode = addressMode,
FunctionId = functionId,
};
}
}