-
-
Notifications
You must be signed in to change notification settings - Fork 213
/
Copy pathProgram.cs
288 lines (240 loc) · 11.5 KB
/
Program.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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
using System.Xml.Xsl;
using Sentry.Extensibility;
internal static class Program
{
private static async Task Main()
{
// When the SDK is disabled, no callback is executed:
await SentrySdk.ConfigureScopeAsync(async scope =>
{
// Never executed:
// This could be any async I/O operation, like a DB query
await Task.Yield();
scope.SetExtra("Key", "Value");
});
// Enable the SDK
using (SentrySdk.Init(options =>
{
// You can set here in code, or you can set it in the SENTRY_DSN environment variable.
// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/
options.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537";
// Send stack trace for events that were not created from an exception
// e.g: CaptureMessage, log.LogDebug, log.LogInformation ...
options.AttachStacktrace = true;
// Sentry won't consider code from namespace LibraryX.* as part of the app code and will hide it from the stacktrace by default
// To see the lines from non `AppCode`, select `Full`. Will include non App code like System.*, Microsoft.* and LibraryX.*
options.AddInAppExclude("LibraryX.");
// Before excluding all prefixed 'LibraryX.', any stack trace from a type namespaced 'LibraryX.Core' will be considered InApp.
options.AddInAppInclude("LibraryX.Core");
// Send personal identifiable information like the username logged on to the computer and machine name
options.SendDefaultPii = true;
// To enable event sampling, uncomment:
// o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events
// Modifications to event before it goes out. Could replace the event altogether
options.SetBeforeSend((@event, _) =>
{
// Drop an event altogether:
if (@event.Tags.ContainsKey("SomeTag"))
{
return null;
}
return @event;
}
);
// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
options.SetBeforeBreadcrumb((crumb, hint) =>
{
// Don't add breadcrumbs with message containing:
if (crumb.Message?.Contains("bad breadcrumb") == true)
{
return null;
}
// Replace breadcrumbs entirely incase of a drastic hint
const string replaceBreadcrumb = "don't trust this breadcrumb";
if (hint.Items.TryGetValue(replaceBreadcrumb, out var replacementMessage))
{
return new Breadcrumb((string)replacementMessage, null, null, null, BreadcrumbLevel.Critical);
}
return crumb;
});
// Ignore exception by its type:
options.AddExceptionFilterForType<XsltCompileException>();
// Configure the background worker which sends events to sentry:
// Wait up to 5 seconds before shutdown while there are events to send.
options.ShutdownTimeout = TimeSpan.FromSeconds(5);
// Enable SDK logging with Debug level
options.Debug = true;
// To change the verbosity, use:
// options.DiagnosticLevel = SentryLevel.Info;
// To use a custom logger:
// options.DiagnosticLogger = ...
// Using a proxy:
options.HttpProxy = null; //new WebProxy("https://localhost:3128");
// Example customizing the HttpMessageHandlers created
options.CreateHttpMessageHandler = () => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, certificate, _, _) =>
!certificate.Archived
};
// Access to the HttpClient created to serve the SentryClint
options.ConfigureClient = client => client.DefaultRequestHeaders.TryAddWithoutValidation("CustomHeader", new[] { "my value" });
// Control/override how to apply the State object into the scope
options.SentryScopeStateProcessor = new MyCustomerScopeStateProcessor();
}))
{
// Ignored by its type due to the setting above
SentrySdk.CaptureException(new XsltCompileException());
SentrySdk.AddBreadcrumb(
"A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'");
SentrySdk.AddBreadcrumb(
new Breadcrumb("A breadcrumb that will be replaced by the 'BeforeBreadcrumb callback because of the hint", null),
new SentryHint("don't trust this breadcrumb", "trust this instead")
);
// Data added to the root scope (no PushScope called up to this point)
// The modifications done here will affect all events sent and will propagate to child scopes.
await SentrySdk.ConfigureScopeAsync(async scope =>
{
scope.AddEventProcessor(new SomeEventProcessor());
scope.AddExceptionProcessor(new ArgumentExceptionProcessor());
// This could be any async I/O operation, like a DB query
await Task.Yield();
scope.SetExtra("SomeExtraInfo",
new
{
Data = "Value fetched asynchronously",
ManaLevel = 199
});
});
// Configures a scope which is only valid within the callback
SentrySdk.CaptureMessage("Fatal message!", s =>
{
s.Level = SentryLevel.Fatal;
s.TransactionName = "main";
s.Environment = "SpecialEnvironment";
// Add a file attachment for upload
s.AddAttachment(typeof(Program).Assembly.Location);
});
var eventId = SentrySdk.CaptureMessage("Some warning!", SentryLevel.Warning);
// Send an user feedback linked to the warning.
var timestamp = DateTime.Now.Ticks;
var user = $"user{timestamp}";
var email = $"user{timestamp}@user{timestamp}.com";
SentrySdk.CaptureUserFeedback(new UserFeedback(eventId, user, email, "this is a sample user feedback"));
var error = new Exception("Attempting to send this multiple times");
// Only the first capture will be sent to Sentry
for (var i = 0; i < 3; i++)
{
// The SDK is able to detect duplicate events:
// This is useful, for example, when multiple loggers log the same exception. Or exception is re-thrown and recaptured.
SentrySdk.CaptureException(error);
}
var count = 10;
for (var i = 0; i < count; i++)
{
const string msg = "{0} of {1} items we'll wait to flush to Sentry!";
SentrySdk.CaptureEvent(new SentryEvent
{
Message = new SentryMessage
{
Message = msg,
Formatted = string.Format(msg, i, count)
},
Level = SentryLevel.Debug
});
}
// Console output will show queue being flushed.
await SentrySdk.FlushAsync();
// -------------------------
// A custom made client, that could be registered with DI,
// would get disposed by the container on app shutdown
var evt = new SentryEvent
{
Message = "Starting new client"
};
evt.AddBreadcrumb("Breadcrumb directly to the event");
evt.User.Username = "some@user";
// Group all events with the following fingerprint:
evt.SetFingerprint("NewClientDebug");
evt.Level = SentryLevel.Debug;
SentrySdk.CaptureEvent(evt);
// Using a different DSN for a section of the app (i.e: admin)
const string AdminDsn = "https://f670c444cca14cf2bb4bfc403525b6a3@sentry.io/259314";
using (var adminClient = new SentryClient(new SentryOptions { Dsn = AdminDsn }))
{
// Make believe web framework middleware
var middleware = new AdminPartMiddleware(adminClient, null);
var request = new { Path = "/admin" }; // made up request
middleware.Invoke(request);
} // Dispose the client which flushes any queued events
SentrySdk.CaptureException(
new Exception("Error outside of the admin section: Goes to the default DSN"));
} // On Dispose: SDK closed, events queued are flushed/sent to Sentry
}
private class AdminPartMiddleware
{
private readonly ISentryClient _adminClient;
private readonly dynamic _middleware;
public AdminPartMiddleware(ISentryClient adminClient, dynamic middleware)
{
_adminClient = adminClient;
_middleware = middleware;
}
public void Invoke(dynamic request)
{
using (SentrySdk.PushScope(new SpecialContextObject()))
{
SentrySdk.AddBreadcrumb(request.Path, "request-path");
// Change the SentryClient in case the request is to the admin part:
if (request.Path.StartsWith("/admin"))
{
// Within this scope, the _adminClient will be used instead of whatever
// client was defined before this point:
SentrySdk.BindClient(_adminClient);
}
SentrySdk.CaptureException(new Exception("Error at the admin section"));
// Else it uses the default client
_middleware?.Invoke(request);
} // Scope is disposed.
}
}
private class SomeEventProcessor : ISentryEventProcessor
{
public SentryEvent Process(SentryEvent @event)
{
// Here you can modify the event as you need
if (@event.Level > SentryLevel.Info)
{
@event.AddBreadcrumb("Processed by " + nameof(SomeEventProcessor));
}
return @event;
}
}
private class ArgumentExceptionProcessor : SentryEventExceptionProcessor<ArgumentException>
{
protected override void ProcessException(ArgumentException exception, SentryEvent sentryEvent)
{
// Handle specific types of exceptions and add more data to the event
sentryEvent.SetTag("parameter-name", exception.ParamName);
}
}
private class MyCustomerScopeStateProcessor : ISentryScopeStateProcessor
{
private readonly ISentryScopeStateProcessor _fallback = new DefaultSentryScopeStateProcessor();
public void Apply(Scope scope, object state)
{
if (state is SpecialContextObject specialState)
{
scope.SetTag("SpecialContextObject", specialState.A + specialState.B);
}
else
{
_fallback.Apply(scope, state);
}
}
}
private class SpecialContextObject
{
public string A { get; } = "hello";
public string B { get; } = "world";
}
}