From 0d32fac441f603f9a2e9bcee376b7715fe9993f2 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Mon, 24 Sep 2018 17:49:43 +0200 Subject: [PATCH] Version 2.0.4 (#16) ## Version 2.0.4 - 23.09.2018 - `BacktraceClient` allows developer to unpack `AggregateException` and send only exceptions available in `InnerExceptions` property. - `BacktraceReport` accepts two new properties: `Factor` and `Fingerprint`. These properties allows you to change server side algorithms. - `BacktraceData` now include informations about `Exception` properties. You can check detailed `Exception` properties in Annotations. - `BacktraceDatabase` doesn't throw exception when developer can't add new record. This situation exists when database was full or database hasn't enough disk space for exceptions. - `BacktraceResult` can use new `Status`. In case when developer want to unpack `AggregateException` and `InnerExceptions` property is empty, `BacktraceClient` return `BacktraceResult` with status `Empty`, --- Backtrace.Tests/Backtrace.Tests.csproj | 6 -- .../ClientTests/AggregateExceptionTests.cs | 70 +++++++++++++++ .../ClientTests/LogCreationBase.cs | 8 +- Backtrace.Tests/DatabaseTests/BatchTests.cs | 48 ++++++++-- .../DatabaseTests/DatabaseConditionTests.cs | 15 ++-- .../DatabaseContextOperationTests.cs | 26 +++--- .../DatabaseTests/DatabaseSetupTests.cs | 2 +- .../DatabaseTests/Model/DatabaseTestBase.cs | 15 ++-- .../BacktraceAttributesTests.cs | 44 +++++++-- .../DatabaseEventBehaviourTests.cs | 2 +- .../DatabaseIntegrationTests.cs | 16 ++-- Backtrace/Backtrace.csproj | 8 +- Backtrace/BacktraceClient.cs | 9 +- Backtrace/BacktraceDatabase.cs | 87 ++++++++++++------ Backtrace/Base/BacktraceBase.cs | 90 +++++++++++-------- Backtrace/Extensions/DictionaryExtensions.cs | 6 +- .../Interfaces/IBacktraceDatabaseContext.cs | 6 +- Backtrace/Model/BacktraceReport.cs | 23 +++-- Backtrace/Model/BacktraceResult.cs | 10 +++ .../Model/Database/BacktraceDatabaseRecord.cs | 12 +-- .../Database/BacktraceDatabaseSettings.cs | 2 +- .../Model/JsonData/BacktraceAttributes.cs | 23 ++++- Backtrace/Model/JsonData/ThreadData.cs | 18 ++-- .../Services/BacktraceDatabaseContext.cs | 50 +++++++++-- .../Services/BacktraceDatabaseFileContext.cs | 1 - Backtrace/Types/BacktraceResultStatus.cs | 6 +- CHANGELOG.md | 7 ++ Examples/Backtrace.Core/Program.cs | 1 - .../Backtrace.Framework45Example/Program.cs | 20 +++-- README.md | 35 ++++++-- appveyor.yml | 16 ++-- 31 files changed, 485 insertions(+), 197 deletions(-) create mode 100644 Backtrace.Tests/ClientTests/AggregateExceptionTests.cs diff --git a/Backtrace.Tests/Backtrace.Tests.csproj b/Backtrace.Tests/Backtrace.Tests.csproj index 588da2e..71ee28e 100644 --- a/Backtrace.Tests/Backtrace.Tests.csproj +++ b/Backtrace.Tests/Backtrace.Tests.csproj @@ -12,12 +12,6 @@ - - - - - - diff --git a/Backtrace.Tests/ClientTests/AggregateExceptionTests.cs b/Backtrace.Tests/ClientTests/AggregateExceptionTests.cs new file mode 100644 index 0000000..cc7fc69 --- /dev/null +++ b/Backtrace.Tests/ClientTests/AggregateExceptionTests.cs @@ -0,0 +1,70 @@ +using Backtrace.Interfaces; +using Backtrace.Model; +using Backtrace.Types; +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Backtrace.Tests.ClientTests +{ + [TestFixture( + Author = "Konrad Dysput", + Category = "Client.AggreagateException", + Description = "Test client bahaviour for handling aggreagate exceptions")] + public class AggregateExceptionTests + { + protected BacktraceClient _backtraceClient; + + [SetUp] + public virtual void Setup() + { + var api = new Mock(); + api.Setup(n => n.Send(It.IsAny())) + .Returns(new BacktraceResult() { Status = BacktraceResultStatus.Ok }); + + api.Setup(n => n.SendAsync(It.IsAny())) + .ReturnsAsync(() => new BacktraceResult() { Status = BacktraceResultStatus.Ok }); + + var credentials = new BacktraceCredentials(@"https://validurl.com/", "validToken"); + _backtraceClient = new BacktraceClient(credentials) + { + BacktraceApi = api.Object, + UnpackAggregateExcetpion = true + }; + } + + [Test(Description = "Test empty aggregate exception")] + public void TestEmptyAggreagateException() + { + var aggregateException = new AggregateException("test exception"); + var result = _backtraceClient.Send(aggregateException); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, BacktraceResultStatus.Empty); + } + + [Test(Description = "Test default scenario for aggregate exception")] + public void TestAggreagateException() + { + var aggregateException = new AggregateException("test exception", + new List() { + new ArgumentException("argument exception"), + new InvalidOperationException("invalid operation exception"), + new FormatException("format exception"), + }); + var result = _backtraceClient.Send(aggregateException); + + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, BacktraceResultStatus.Ok); + + int totalReports = 0; + while (result != null) + { + totalReports++; + result = result.InnerExceptionResult; + } + Assert.AreEqual(3, totalReports); + + } + } +} diff --git a/Backtrace.Tests/ClientTests/LogCreationBase.cs b/Backtrace.Tests/ClientTests/LogCreationBase.cs index 7ae4727..7222ac0 100644 --- a/Backtrace.Tests/ClientTests/LogCreationBase.cs +++ b/Backtrace.Tests/ClientTests/LogCreationBase.cs @@ -2,11 +2,7 @@ using Backtrace.Model; using Moq; using NUnit.Framework; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Backtrace.Tests.ClientTests { @@ -19,7 +15,9 @@ public class LogCreationBase public virtual void Setup() { var api = new Mock(); - api.Setup(n => n.Send(It.IsAny())).Returns(new BacktraceResult()); + api.Setup(n => n.Send(It.IsAny())) + .Returns(new BacktraceResult() { Status = Types.BacktraceResultStatus.Ok }); + var credentials = new BacktraceCredentials(@"https://validurl.com/", "validToken"); _backtraceClient = new BacktraceClient(credentials) { diff --git a/Backtrace.Tests/DatabaseTests/BatchTests.cs b/Backtrace.Tests/DatabaseTests/BatchTests.cs index 6da019b..8694708 100644 --- a/Backtrace.Tests/DatabaseTests/BatchTests.cs +++ b/Backtrace.Tests/DatabaseTests/BatchTests.cs @@ -1,10 +1,5 @@ using Backtrace.Tests.DatabaseTests.Model; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Backtrace.Tests.DatabaseTests { @@ -17,11 +12,12 @@ public class BatchTests : DatabaseTestBase /// /// Add record to first batch on context /// - private void AddRecords(int numberOfRecordsOnBatch) + private void AddRecords(int numberOfRecordsOnBatch, bool locked = false) { for (int i = 0; i < numberOfRecordsOnBatch; i++) { - _database.BacktraceDatabaseContext.Add(GetRecord()); + var fakeRecord = _database.BacktraceDatabaseContext.Add(GetRecord()); + fakeRecord.Locked = locked; } } [TestCase(3, 2, 0)] @@ -54,5 +50,43 @@ public void TestBatchAdd(int recordsOnFirstBatch, int recordsOnSecoundBatch, int totalRecords = _database.BacktraceDatabaseContext.Count(); Assert.AreEqual(totalRecords, recordsOnFirstBatch + recordsOnSecoundBatch); } + + [Test] + public void TestRecordLimitInBatches() + { + _database.Start(); + _database.Clear(); + //value from database settings + int maxRecordCount = 100; + AddRecords(maxRecordCount); + + var report = new Backtrace.Model.BacktraceReport("report"); + var result = _database.Add( + backtraceReport: report, + attributes: null, + miniDumpType: Types.MiniDumpType.None + ); + Assert.IsNotNull(result); + Assert.AreEqual(maxRecordCount, _database.Count()); + } + + [Test] + public void TestRecordLimitWithAllReservedBatches() + { + _database.Start(); + _database.Clear(); + //value from database settings + int maxRecordCount = 100; + AddRecords(maxRecordCount, true); + + var report = new Backtrace.Model.BacktraceReport("report"); + var result = _database.Add( + backtraceReport: report, + attributes: null, + miniDumpType: Types.MiniDumpType.None + ); + Assert.IsNull(result); + Assert.AreEqual(maxRecordCount, _database.Count()); + } } } diff --git a/Backtrace.Tests/DatabaseTests/DatabaseConditionTests.cs b/Backtrace.Tests/DatabaseTests/DatabaseConditionTests.cs index 2db39bd..6a40bb5 100644 --- a/Backtrace.Tests/DatabaseTests/DatabaseConditionTests.cs +++ b/Backtrace.Tests/DatabaseTests/DatabaseConditionTests.cs @@ -1,14 +1,12 @@ using Backtrace.Extensions; using Backtrace.Interfaces; +using Backtrace.Model; using Backtrace.Model.Database; using Moq; using NUnit.Framework; using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Backtrace.Tests.DatabaseTests { @@ -23,7 +21,7 @@ public class DatabaseConditionTests /// /// Current project directory - any database path /// - private readonly string _projectDirectory = Environment.CurrentDirectory; + private readonly string _projectDirectory = System.IO.Path.GetTempPath(); /// /// Total number of reports @@ -41,7 +39,8 @@ public void Setup() var backtraceDatabaseSettings = new BacktraceDatabaseSettings(_projectDirectory) { - MaxRecordCount = _totalNumberOfReports + MaxRecordCount = _totalNumberOfReports, + RetryBehavior = Types.RetryBehavior.NoRetry }; _backtraceDatabase = new BacktraceDatabase(backtraceDatabaseSettings) @@ -59,9 +58,11 @@ public void TestDatabaseRecordLimitConditions() _backtraceDatabase.Clear(); for (int i = 0; i < _totalNumberOfReports; i++) { - _backtraceDatabase.Add(backtraceReport, new Dictionary(), Types.MiniDumpType.None); + var record = _backtraceDatabase.Add(backtraceReport, new Dictionary(), Types.MiniDumpType.None); + record.Dispose(); } - Assert.Throws(() => _backtraceDatabase.Add(backtraceReport, new Dictionary(),Types.MiniDumpType.None)); + _backtraceDatabase.Add(backtraceReport, new Dictionary(), Types.MiniDumpType.None); + Assert.AreEqual(_totalNumberOfReports, _backtraceDatabase.Count()); } [TestCase(1)] diff --git a/Backtrace.Tests/DatabaseTests/DatabaseContextOperationTests.cs b/Backtrace.Tests/DatabaseTests/DatabaseContextOperationTests.cs index e1eb7b6..f50e7a9 100644 --- a/Backtrace.Tests/DatabaseTests/DatabaseContextOperationTests.cs +++ b/Backtrace.Tests/DatabaseTests/DatabaseContextOperationTests.cs @@ -1,14 +1,9 @@ -using Backtrace.Interfaces; +using Backtrace.Model; using Backtrace.Model.Database; using Backtrace.Tests.DatabaseTests.Model; using Backtrace.Types; -using Moq; using NUnit.Framework; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Backtrace.Tests.DatabaseTests { @@ -36,14 +31,23 @@ public void TestMaximumNumberOfRecords() { _database.Clear(); //we set maximum number of records to equal to 100 in Setup method on DatabaseTestBase class - int maximumNumberOfRecords = 101; + int maximumNumberOfRecords = 100; //we add 100 records - 100 is our database limit - for (int i = 0; i < maximumNumberOfRecords - 1; i++) + for (int i = 0; i < maximumNumberOfRecords; i++) { - _database.BacktraceDatabaseContext.Add(GetRecord()); + var fakeRecord = GetRecord(); + var record = _database.BacktraceDatabaseContext.Add(fakeRecord); + fakeRecord.Locked = false; } _database.Start(); - Assert.Throws(() => _database.Add(null, new Dictionary(), MiniDumpType.None)); + _database.Add( + backtraceReport: new BacktraceReport("fake report"), + attributes: new Dictionary(), + miniDumpType: MiniDumpType.None); + + // in the end BacktraceDatabase should contain 100 reports. + // Database should remove first ever report. + Assert.AreEqual(_database.Count(), maximumNumberOfRecords); } @@ -82,7 +86,7 @@ public void TestFifoListOrder(int totalNumberOfRecords) { var record = GetRecord(); records.Add(record); - _database.BacktraceDatabaseContext.Add(record); + var dbRecord = _database.BacktraceDatabaseContext.Add(record); } DisposeRecords(); foreach (var record in records) diff --git a/Backtrace.Tests/DatabaseTests/DatabaseSetupTests.cs b/Backtrace.Tests/DatabaseTests/DatabaseSetupTests.cs index 9978ee4..47a35ae 100644 --- a/Backtrace.Tests/DatabaseTests/DatabaseSetupTests.cs +++ b/Backtrace.Tests/DatabaseTests/DatabaseSetupTests.cs @@ -20,7 +20,7 @@ public class DatabaseSetupTests /// /// Current project directory /// - private readonly string _projectDirectory = Environment.CurrentDirectory; + private readonly string _projectDirectory = System.IO.Path.GetTempPath(); [Test(Author = "Konrad Dysput", Description = "Test database initialization")] public void TestDatabaseInitalizationConditions() diff --git a/Backtrace.Tests/DatabaseTests/Model/DatabaseTestBase.cs b/Backtrace.Tests/DatabaseTests/Model/DatabaseTestBase.cs index 39ac321..c563628 100644 --- a/Backtrace.Tests/DatabaseTests/Model/DatabaseTestBase.cs +++ b/Backtrace.Tests/DatabaseTests/Model/DatabaseTestBase.cs @@ -1,6 +1,4 @@ -using Backtrace.Base; -using Backtrace.Interfaces; -using Backtrace.Interfaces.Database; +using Backtrace.Interfaces; using Backtrace.Model; using Backtrace.Model.Database; using Backtrace.Services; @@ -10,9 +8,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Backtrace.Tests.DatabaseTests.Model { @@ -30,7 +25,7 @@ public class DatabaseTestBase public virtual void Setup() { //get project path - string projectPath = Environment.CurrentDirectory; + string projectPath = Path.GetTempPath(); //mock api var mockApi = new Mock(); @@ -49,9 +44,10 @@ public virtual void Setup() var settings = new BacktraceDatabaseSettings(projectPath) { + RetryBehavior = RetryBehavior.NoRetry, AutoSendMode = false, //we don't want to test timers MaxRecordCount = 100, - RetryLimit = 3 + RetryLimit = 3 }; _database = new BacktraceDatabase(settings) { @@ -85,6 +81,9 @@ protected BacktraceDatabaseRecord GetRecord() mockRecord.Setup(n => n.Delete()); mockRecord.Setup(n => n.BacktraceData) .Returns(new BacktraceData(It.IsAny(), It.IsAny>())); + mockRecord.Setup(n => n.Valid()) + .Returns(true); + var data = new BacktraceData(null, new Dictionary()); mockRecord.SetupProperty(n => n.Record, data); diff --git a/Backtrace.Tests/EnvironmentTests/BacktraceAttributesTests.cs b/Backtrace.Tests/EnvironmentTests/BacktraceAttributesTests.cs index 1724fc4..0c2ee4e 100644 --- a/Backtrace.Tests/EnvironmentTests/BacktraceAttributesTests.cs +++ b/Backtrace.Tests/EnvironmentTests/BacktraceAttributesTests.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; -using Backtrace.Base; -using Backtrace.Model; +using Backtrace.Model; using Backtrace.Model.JsonData; using NUnit.Framework; +using System.Collections.Generic; namespace Backtrace.Tests.EnvironmentTests { /// @@ -24,15 +20,47 @@ public void TestAttributesCreation() //test empty exception Assert.DoesNotThrow(() => { - var backtraceAttributes = new BacktraceAttributes(report,new Dictionary()); + var backtraceAttributes = new BacktraceAttributes(report, new Dictionary()); backtraceAttributes.SetExceptionAttributes(new BacktraceReport("message")); }); //test null Assert.DoesNotThrow(() => { - var backtraceAttributes = new BacktraceAttributes(report,new Dictionary()); + var backtraceAttributes = new BacktraceAttributes(report, new Dictionary()); backtraceAttributes.SetExceptionAttributes(null); }); } + + + [Test] + public void TestFingerprintAttribute() + { + string fingerprint = "fingerprint for testing purpose"; + var report = new BacktraceReport("testMessage") + { + Fingerprint = fingerprint + }; + var attributes = new BacktraceAttributes(report, null); + Assert.IsTrue(attributes.Attributes.ContainsKey("_mod_fingerprint")); + Assert.IsFalse(attributes.Attributes.ContainsKey("_mod_factor")); + + Assert.AreEqual(attributes.Attributes["_mod_fingerprint"], fingerprint); + } + + [Test] + public void TestFactorAttribute() + { + string factor = "factor attribute value"; + var report = new BacktraceReport("testMessage") + { + Factor = factor + }; + + var attributes = new BacktraceAttributes(report, null); + Assert.IsTrue(attributes.Attributes.ContainsKey("_mod_factor")); + + Assert.AreEqual(attributes.Attributes["_mod_factor"], factor); + Assert.IsFalse(attributes.Attributes.ContainsKey("_mod_fingerprint")); + } } } diff --git a/Backtrace.Tests/IntegrationTests/DatabaseEventBehaviourTests.cs b/Backtrace.Tests/IntegrationTests/DatabaseEventBehaviourTests.cs index c6503d0..325e95d 100644 --- a/Backtrace.Tests/IntegrationTests/DatabaseEventBehaviourTests.cs +++ b/Backtrace.Tests/IntegrationTests/DatabaseEventBehaviourTests.cs @@ -56,7 +56,7 @@ public void Setup() { _lastEntry = GetEntry(); //get project path - string projectPath = Environment.CurrentDirectory; + string projectPath = System.IO.Path.GetTempPath(); //setup credentials var credentials = new BacktraceCredentials("https://validurl.com/", "validToken"); //mock api diff --git a/Backtrace.Tests/IntegrationTests/DatabaseIntegrationTests.cs b/Backtrace.Tests/IntegrationTests/DatabaseIntegrationTests.cs index 6cbc3ef..855163e 100644 --- a/Backtrace.Tests/IntegrationTests/DatabaseIntegrationTests.cs +++ b/Backtrace.Tests/IntegrationTests/DatabaseIntegrationTests.cs @@ -1,5 +1,4 @@ -using Backtrace.Base; -using Backtrace.Extensions; +using Backtrace.Extensions; using Backtrace.Interfaces; using Backtrace.Model; using Backtrace.Model.Database; @@ -12,8 +11,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Backtrace.Tests.IntegrationTests @@ -24,19 +21,18 @@ public class DatabaseIntegrationTests /// /// Client /// - BacktraceClient _backtraceClient; + private BacktraceClient _backtraceClient; /// /// Last database record /// - BacktraceDatabaseRecord _lastRecord; + private BacktraceDatabaseRecord _lastRecord; /// /// Enable hard drive write errors /// - bool _enableWriteErrors = false; - - bool _writeFail = false; + private bool _enableWriteErrors = false; + private bool _writeFail = false; /// /// Get new database record @@ -64,7 +60,7 @@ public void Setup() { _lastRecord = GetRecord(); //get project path - string projectPath = Environment.CurrentDirectory; + string projectPath = Path.GetTempPath(); //setup credentials var credentials = new BacktraceCredentials("https://validurl.com/", "validToken"); //mock api diff --git a/Backtrace/Backtrace.csproj b/Backtrace/Backtrace.csproj index a2c3782..c1e1d3c 100644 --- a/Backtrace/Backtrace.csproj +++ b/Backtrace/Backtrace.csproj @@ -4,7 +4,7 @@ netstandard2.0;net45;net35 true Backtrace Error Diagnostic Tools Debug Bug Bugs StackTrace - 2.0.3 + 2.0.4 Backtrace https://github.com/backtrace-labs/backtrace-csharp/blob/master/LICENSE https://github.com/backtrace-labs/backtrace-csharp @@ -12,12 +12,12 @@ Backtrace's integration with C# applications allows customers to capture and report handled and unhandled C# exceptions to their Backtrace instance, instantly offering the ability to prioritize and debug software errors. https://github.com/backtrace-labs/backtrace-csharp en - 2.0.3 + 2.0.4 Backtrace I/O Backtrace I/O Backtrace I/O - 2.0.3.0 - 2.0.3.0 + 2.0.4.0 + 2.0.4.0 diff --git a/Backtrace/BacktraceClient.cs b/Backtrace/BacktraceClient.cs index 3b98e8d..1a6b409 100644 --- a/Backtrace/BacktraceClient.cs +++ b/Backtrace/BacktraceClient.cs @@ -159,13 +159,13 @@ public BacktraceClient( /// Current exception /// Additional information about application state /// Path to all report attachments - [Obsolete("Send is obsolete, please use SendAsync instead if possible.")] public virtual BacktraceResult Send( Exception exception, Dictionary attributes = null, List attachmentPaths = null) { - return Send(new BacktraceReport(exception, attributes, attachmentPaths)); + var report = new BacktraceReport(exception, attributes, attachmentPaths); + return Send(report); } /// @@ -174,20 +174,19 @@ public virtual BacktraceResult Send( /// Custom client message /// Additional information about application state /// Path to all report attachments - [Obsolete("Send is obsolete, please use SendAsync instead if possible.")] public virtual BacktraceResult Send( string message, Dictionary attributes = null, List attachmentPaths = null) { - return Send(new BacktraceReport(message, attributes, attachmentPaths)); + var report = new BacktraceReport(message, attributes, attachmentPaths); + return Send(report); } /// /// Sending a backtrace report to Backtrace API /// /// Current report - [Obsolete("Send is obsolete, please use SendAsync instead if possible.")] public override BacktraceResult Send(BacktraceReport backtraceReport) { OnReportStart?.Invoke(backtraceReport); diff --git a/Backtrace/BacktraceDatabase.cs b/Backtrace/BacktraceDatabase.cs index 760aa94..4858a45 100644 --- a/Backtrace/BacktraceDatabase.cs +++ b/Backtrace/BacktraceDatabase.cs @@ -1,5 +1,4 @@ -using Backtrace.Base; -using Backtrace.Common; +using Backtrace.Common; using Backtrace.Interfaces; using Backtrace.Model; using Backtrace.Model.Database; @@ -9,7 +8,6 @@ using System.Collections.Generic; using System.IO; using System.Timers; -using System.Diagnostics; #if !NET35 using System.Threading.Tasks; #endif @@ -110,7 +108,7 @@ public void Start() RemoveOrphaned(); // setup database timer events if (DatabaseSettings.RetryBehavior == RetryBehavior.ByInterval - || !DatabaseSettings.AutoSendMode) + || DatabaseSettings.AutoSendMode) { SetupTimer(); } @@ -166,17 +164,16 @@ public void Clear() /// public BacktraceDatabaseRecord Add(BacktraceReport backtraceReport, Dictionary attributes, MiniDumpType miniDumpType = MiniDumpType.Normal) { - if (!_enable) + if (!_enable || backtraceReport == null) { return null; } - if (BacktraceDatabaseContext.Count() + 1 > DatabaseSettings.MaxRecordCount && DatabaseSettings.MaxRecordCount != 0) - { - throw new ArgumentException("Maximum number of records available in BacktraceDatabase"); - } - if (BacktraceDatabaseContext.GetSize() > DatabaseSettings.MaxDatabaseSize && DatabaseSettings.MaxDatabaseSize != 0) + //remove old reports (if database is full) + //and check database health state + var validationResult = ValidateDatabaseSize(); + if (!validationResult) { - throw new ArgumentException("You don't have enought space in database for more records"); + return null; } if (miniDumpType != MiniDumpType.None) { @@ -196,13 +193,19 @@ public BacktraceDatabaseRecord Add(BacktraceReport backtraceReport, Dictionary /// All stored records in BacktraceDatabase - public IEnumerable Get() => BacktraceDatabaseContext?.Get() ?? new List(); + public IEnumerable Get() + { + return BacktraceDatabaseContext?.Get() ?? new List(); + } /// /// Delete single record from database /// /// Record to delete - public void Delete(BacktraceDatabaseRecord record) => BacktraceDatabaseContext?.Delete(record); + public void Delete(BacktraceDatabaseRecord record) + { + BacktraceDatabaseContext?.Delete(record); + } /// /// Send and delete all records from database @@ -250,7 +253,11 @@ record = BacktraceDatabaseContext.FirstOrDefault(); private async void OnTimedEventAsync(object source, ElapsedEventArgs e) { - if (!BacktraceDatabaseContext.Any() || _timerBackgroundWork) return; + if (!BacktraceDatabaseContext.Any() || _timerBackgroundWork) + { + return; + } + _timerBackgroundWork = true; _timer.Stop(); //read first record (keep in mind LIFO and FIFO settings) from memory database @@ -287,7 +294,11 @@ record = BacktraceDatabaseContext.FirstOrDefault(); #endif private void OnTimedEvent(object source, ElapsedEventArgs e) { - if (!BacktraceDatabaseContext.Any() || _timerBackgroundWork) return; + if (!BacktraceDatabaseContext.Any() || _timerBackgroundWork) + { + return; + } + _timerBackgroundWork = true; _timer.Stop(); //read first record (keep in mind LIFO and FIFO settings) from memory database @@ -352,7 +363,10 @@ private string GenerateMiniDump(BacktraceReport backtraceReport, MiniDumpType mi /// Get total number of records in database /// /// Total number of records - internal int Count() => BacktraceDatabaseContext.Count(); + internal int Count() + { + return BacktraceDatabaseContext.Count(); + } /// /// Detect all orphaned minidump and files @@ -378,21 +392,44 @@ private void LoadReports() continue; } BacktraceDatabaseContext.Add(record); - if (!ValidDatabaseSize()) - { - throw new ArgumentException("Database directory has too many records or database size is bigger than in option declaration."); - } + ValidateDatabaseSize(); record.Dispose(); } } /// - /// Check current size of database + /// Validate database size - check how many records are stored + /// in database and how much records need space. + /// If space or number of records are invalid + /// database will remove old reports /// - /// False if BacktraceDatabase doesn't have more free space - private bool ValidDatabaseSize() + private bool ValidateDatabaseSize() { - return ((BacktraceDatabaseContext.GetSize() < DatabaseSettings.MaxDatabaseSize || DatabaseSettings.MaxDatabaseSize == 0) - || (BacktraceDatabaseContext.GetTotalNumberOfRecords() < DatabaseSettings.MaxRecordCount || DatabaseSettings.MaxDatabaseSize == 0)); + //check how many records are stored in database + //remove in case when we want to store one more than expected number + //If record count == 0 then we ignore this condition + if (BacktraceDatabaseContext.Count() + 1 > DatabaseSettings.MaxRecordCount && DatabaseSettings.MaxRecordCount != 0) + { + if (!BacktraceDatabaseContext.RemoveLastRecord()) + { + return false; + } + } + + //check database size. If database size == 0 then we ignore this condition + //remove all records till database use enough space + if (DatabaseSettings.MaxDatabaseSize != 0) + { + //if your database is entry or every record is locked + //deletePolicyRetry avoid infinity loop + int deletePolicyRetry = 5; + while (BacktraceDatabaseContext.GetSize() > DatabaseSettings.MaxDatabaseSize || deletePolicyRetry != 0) + { + BacktraceDatabaseContext.RemoveLastRecord(); + deletePolicyRetry--; + } + return deletePolicyRetry != 0; + } + return true; } /// diff --git a/Backtrace/Base/BacktraceBase.cs b/Backtrace/Base/BacktraceBase.cs index bade684..467b4d1 100644 --- a/Backtrace/Base/BacktraceBase.cs +++ b/Backtrace/Base/BacktraceBase.cs @@ -17,33 +17,28 @@ namespace Backtrace.Base /// public class BacktraceBase { +#if !NET35 + /// + /// Ignore AggregateException and only prepare report for inner exceptions + /// + public bool UnpackAggregateExcetpion { get; set; } = false; +#endif + /// /// Custom request handler for HTTP call to server /// public Func RequestHandler { - get - { - return BacktraceApi.RequestHandler; - } - set - { - BacktraceApi.RequestHandler = value; - } + get => BacktraceApi.RequestHandler; + set => BacktraceApi.RequestHandler = value; } /// /// Set an event executed when received bad request, unauthorize request or other information from server /// public Action OnServerError { - get - { - return BacktraceApi.OnServerError; - } - set - { - BacktraceApi.OnServerError = value; - } + get => BacktraceApi.OnServerError; + set => BacktraceApi.OnServerError = value; } /// @@ -51,14 +46,8 @@ public Action OnServerError /// public Action OnServerResponse { - get - { - return BacktraceApi.OnServerResponse; - } - set - { - BacktraceApi.OnServerResponse = value; - } + get => BacktraceApi.OnServerResponse; + set => BacktraceApi.OnServerResponse = value; } /// @@ -71,10 +60,7 @@ public Action OnServerResponse /// public Action OnClientReportLimitReached { - set - { - BacktraceApi.SetClientRateLimitEvent(value); - } + set => BacktraceApi.SetClientRateLimitEvent(value); } /// @@ -103,10 +89,7 @@ public Action OnClientReportLimitReached /// internal IBacktraceApi BacktraceApi { - get - { - return _backtraceApi; - } + get => _backtraceApi; set { _backtraceApi = value; @@ -163,9 +146,14 @@ public void SetClientReportLimit(uint reportPerMin) /// Send a report to Backtrace API /// /// Report to send - [Obsolete("Send is obsolete, please use SendAsync instead if possible.")] public virtual BacktraceResult Send(BacktraceReport report) { +#if !NET35 + if (UnpackAggregateExcetpion && report.Exception is AggregateException) + { + return HandleAggregateException(report).Result; + } +#endif var record = Database.Add(report, Attributes, MiniDumpType); //create a JSON payload instance var data = record?.BacktraceData ?? report.ToBacktraceData(Attributes); @@ -173,7 +161,7 @@ public virtual BacktraceResult Send(BacktraceReport report) data = BeforeSend?.Invoke(data) ?? data; var result = BacktraceApi.Send(data); record?.Dispose(); - if (result.Status == BacktraceResultStatus.Ok) + if (result?.Status == BacktraceResultStatus.Ok) { Database.Delete(record); } @@ -207,6 +195,10 @@ private BacktraceResult HandleInnerException(BacktraceReport report) /// Report to send public virtual async Task SendAsync(BacktraceReport report) { + if (UnpackAggregateExcetpion && report.Exception is AggregateException) + { + return await HandleAggregateException(report); + } var record = Database.Add(report, Attributes, MiniDumpType); //create a JSON payload instance var data = record?.BacktraceData ?? report.ToBacktraceData(Attributes); @@ -214,7 +206,7 @@ public virtual async Task SendAsync(BacktraceReport report) data = BeforeSend?.Invoke(data) ?? data; var result = await BacktraceApi.SendAsync(data); record?.Dispose(); - if (result.Status == BacktraceResultStatus.Ok) + if (result?.Status == BacktraceResultStatus.Ok) { Database.Delete(record); } @@ -224,6 +216,34 @@ public virtual async Task SendAsync(BacktraceReport report) return result; } + private async Task HandleAggregateException(BacktraceReport report) + { + AggregateException aggregateException = report.Exception as AggregateException; + BacktraceResult result = null; + + foreach (var ex in aggregateException.InnerExceptions) + { + var innerReport = new BacktraceReport( + exception: ex, + attributes: report.Attributes, + attachmentPaths: report.AttachmentPaths, + reflectionMethodName: report._reflectionMethodName) + { + Factor = report.Factor, + Fingerprint = report.Fingerprint + }; + if (result == null) + { + result = await SendAsync(innerReport); + } + else + { + result.AddInnerResult(await SendAsync(innerReport)); + } + } + return result ?? new BacktraceResult() { Status = BacktraceResultStatus.Empty, BacktraceReport = report }; + } + /// /// Handle inner exception in current backtrace report /// if inner exception exists, client should send report twice - one with current exception, one with inner exception diff --git a/Backtrace/Extensions/DictionaryExtensions.cs b/Backtrace/Extensions/DictionaryExtensions.cs index aa92e99..e42759f 100644 --- a/Backtrace/Extensions/DictionaryExtensions.cs +++ b/Backtrace/Extensions/DictionaryExtensions.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Backtrace.Extensions { @@ -20,11 +18,11 @@ internal static class DictionaryExtensions internal static Dictionary Merge( this Dictionary source, Dictionary toMerge) { - if(source == null) + if (source == null) { throw new ArgumentException(nameof(source)); } - if(toMerge == null) + if (toMerge == null) { throw new ArgumentException(nameof(toMerge)); } diff --git a/Backtrace/Interfaces/IBacktraceDatabaseContext.cs b/Backtrace/Interfaces/IBacktraceDatabaseContext.cs index 027364d..cba1b08 100644 --- a/Backtrace/Interfaces/IBacktraceDatabaseContext.cs +++ b/Backtrace/Interfaces/IBacktraceDatabaseContext.cs @@ -85,9 +85,9 @@ internal interface IBacktraceDatabaseContext : IDisposable int GetTotalNumberOfRecords(); /// - /// Get all repots stored in Database + /// Remove last record in database. /// - //IEnumerable Get(FuncIf algorithm can remove last record, method return true. Otherwise false + bool RemoveLastRecord(); } } diff --git a/Backtrace/Model/BacktraceReport.cs b/Backtrace/Model/BacktraceReport.cs index a8b7dc6..b6aa5a5 100644 --- a/Backtrace/Model/BacktraceReport.cs +++ b/Backtrace/Model/BacktraceReport.cs @@ -1,12 +1,7 @@ -using Backtrace.Common; -using Backtrace.Extensions; -using Backtrace.Model; -using Backtrace.Model.JsonData; +using Backtrace.Extensions; using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Reflection; #if !NET35 using System.Runtime.ExceptionServices; @@ -20,6 +15,16 @@ namespace Backtrace.Model [Serializable] public class BacktraceReport { + /// + /// Fingerprint + /// + public string Fingerprint { get; set; } + + /// + /// Factor + /// + public string Factor { get; set; } + /// /// 16 bytes of randomness in human readable UUID format /// server will reject request if uuid is already found @@ -31,7 +36,7 @@ public class BacktraceReport /// UTC timestamp in seconds /// [JsonProperty(PropertyName = "timestamp")] - public long Timestamp { get; private set; } = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + public long Timestamp { get; private set; } = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; /// /// Get information aboout report type. If value is true the BacktraceReport has an error information @@ -181,8 +186,8 @@ internal BacktraceReport CreateInnerReport() { return null; } - var copy = (BacktraceReport)this.MemberwiseClone(); - copy.Exception = this.Exception.InnerException; + var copy = (BacktraceReport)MemberwiseClone(); + copy.Exception = Exception.InnerException; copy.SetCallingAssemblyInformation(); copy.Classifier = copy.Exception.GetType().Name; return copy; diff --git a/Backtrace/Model/BacktraceResult.cs b/Backtrace/Model/BacktraceResult.cs index 47a97a2..1a318cb 100644 --- a/Backtrace/Model/BacktraceResult.cs +++ b/Backtrace/Model/BacktraceResult.cs @@ -106,5 +106,15 @@ internal static BacktraceResult OnError(BacktraceReport report, Exception except Status = BacktraceResultStatus.ServerError }; } + + internal void AddInnerResult(BacktraceResult innerResult) + { + if(InnerExceptionResult == null) + { + InnerExceptionResult = innerResult; + return; + } + InnerExceptionResult.AddInnerResult(innerResult); + } } } \ No newline at end of file diff --git a/Backtrace/Model/Database/BacktraceDatabaseRecord.cs b/Backtrace/Model/Database/BacktraceDatabaseRecord.cs index 0e586fd..af599d6 100644 --- a/Backtrace/Model/Database/BacktraceDatabaseRecord.cs +++ b/Backtrace/Model/Database/BacktraceDatabaseRecord.cs @@ -169,14 +169,16 @@ public bool Save() RecordWriter.Write(this, $"{Id}-record"); return true; } - catch (IOException) + catch (IOException io) { - Trace.WriteLine($"Received {nameof(IOException)} while saving data to database"); + Trace.WriteLine($"Received {nameof(IOException)} while saving data to database."); + Debug.WriteLine($"Message {io.Message}"); return false; } - catch (Exception) + catch (Exception ex) { - Trace.WriteLine($"Received {nameof(Exception)} while saving data to database"); + Trace.WriteLine($"Received {nameof(Exception)} while saving data to database."); + Debug.WriteLine($"Message {ex.Message}"); return false; } } @@ -203,7 +205,7 @@ private string Save(object data, string prefix) /// Check if all necessary files declared on record exists /// /// True if record is valid - public bool Valid() + internal virtual bool Valid() { return File.Exists(DiagnosticDataPath) && File.Exists(ReportPath); } diff --git a/Backtrace/Model/Database/BacktraceDatabaseSettings.cs b/Backtrace/Model/Database/BacktraceDatabaseSettings.cs index d31b705..a7e8ac5 100644 --- a/Backtrace/Model/Database/BacktraceDatabaseSettings.cs +++ b/Backtrace/Model/Database/BacktraceDatabaseSettings.cs @@ -65,6 +65,6 @@ public long MaxDatabaseSize /// public uint RetryLimit { get; set; } = 3; - public RetryOrder RetryOrder { get; set; } = RetryOrder.Stack; + public RetryOrder RetryOrder { get; set; } = RetryOrder.Queue; } } diff --git a/Backtrace/Model/JsonData/BacktraceAttributes.cs b/Backtrace/Model/JsonData/BacktraceAttributes.cs index 57a2cdb..a8e45d2 100644 --- a/Backtrace/Model/JsonData/BacktraceAttributes.cs +++ b/Backtrace/Model/JsonData/BacktraceAttributes.cs @@ -39,7 +39,7 @@ public BacktraceAttributes(BacktraceReport report, Dictionary cl if (report != null) { ConvertAttributes(report, clientAttributes); - SetLibraryAttributes(report.CallingAssembly); + SetLibraryAttributes(report); SetDebuggerAttributes(report.CallingAssembly); SetExceptionAttributes(report); } @@ -52,8 +52,18 @@ public BacktraceAttributes(BacktraceReport report, Dictionary cl /// Set library attributes /// /// Calling assembly - private void SetLibraryAttributes(Assembly callingAssembly) + private void SetLibraryAttributes(BacktraceReport report) { + var callingAssembly = report.CallingAssembly; + if (!string.IsNullOrEmpty(report.Fingerprint)) + { + Attributes["_mod_fingerprint"] = report.Fingerprint; + } + + if (!string.IsNullOrEmpty(report.Factor)) + { + Attributes["_mod_factor"] = report.Factor; + } //A unique identifier of a machine Attributes["guid"] = GenerateMachineId().ToString(); //Base name of application generating the report @@ -129,6 +139,11 @@ private void ConvertAttributes(BacktraceReport report, Dictionary @@ -138,7 +153,9 @@ private void ConvertAttributes(BacktraceReport report, DictionaryMachine uuid private Guid GenerateMachineId() { - var networkInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up); + var networkInterface = + NetworkInterface.GetAllNetworkInterfaces() + .FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up); PhysicalAddress physicalAddr = null; string macAddress = null; diff --git a/Backtrace/Model/JsonData/ThreadData.cs b/Backtrace/Model/JsonData/ThreadData.cs index 1a0607b..13035d8 100644 --- a/Backtrace/Model/JsonData/ThreadData.cs +++ b/Backtrace/Model/JsonData/ThreadData.cs @@ -1,13 +1,13 @@ #if NET45 using Microsoft.Diagnostics.Runtime; #endif +using Backtrace.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Threading; using System.Linq; using System.Reflection; -using Backtrace.Extensions; +using System.Threading; namespace Backtrace.Model.JsonData { @@ -32,8 +32,16 @@ public class ThreadData internal ThreadData(Assembly callingAssembly, IEnumerable exceptionStack) { #if NET45 - //use available in .NET 4.5 api to find stack trace of all available managed threads - GetUsedThreads(callingAssembly); + try + { + //use available in .NET 4.5 api to find stack trace of all available managed threads + GetUsedThreads(callingAssembly); + } + catch (Exception e) + { + Debug.WriteLine($"[ThreadData] Received exception: {e.Message}"); + Trace.WriteLine("Cannot retrieve information about threads."); + } #else //get all available process threads ProcessThreads(); @@ -135,7 +143,7 @@ private void GetUsedThreads(Assembly callingAssembly) var frames = new List(); foreach (var frame in thread.StackTrace) { - if(frame.Method == null) + if (frame.Method == null) { continue; } diff --git a/Backtrace/Services/BacktraceDatabaseContext.cs b/Backtrace/Services/BacktraceDatabaseContext.cs index ffb1740..bca67ae 100644 --- a/Backtrace/Services/BacktraceDatabaseContext.cs +++ b/Backtrace/Services/BacktraceDatabaseContext.cs @@ -4,7 +4,6 @@ using Backtrace.Types; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2")] @@ -81,7 +80,10 @@ private void SetupBatch() /// New instance of DatabaseRecordy public virtual BacktraceDatabaseRecord Add(BacktraceData backtraceData) { - if (backtraceData == null) throw new NullReferenceException(nameof(backtraceData)); + if (backtraceData == null) + { + throw new NullReferenceException(nameof(backtraceData)); + } //create new record and save it on hard drive var record = new BacktraceDatabaseRecord(backtraceData, _path); record.Save(); @@ -95,7 +97,10 @@ public virtual BacktraceDatabaseRecord Add(BacktraceData backtraceData) /// Database record public BacktraceDatabaseRecord Add(BacktraceDatabaseRecord backtraceRecord) { - if (backtraceRecord == null) throw new NullReferenceException(nameof(BacktraceDatabaseRecord)); + if (backtraceRecord == null) + { + throw new NullReferenceException(nameof(BacktraceDatabaseRecord)); + } //lock record, because Add method returns record backtraceRecord.Locked = true; //increment total size of database @@ -151,6 +156,7 @@ public virtual void Delete(BacktraceDatabaseRecord record) TotalRecords--; //decrement total size of database TotalSize -= value.Size; + System.Diagnostics.Debug.WriteLine($"[Delete] :: Total Size = {TotalSize}"); return; } } @@ -167,6 +173,24 @@ public void IncrementBatchRetry() IncrementBatches(); } + /// + /// Remove last record in database. + /// + /// If algorithm can remove last record, method return true. Otherwise false + public bool RemoveLastRecord() + { + var record = LastOrDefault(); + if (record != null) + { + record.Delete(); + TotalRecords--; + TotalSize -= record.Size; + System.Diagnostics.Debug.WriteLine($"[RemoveLastRecord] :: Total Size = {TotalSize}"); + return true; + } + return false; + } + /// /// Increment each batch /// @@ -190,10 +214,15 @@ private void RemoveMaxRetries() for (int i = 0; i < total; i++) { var value = currentBatch[i]; - value.Delete(); - TotalRecords--; - //decrement total size of database - TotalSize -= value.Size; + if (value.Valid()) + { + value.Delete(); + TotalRecords--; + //decrement total size of database + System.Diagnostics.Debug.WriteLine($"[RemoveMaxRetries]::BeforeDelete Total size: {TotalSize}. Record Size: {value.Size} "); + TotalSize -= value.Size; + System.Diagnostics.Debug.WriteLine($"[RemoveMaxRetries]::AfterDelete Total size: {TotalSize} "); + } } } @@ -210,7 +239,10 @@ public IEnumerable Get() /// Get total number of records in database /// /// - public int Count() => TotalRecords; + public int Count() + { + return TotalRecords; + } /// /// Dispose @@ -269,7 +301,7 @@ public BacktraceDatabaseRecord FirstOrDefault() private BacktraceDatabaseRecord GetFirstRecord() { //get all batches (from the beginning) - for (int i = 0; i < _retryNumber - 1; i++) + for (int i = 0; i < _retryNumber; i++) { //if batch has any record that is not used //set lock to true diff --git a/Backtrace/Services/BacktraceDatabaseFileContext.cs b/Backtrace/Services/BacktraceDatabaseFileContext.cs index f1cfa68..5692a0e 100644 --- a/Backtrace/Services/BacktraceDatabaseFileContext.cs +++ b/Backtrace/Services/BacktraceDatabaseFileContext.cs @@ -141,7 +141,6 @@ public void Clear() var file = files[i]; file.Delete(); } - } } } diff --git a/Backtrace/Types/BacktraceResultStatus.cs b/Backtrace/Types/BacktraceResultStatus.cs index fef0044..2869a05 100644 --- a/Backtrace/Types/BacktraceResultStatus.cs +++ b/Backtrace/Types/BacktraceResultStatus.cs @@ -20,6 +20,10 @@ public enum BacktraceResultStatus /// /// Set when data were send to API /// - Ok + Ok, + /// + /// Status generated Backtrace client receive empty report (Aggregate Exception purpose) + /// + Empty } } diff --git a/CHANGELOG.md b/CHANGELOG.md index e396993..234e2d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Backtrace C# Release Notes +## Version 2.0.4 - 23.09.2018 +- `BacktraceClient` allows developer to unpack `AggregateException` and send only exceptions available in `InnerExceptions` property. +- `BacktraceReport` accepts two new properties: `Factor` and `Fingerprint`. These properties allows you to change server side algorithms. +- `BacktraceData` now include informations about `Exception` properties. You can check detailed `Exception` properties in Annotations. +- `BacktraceDatabase` doesn't throw exception when developer can't add new record. This situation exists when database was full or database hasn't enough disk space for exceptions. +- `BacktraceResult` can use new `Status`. In case when developer want to unpack `AggregateException` and `InnerExceptions` property is empty, `BacktraceClient` return `BacktraceResult` with status `Empty`, + ## Version 2.0.3 - 04.09.2018 - Thread data condition for Unity on .NET Framework 4.5+ diff --git a/Examples/Backtrace.Core/Program.cs b/Examples/Backtrace.Core/Program.cs index 7080d29..b376140 100644 --- a/Examples/Backtrace.Core/Program.cs +++ b/Examples/Backtrace.Core/Program.cs @@ -37,7 +37,6 @@ public Program() public async Task Start() { - await Task.Delay(int.MaxValue); await GenerateRandomStrings(); await TryClean(); //handle uncaught exception from unsafe code diff --git a/Examples/Backtrace.Framework45Example/Program.cs b/Examples/Backtrace.Framework45Example/Program.cs index de03125..1ce554b 100644 --- a/Examples/Backtrace.Framework45Example/Program.cs +++ b/Examples/Backtrace.Framework45Example/Program.cs @@ -9,19 +9,19 @@ namespace Backtrace.Framework45Example { - class Program + internal class Program { private Tree tree; /// /// Credentials /// - private BacktraceCredentials credentials = new BacktraceCredentials(ApplicationSettings.Host, ApplicationSettings.Token); + private readonly BacktraceCredentials credentials = new BacktraceCredentials(ApplicationSettings.Host, ApplicationSettings.Token); /// /// Database settings /// - private BacktraceDatabaseSettings databaseSettings = new BacktraceDatabaseSettings(ApplicationSettings.DatabasePath); + private readonly BacktraceDatabaseSettings databaseSettings = new BacktraceDatabaseSettings(ApplicationSettings.DatabasePath); /// /// New instance of BacktraceClient. Check SetupBacktraceLibrary method for intiailization example @@ -94,8 +94,8 @@ private async Task GenerateRandomStrings() continue; } } - //var response = await backtraceClient.SendAsync($"{DateTime.Now}: Tree generated"); - //Console.WriteLine($"Tree generated! Backtrace object id for last message: {response.Object}"); + var response = await backtraceClient.SendAsync($"{DateTime.Now}: Tree generated"); + Console.WriteLine($"Tree generated! Backtrace object id for last message: {response.Object}"); } private async Task TryClean() @@ -135,7 +135,7 @@ private async Task RemoveWords(string[] shuffledWords) } - unsafe static void DividePtrParam(int* p, int* j) + private static unsafe void DividePtrParam(int* p, int* j) { *p = *p / *j; } @@ -160,7 +160,10 @@ private void SetupBacktraceLibrary() //create Backtrace database var database = new BacktraceDatabase(databaseSettings); //setup new client - backtraceClient = new BacktraceClient(configuartion, database); + backtraceClient = new BacktraceClient(configuartion, database) + { + UnpackAggregateExcetpion = true + }; //handle all unhandled application exceptions backtraceClient.HandleApplicationException(); @@ -191,7 +194,8 @@ private void SetupBacktraceLibrary() return data; }; } - static void Main(string[] args) + + private static void Main(string[] args) { Program program = new Program(); program.Start().Wait(); diff --git a/README.md b/README.md index 113659b..4a626ec 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Backtrace -[![Backtrace@release](https://img.shields.io/badge/Backtrace%40master-2.0.3-blue.svg)](https://www.nuget.org/packages/Backtrace) +[![Backtrace@release](https://img.shields.io/badge/Backtrace%40master-2.0.4-blue.svg)](https://www.nuget.org/packages/Backtrace) [![Build status](https://ci.appveyor.com/api/projects/status/o0n9sp0ydgxb3ktu?svg=true)](https://ci.appveyor.com/project/konraddysput/backtrace-csharp) -[![Backtrace@pre-release](https://img.shields.io/badge/Backtrace%40dev-2.0.4-blue.svg)](https://www.nuget.org/packages/Backtrace) +[![Backtrace@pre-release](https://img.shields.io/badge/Backtrace%40dev-2.0.5-blue.svg)](https://www.nuget.org/packages/Backtrace) [![Build status](https://ci.appveyor.com/api/projects/status/o0n9sp0ydgxb3ktu/branch/dev?svg=true)](https://ci.appveyor.com/project/konraddysput/backtrace-csharp/branch/dev) @@ -190,7 +190,8 @@ For more information on `BacktraceClientConfiguration` parameters please see @@ -204,7 +205,7 @@ var dbSettings = new BacktraceDatabaseSettings("databaseDirectory"){ AutoSendMode = true, RetryBehavior = Backtrace.Types.RetryBehavior.ByInterval }; -var database = new BacktraceDatabase(dbSettings); +var database = new BacktraceDatabase(dbSettings); var credentials = new BacktraceCredentials("backtrace_endpoint_url", "token"); var configuration = new BacktraceClientConfiguration(credentials); var backtraceClient = new BacktraceClient(configuration, database); @@ -259,7 +260,27 @@ catch (Exception exception) Notes: - if you initialize `BacktraceClient` with `BacktraceDatabase` and your application is offline or you pass invalid credentials to `BacktraceClient`, reports will be stored in database directory path, - for .NET 4.5+, we recommend to use `SendAsync` method, -- if you don't want to use reflection to determine valid stack frame method name, you can pass `false` to `reflectionMethodName`. By default this value is equal to `true`. +- if you don't want to use reflection to determine valid stack frame method name, you can pass `false` to `reflectionMethodName`. By default this value is equal to `true`, +- `BacktraceReport` allows you to change default fingerprint generation algorithm. You can use `Fingerprint` property if you want to change fingerprint value. Keep in mind - fingerprint should be valid sha256 string., +- `BacktraceReport` allows you to change grouping strategy in Backtrace server. If you want to change how algorithm group your reports in Backtrace server please override `Factor` property. + +If you want to use `Fingerprint` and `Factor` property you have to override default property values. See example below to check how to use these properties: + +``` +try +{ + //throw exception here +} +catch (Exception exception) +{ + var report = new BacktraceReport(...){ + FingerPrint = "sha256 string", + Factor = exception.GetType().Name + }; + .... +} + +``` #### Asynchronous Send Support @@ -313,7 +334,7 @@ catch (Exception exception) //Add your own handler to client API backtraceClient.BeforeSend = - (Model.BacktraceData model) => + (Model.BacktraceData model) => { var data = model; //do something with data for example: @@ -374,7 +395,7 @@ You can extend `BacktraceBase` to create your own Backtrace client and error rep `BacktraceApi` can send synchronous and asynchronous reports to the Backtrace endpoint. To enable asynchronous report (default is synchronous) you have to set `AsynchronousRequest` property to `true`. ## BacktraceResult -**`BacktraceResult`** is a class that holds response and result from a `Send` or `SendAsync` call. The class contains a `Status` property that indicates whether the call was completed (`OK`), the call returned with an error (`ServerError`), or the call was aborted because client reporting limit was reached (`LimitReached`). Additionally, the class has a `Message` property that contains details about the status. Note that the `Send` call may produce an error report on an inner exception, in this case you can find an additional `BacktraceResult` object in the `InnerExceptionResult` property. +**`BacktraceResult`** is a class that holds response and result from a `Send` or `SendAsync` call. The class contains a `Status` property that indicates whether the call was completed (`OK`), the call returned with an error (`ServerError`), the call was aborted because client reporting limit was reached (`LimitReached`), or the call wasn't needed because developer use `UnpackAggregateException` property with empty `AggregateException` object (`Empty`). Additionally, the class has a `Message` property that contains details about the status. Note that the `Send` call may produce an error report on an inner exception, in this case you can find an additional `BacktraceResult` object in the `InnerExceptionResult` property. ## BacktraceDatabase **`BacktraceDatabase`** is a class that stores error report data in your local hard drive. If `DatabaseSettings` dones't contain a **valid** `DatabasePath` then `BacktraceDatabase` won't generate minidump files and store error report data. diff --git a/appveyor.yml b/appveyor.yml index 7f6b6db..b564d03 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,22 +1,24 @@ version: 1.0.{build} image: - - Visual Studio 2017 - Ubuntu + - Visual Studio 2017 branches: only: - /master - /dev +environment: + APPVEYOR_YML_DISABLE_PS_LINUX: true init: - # Good practise, because Windows line endings are different from Unix/Linux ones - - cmd: git config --global core.autocrlf true + # Good practise, because Windows line endings are different from Unix/Linux ones + - ps: git config --global core.autocrlf true before_build: # Display .NET version - - cmd: dotnet --version + - ps: dotnet --version build_script: #solve problem for .NET 3.5 by using two build commands# - ps: dotnet build .\Backtrace\Backtrace.csproj -f netstandard2.0 ; dotnet build .\Backtrace\Backtrace.csproj -f net45 + - sh: dotnet build ./Backtrace/Backtrace.csproj -f netstandard2.0 test_script: - ps: dotnet test .\Backtrace.Tests\ -deploy: off -on_finish: - - ps echo "Build finished successfully" \ No newline at end of file + - sh: dotnet test ./Backtrace.Tests -f netcoreapp2.0 +deploy: off \ No newline at end of file