I'm a big fan of the Sentry service which is used to send error reports.
Each time an unexpected error occurs in one of my applications, a certain amount of information is collected (exception type and message, call stack, operating system version, user name...) then sent to Sentry's servers.
Each event is grouped with other similar events (same exception type, same message) to form a "problem". I can then locate the source of the error and correct it without having to wait for the user to give me the details of the problem.
Lately, I had a small update to do on an old application that had been developed at the time with the .NET 2.0 framework. I thought it would be nice to set up error collection with Sentry. So I search NuGet to download and install SharpRaven, which is the C# client for Sentry, into my application. The problem is that it is only available from .NET 4.0. Updating the application proved complicated, so I was about to drop it. But when you think about it, sending the data certainly had to be done with a simple HTTP request and so it couldn't have been that complicated to do. So I started reading the SDK documentation.
A few minutes later I managed to create my first event. It took me a little longer to fine-tune a few details, but in the end I was able to integrate Sentry support into my NET 2.0 application.
Here is the code, maybe it will be useful if you can't, for one reason or another, use SharpRaven :
class RavenClient { class Packet { public string Message { get; set; } public string Platform { get; set; } public ExceptionInterface Exception { get; } = new ExceptionInterface(); public IDictionary<string, string> Modules { get; } = new SortedDictionary<string, string>(); public string Release { get; set; } public User User { get; } = new User(); public string ServerName { get; set; } } class User { public string Username { get; set; } } class SentryStackTrace { public List<Frame> Frames { get; } = new List<Frame>(); } class ExceptionValue { public string Type { get; set; } public string Value { get; set; } public SentryStackTrace Stacktrace { get; } = new SentryStackTrace(); } class ExceptionInterface { public List<ExceptionValue> Values { get; } = new List<ExceptionValue>(); } class Frame { public string Filename { get; set; } public string Function { get; set; } public string Module { get; set; } public int Lineno { get; set; } public int Colno { get; set; } public string Package { get; set; } public string Source { get; set; } public string InstructionOffset { get; set; } } class Response { public string Id { get; set; } } #region Fields readonly string uri; readonly string publicKey; readonly string projectId; #endregion #region Constructors public RavenClient(string dsn) { if (dsn == null) throw new ArgumentNullException(nameof(dsn)); var dsnAsUri = new Uri(dsn); uri = dsnAsUri.Scheme + "://" + dsnAsUri.Host; publicKey = dsnAsUri.UserInfo; projectId = dsnAsUri.Segments[dsnAsUri.Segments.Length - 1]; } #endregion #region Methods public string CaptureException(Exception exception) { if (exception == null) throw new ArgumentNullException(nameof(exception)); var report = new Packet { Message = exception.Message, Platform = "csharp", Release = Assembly.GetExecutingAssembly().GetName().Version?.ToString(), User = { Username = Environment.UserName }, ServerName = Environment.MachineName }; foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { string name = assembly.GetName().Name; if (name != null) report.Modules[name] = assembly.GetName().Version?.ToString(); } for (Exception currentException = exception ; currentException != null; currentException = currentException.InnerException) { var exceptionValue = new ExceptionValue { Type = exception.GetType().FullName, Value = exception.Message }; report.Exception.Values.Add(exceptionValue); var st = new StackTrace(exception, fNeedFileInfo: true); StackFrame[] stackFrames = st.GetFrames(); if (stackFrames != null) { foreach (StackFrame stackFrame in stackFrames) { MethodBase method = stackFrame.GetMethod(); var frame = new Frame { Filename = stackFrame.GetFileName(), Lineno = stackFrame.GetFileLineNumber(), Colno = stackFrame.GetFileColumnNumber(), Function = method?.Name, Module = method?.DeclaringType?.FullName, Package = method?.DeclaringType?.Assembly.GetName().Name, Source = method?.ToString(), InstructionOffset = "0x" + stackFrame.GetILOffset().ToString("x") }; exceptionValue.Stacktrace.Frames.Insert(0, frame); } } } var contractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() }; var jsonSerializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, // Converts the property names from "PascalCase" to "snake_case". ContractResolver = contractResolver }; string packetAsString; using (var sw = new StringWriter()) { jsonSerializer.Serialize(sw, report); packetAsString = sw.ToString(); } int unixTimestamp = (int) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; string responseAsString = HttpPost.Send( $"{uri}/api/{projectId}/store/?sentry_version=7&sentry_key={publicKey}&sentry_client=wiip/1.0&sentry_timestamp={unixTimestamp}", packetAsString); var response = JsonConvert.DeserializeObject<Response>(responseAsString); return response?.Id; } #endregion }
Some remarks on this code.
for
loop used by SharpRaven to browse the different InnerException
. I wouldn't have thought of that approach, it's very elegant;public static class HttpPost { public static string Send(string uri, string content) { bool expect100Continue = ServicePointManager.Expect100Continue; try { // To avoid a 417 error with some proxy. // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c ServicePointManager.Expect100Continue = false; var rq = (HttpWebRequest) WebRequest.Create(uri); // To avoid a 407 error. rq.UseDefaultCredentials = true; rq.ContentType = "application/x-www-form-urlencoded"; rq.Method = "POST"; byte[] bytes = Encoding.UTF8.GetBytes(content); rq.ContentLength = bytes.Length; using (Stream os = rq.GetRequestStream()) { os.Write(bytes, 0, bytes.Length); } using (WebResponse webResponse = rq.GetResponse()) { Stream responseStream = webResponse.GetResponseStream(); if (responseStream == null) return null; using (var sr = new StreamReader(responseStream)) { return sr.ReadToEnd().Trim(); } } } finally { ServicePointManager.Expect100Continue = expect100Continue; } } }
It's all for today, hoping it can be used by others. Feel free to try Sentry if you haven't already, there is a free plan where the number of daily events is limited.
Ajouter un commentaire