From fbdfcd5244896ccfa135891c373f7ae4caac3fce Mon Sep 17 00:00:00 2001 From: Marcell Toth Date: Sat, 7 Sep 2019 19:03:21 +0200 Subject: [PATCH] Implement more sophisticated error logging during web refresh --- NeptunLight/NeptunLight.Android/App.cs | 5 ++ .../NeptunLight.Android.csproj | 1 + .../Properties/AndroidManifest.xml | 2 +- .../Services/AppCenterLogger.cs | 18 ++++++ .../DataAccess/WebNeptunInterface.cs | 64 ++++++++++++++++--- NeptunLight/NeptunLight/Services/ILogger.cs | 10 +++ 6 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 NeptunLight/NeptunLight.Android/Services/AppCenterLogger.cs create mode 100644 NeptunLight/NeptunLight/Services/ILogger.cs diff --git a/NeptunLight/NeptunLight.Android/App.cs b/NeptunLight/NeptunLight.Android/App.cs index 0d5f45a..e82799d 100644 --- a/NeptunLight/NeptunLight.Android/App.cs +++ b/NeptunLight/NeptunLight.Android/App.cs @@ -35,10 +35,15 @@ public class App : Application .As() .SingleInstance(); + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() .As() .InstancePerDependency(); + builder.Register(context => MainActivity) .As(); diff --git a/NeptunLight/NeptunLight.Android/NeptunLight.Android.csproj b/NeptunLight/NeptunLight.Android/NeptunLight.Android.csproj index 8efd6b2..2eb3804 100644 --- a/NeptunLight/NeptunLight.Android/NeptunLight.Android.csproj +++ b/NeptunLight/NeptunLight.Android/NeptunLight.Android.csproj @@ -81,6 +81,7 @@ + diff --git a/NeptunLight/NeptunLight.Android/Properties/AndroidManifest.xml b/NeptunLight/NeptunLight.Android/Properties/AndroidManifest.xml index 1b23408..557d0e9 100644 --- a/NeptunLight/NeptunLight.Android/Properties/AndroidManifest.xml +++ b/NeptunLight/NeptunLight.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/NeptunLight/NeptunLight.Android/Services/AppCenterLogger.cs b/NeptunLight/NeptunLight.Android/Services/AppCenterLogger.cs new file mode 100644 index 0000000..8166c08 --- /dev/null +++ b/NeptunLight/NeptunLight.Android/Services/AppCenterLogger.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Microsoft.AppCenter.Crashes; +using NeptunLight.Services; + +namespace NeptunLight.Droid.Services +{ + /// + /// Implements using the AppCenter SDK + /// + public class AppCenterLogger : ILogger + { + public void LogError(Exception ex, Dictionary context = null) + { + Crashes.TrackError(ex, context); + } + } +} \ No newline at end of file diff --git a/NeptunLight/NeptunLight/DataAccess/WebNeptunInterface.cs b/NeptunLight/NeptunLight/DataAccess/WebNeptunInterface.cs index e3107bb..233a587 100644 --- a/NeptunLight/NeptunLight/DataAccess/WebNeptunInterface.cs +++ b/NeptunLight/NeptunLight/DataAccess/WebNeptunInterface.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using AngleSharp.Dom; using AngleSharp.Dom.Html; +using AngleSharp.Extensions; using AngleSharp.Parser.Html; using JetBrains.Annotations; using NeptunLight.Helpers; @@ -31,6 +32,7 @@ public class WebNeptunInterface : INeptunInterface private readonly IPrimitiveStorage _primitiveStorage; private readonly Func _clientFactory; + private readonly ILogger _logger; private Uri _baseUri; private WebScraperClient _client; @@ -38,11 +40,12 @@ public class WebNeptunInterface : INeptunInterface private string _username; - public WebNeptunInterface([CanBeNull] IMailContentCache mailContentCache, [CanBeNull] IPrimitiveStorage primitiveStorage, Func clientFactory) + public WebNeptunInterface([CanBeNull] IMailContentCache mailContentCache, [CanBeNull] IPrimitiveStorage primitiveStorage, Func clientFactory, ILogger logger) { _mailContentCache = mailContentCache; _primitiveStorage = primitiveStorage; _clientFactory = clientFactory; + _logger = logger; } public string Username @@ -125,7 +128,7 @@ public IObservable RefreshMessages(IProgress progr IDocument rawMessagesDocument = await parser.ParseAsync(rawMessages, ct); IHtmlTableElement messageHeaderTable = (IHtmlTableElement) rawMessagesDocument.GetElementById("c_messages_gridMessages_bodytable"); - IList mailHeaders = ParseMailHeaderTable(messageHeaderTable); + IList mailHeaders = ParseMailHeaderTable(messageHeaderTable, _logger); int messagesToLoad = Math.Min(mailHeaders.Count, 100); for (int i = 0; i < messagesToLoad; i++) { @@ -176,7 +179,7 @@ await Task.Run(async () => catch (Exception e) { // Do not crash if a single mail throws - // TODO: implement logging + _logger.LogError(new MailParsingException(mailHeader, e)); } }, ct); } @@ -215,6 +218,7 @@ public async Task>> R IEnumerable semesterOptions = subjectsPage.GetElementById("cmb_cmb").Children.Where(opt => opt.GetAttribute("value") != "-1"); foreach (IElement option in semesterOptions) { + IDocument semesterSubjectData = null; try { await Task.Delay(200); @@ -224,7 +228,7 @@ public async Task>> R // load subcject data in semester subjectsPage = await _client.GetDocumentAsnyc("main.aspx?ismenuclick=true&ctrl=0304"); await Task.Delay(100); - IDocument semesterSubjectData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0304", subjectsPage, new[] {new KeyValuePair("upFilter$cmb$m_cmb", option.GetAttribute("value"))}); + semesterSubjectData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0304", subjectsPage, new[] {new KeyValuePair("upFilter$cmb$m_cmb", option.GetAttribute("value"))}); IHtmlTableElement subjectDataTable = (IHtmlTableElement) semesterSubjectData.GetElementById("h_addedsubjects_gridAddedSubjects_bodytable"); // load course data @@ -266,7 +270,7 @@ public async Task>> R catch (Exception e) { // do not fail because of a single semester failing - // TODO: log + _logger.LogError(new SubjectListParsingException(semesterSubjectData?.DocumentElement.OuterHtml ?? "NOT LOADED", e)); } } @@ -282,6 +286,7 @@ public async Task>> Refr IEnumerable semesterOptions = examsPage.GetElementById("upFilter_cmbTerms").Children.Where(opt => opt.GetAttribute("value") != "-1"); foreach (IElement option in semesterOptions.Take(8)) { + IDocument semesterExamData = null; try { Semester semester = Semester.Parse(option.TextContent); @@ -294,7 +299,7 @@ public async Task>> Refr await Task.Delay(200); examsPage = await _client.GetDocumentAsnyc("main.aspx?ismenuclick=true&ctrl=0402"); await Task.Delay(100); - IDocument semesterExamData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0402", examsPage, new[] {new KeyValuePair("upFilter$cmbTerms", option.GetAttribute("value"))}); + semesterExamData = await _client.PostFormAsnyc("main.aspx?ismenuclick=true&ctrl=0402", examsPage, new[] {new KeyValuePair("upFilter$cmbTerms", option.GetAttribute("value"))}); IHtmlTableElement subjectDataTable = (IHtmlTableElement) semesterExamData.GetElementById("h_signedexams_gridExamList_bodytable"); foreach (IHtmlTableRowElement dataRow in subjectDataTable.Bodies[0].Rows) @@ -334,6 +339,7 @@ public async Task>> Refr catch (Exception ex) { // don't fail the whole sync if one semester load fails + _logger.LogError(new ExamListParsingException(semesterExamData?.DocumentElement.OuterHtml ?? "NOT LOADED", ex)); } } @@ -419,7 +425,7 @@ public async Task> RefreshPeriodsAsnyc() return result; } - private static IList ParseMailHeaderTable(IHtmlTableElement table) + private static IList ParseMailHeaderTable(IHtmlTableElement table, ILogger logger) { List result = new List(); foreach (IHtmlTableRowElement row in table.Bodies[0].Rows) @@ -432,11 +438,53 @@ private static IList ParseMailHeaderTable(IHtmlTableElement table) bool isNew = row.ClassName == "Row1_Bold"; result.Add(new MailHeader(receiveTime, sender, title) {TrId = trid, IsNew = isNew}); } - catch (Exception) + catch (Exception e) { // skip unparsable stuff + logger.LogError(new MailHeaderParsingException(row.OuterHtml, e)); } return result; } + + private class MailParsingException : Exception + { + public MailHeader Header { get; } + public override string Message => $"Unable to load mail for header: ID={Header.TrId}, ReceivedTime={Header.ReceivedTime}, IsNew={Header.IsNew}"; + + public MailParsingException(MailHeader header, Exception innerException) : base(String.Empty, innerException) + { + Header = header; + } + } + + private class MailHeaderParsingException : Exception + { + public string InputHtml { get; } + + public MailHeaderParsingException(string inputHtml, Exception innerException) : base($"Unable to parse mail header, HTML: '{inputHtml}'", innerException) + { + InputHtml = inputHtml; + } + } + + private class SubjectListParsingException : Exception + { + public string InputHtml { get; } + + public SubjectListParsingException(string inputHtml, Exception innerException) : base($"Unable to parse semester subject list, HTML: '{inputHtml}'", innerException) + { + InputHtml = inputHtml; + } + } + + private class ExamListParsingException : Exception + { + public string InputHtml { get; } + + public ExamListParsingException(string inputHtml, Exception innerException) : base($"Unable to parse semester exam list, HTML: '{inputHtml}'", innerException) + { + InputHtml = inputHtml; + } + } } } \ No newline at end of file diff --git a/NeptunLight/NeptunLight/Services/ILogger.cs b/NeptunLight/NeptunLight/Services/ILogger.cs new file mode 100644 index 0000000..ff10d8d --- /dev/null +++ b/NeptunLight/NeptunLight/Services/ILogger.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace NeptunLight.Services +{ + public interface ILogger + { + void LogError(Exception ex, Dictionary context = null); + } +} \ No newline at end of file