-
-
Notifications
You must be signed in to change notification settings - Fork 233
Expand file tree
/
Copy pathSentryLog.cs
More file actions
260 lines (226 loc) · 8.56 KB
/
SentryLog.cs
File metadata and controls
260 lines (226 loc) · 8.56 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
using Sentry.Extensibility;
using Sentry.Infrastructure;
using Sentry.Protocol;
namespace Sentry;
/// <summary>
/// Represents the Sentry Log protocol.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
[DebuggerDisplay(@"SentryLog \{ Level = {Level}, Message = '{Message}' \}")]
public sealed class SentryLog
{
private readonly Dictionary<string, SentryAttribute> _attributes;
[SetsRequiredMembers]
internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel level, string message)
{
Timestamp = timestamp;
TraceId = traceId;
Level = level;
Message = message;
// 7 is the number of built-in attributes, so we start with that.
_attributes = new Dictionary<string, SentryAttribute>(7);
}
/// <summary>
/// The timestamp of the log.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
/// <remarks>
/// Sent as seconds since the Unix epoch.
/// </remarks>
[Experimental(DiagnosticId.ExperimentalFeature)]
public required DateTimeOffset Timestamp { get; init; }
/// <summary>
/// The trace id of the log.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public required SentryId TraceId { get; init; }
/// <summary>
/// The severity level of the log.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public required SentryLogLevel Level { get; init; }
/// <summary>
/// The formatted log message.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public required string Message { get; init; }
/// <summary>
/// The parameterized template string.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public string? Template { get; init; }
/// <summary>
/// The parameters to the template string.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public ImmutableArray<KeyValuePair<string, object>> Parameters { get; init; }
/// <summary>
/// The span id of the span that was active when the log was collected.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public SpanId? ParentSpanId { get; init; }
/// <summary>
/// Gets the attribute value associated with the specified key.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
/// <remarks>
/// Returns <see langword="true"/> if the <see cref="SentryLog"/> contains an attribute with the specified key and it's value is not <see langword="null"/>.
/// Otherwise <see langword="false"/>.
/// Supported types:
/// <list type="table">
/// <listheader>
/// <term>Type</term>
/// <description>Range</description>
/// </listheader>
/// <item>
/// <term>string</term>
/// <description><see langword="string"/> and <see langword="char"/></description>
/// </item>
/// <item>
/// <term>boolean</term>
/// <description><see langword="false"/> and <see langword="true"/></description>
/// </item>
/// <item>
/// <term>integer</term>
/// <description>64-bit signed integral numeric types</description>
/// </item>
/// <item>
/// <term>double</term>
/// <description>64-bit floating-point numeric types</description>
/// </item>
/// </list>
/// Unsupported types:
/// <list type="table">
/// <listheader>
/// <term>Type</term>
/// <description>Result</description>
/// </listheader>
/// <item>
/// <term><see langword="object"/></term>
/// <description><c>ToString</c> as <c>"type": "string"</c></description>
/// </item>
/// <item>
/// <term>Collections</term>
/// <description><c>ToString</c> as <c>"type": "string"</c></description>
/// </item>
/// <item>
/// <term><see langword="null"/></term>
/// <description>ignored</description>
/// </item>
/// </list>
/// </remarks>
/// <seealso href="https://develop.sentry.dev/sdk/telemetry/logs/"/>
[Experimental(DiagnosticId.ExperimentalFeature)]
public bool TryGetAttribute(string key, [NotNullWhen(true)] out object? value)
{
if (_attributes.TryGetValue(key, out var attribute) && attribute.Value is not null)
{
value = attribute.Value;
return true;
}
value = null;
return false;
}
internal bool TryGetAttribute(string key, [NotNullWhen(true)] out string? value)
{
if (_attributes.TryGetValue(key, out var attribute) && attribute.Type == "string" && attribute.Value is not null)
{
value = (string)attribute.Value;
return true;
}
value = null;
return false;
}
/// <summary>
/// Set a key-value pair of data attached to the log.
/// <para>This API is experimental and it may change in the future.</para>
/// </summary>
[Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, object value)
{
_attributes[key] = new SentryAttribute(value);
}
internal void SetAttribute(string key, string value)
{
_attributes[key] = new SentryAttribute(value, "string");
}
internal void SetAttribute(string key, char value)
{
_attributes[key] = new SentryAttribute(value.ToString(), "string");
}
internal void SetAttribute(string key, int value)
{
_attributes[key] = new SentryAttribute(value, "integer");
}
internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk)
{
var environment = options.SettingLocator.GetEnvironment();
SetAttribute("sentry.environment", environment);
var release = options.SettingLocator.GetRelease();
if (release is not null)
{
SetAttribute("sentry.release", release);
}
if (sdk.Name is { } name)
{
SetAttribute("sentry.sdk.name", name);
}
if (sdk.Version is { } version)
{
SetAttribute("sentry.sdk.version", version);
}
}
internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
writer.WriteStartObject();
#if NET9_0_OR_GREATER
writer.WriteNumber("timestamp", Timestamp.ToUnixTimeMilliseconds() / (double)TimeSpan.MillisecondsPerSecond);
#else
writer.WriteNumber("timestamp", Timestamp.ToUnixTimeMilliseconds() / 1_000.0);
#endif
var (severityText, severityNumber) = Level.ToSeverityTextAndOptionalSeverityNumber(logger);
writer.WriteString("level", severityText);
writer.WriteString("body", Message);
writer.WritePropertyName("trace_id");
TraceId.WriteTo(writer, logger);
if (severityNumber.HasValue)
{
writer.WriteNumber("severity_number", severityNumber.Value);
}
writer.WritePropertyName("attributes");
writer.WriteStartObject();
if (Template is not null)
{
SentryAttributeSerializer.WriteStringAttribute(writer, "sentry.message.template", Template);
}
if (!Parameters.IsDefault)
{
foreach (var parameter in Parameters)
{
SentryAttributeSerializer.WriteAttribute(writer, $"sentry.message.parameter.{parameter.Key}", parameter.Value, logger);
}
}
foreach (var attribute in _attributes)
{
SentryAttributeSerializer.WriteAttribute(writer, attribute.Key, attribute.Value, logger);
}
if (ParentSpanId.HasValue)
{
writer.WritePropertyName("sentry.trace.parent_span_id");
writer.WriteStartObject();
writer.WritePropertyName("value");
ParentSpanId.Value.WriteTo(writer, logger);
writer.WriteString("type", "string");
writer.WriteEndObject();
}
writer.WriteEndObject(); // attributes
writer.WriteEndObject();
}
}