diff --git a/AssemblyVersionInfo.cs b/AssemblyVersionInfo.cs
index 86489ac0..a28f2cb5 100644
--- a/AssemblyVersionInfo.cs
+++ b/AssemblyVersionInfo.cs
@@ -10,5 +10,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.10.2.0")]
-[assembly: AssemblyFileVersion("5.10.2.0")]
+[assembly: AssemblyVersion("5.11.0.0")]
+[assembly: AssemblyFileVersion("5.11.0.0")]
diff --git a/Mindscape.Raygun4Net.AspNetCore/Mindscape.Raygun4Net.AspNetCore.csproj b/Mindscape.Raygun4Net.AspNetCore/Mindscape.Raygun4Net.AspNetCore.csproj
index c701162e..944c138d 100644
--- a/Mindscape.Raygun4Net.AspNetCore/Mindscape.Raygun4Net.AspNetCore.csproj
+++ b/Mindscape.Raygun4Net.AspNetCore/Mindscape.Raygun4Net.AspNetCore.csproj
@@ -7,7 +7,7 @@
Raygun
.NetStandard library for targeting ASP.Net Core applications
Mindscape.Raygun4Net.AspNetCore
- 6.3.0
+ 6.3.1
false
https://github.com/MindscapeHQ/raygun4net/blob/master/LICENSE
https://github.com/MindscapeHQ/raygun4net
diff --git a/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyInfo.cs
index 7fbf3f30..e687445b 100644
--- a/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net.AspNetCore")]
@@ -14,8 +13,8 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyVersionInfo.cs b/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyVersionInfo.cs
index 10258aba..d3b951b4 100644
--- a/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyVersionInfo.cs
+++ b/Mindscape.Raygun4Net.AspNetCore/Properties/AssemblyVersionInfo.cs
@@ -1,6 +1,4 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
@@ -12,5 +10,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("6.3.0")]
-[assembly: AssemblyFileVersion("6.3.0")]
+[assembly: AssemblyVersion("6.3.1")]
+[assembly: AssemblyFileVersion("6.3.1")]
diff --git a/Mindscape.Raygun4Net.AspNetCore/readme.txt b/Mindscape.Raygun4Net.AspNetCore/README.md
similarity index 100%
rename from Mindscape.Raygun4Net.AspNetCore/readme.txt
rename to Mindscape.Raygun4Net.AspNetCore/README.md
diff --git a/Mindscape.Raygun4Net.AspNetCore/RaygunClient.cs b/Mindscape.Raygun4Net.AspNetCore/RaygunClient.cs
index b681f927..9d2f8fcf 100644
--- a/Mindscape.Raygun4Net.AspNetCore/RaygunClient.cs
+++ b/Mindscape.Raygun4Net.AspNetCore/RaygunClient.cs
@@ -13,11 +13,11 @@ namespace Mindscape.Raygun4Net.AspNetCore
public class RaygunClient : RaygunClientBase
{
protected readonly RaygunRequestMessageOptions _requestMessageOptions = new RaygunRequestMessageOptions();
-
+
private readonly ThreadLocal _currentHttpContext = new ThreadLocal(() => null);
private readonly ThreadLocal _currentRequestMessage = new ThreadLocal(() => null);
private readonly ThreadLocal _currentResponseMessage = new ThreadLocal(() => null);
-
+
public RaygunClient(string apiKey)
: this(new RaygunSettings {ApiKey = apiKey})
{
@@ -196,7 +196,7 @@ public bool UseKeyValuePairRawDataFilter
///
/// Add an implementation to be used when capturing the raw data
- /// of a HTTP request. This filter will be passed the request raw data and is expected to remove
+ /// of a HTTP request. This filter will be passed the request raw data and is expected to remove
/// or replace values whose keys are found in the list supplied to the Filter method.
///
/// Custom raw data filter implementation.
@@ -211,13 +211,13 @@ protected override bool CanSend(RaygunMessage message)
{
return true;
}
-
+
RaygunSettings settings = GetSettings();
if (settings.ExcludedStatusCodes == null)
{
return true;
}
-
+
return !settings.ExcludedStatusCodes.Contains(message.Details.Response.StatusCode);
}
diff --git a/Mindscape.Raygun4Net.Azure.WebJob.nuspec b/Mindscape.Raygun4Net.Azure.WebJob.nuspec
index 648effc2..d27cf9da 100644
--- a/Mindscape.Raygun4Net.Azure.WebJob.nuspec
+++ b/Mindscape.Raygun4Net.Azure.WebJob.nuspec
@@ -23,6 +23,6 @@
-
+
diff --git a/Mindscape.Raygun4Net.Azure.WebJob/readme.txt b/Mindscape.Raygun4Net.Azure.WebJob/README.md
similarity index 100%
rename from Mindscape.Raygun4Net.Azure.WebJob/readme.txt
rename to Mindscape.Raygun4Net.Azure.WebJob/README.md
diff --git a/Mindscape.Raygun4Net.ClientProfile.Tests/RaygunSettingsTests.cs b/Mindscape.Raygun4Net.ClientProfile.Tests/RaygunSettingsTests.cs
index 804fdda7..fc9005d8 100644
--- a/Mindscape.Raygun4Net.ClientProfile.Tests/RaygunSettingsTests.cs
+++ b/Mindscape.Raygun4Net.ClientProfile.Tests/RaygunSettingsTests.cs
@@ -18,7 +18,7 @@ public void Apikey_EmptyByDefault()
[Test]
public void ApiEndPoint_DefaultValue()
{
- Assert.AreEqual("https://api.raygun.io/entries", RaygunSettings.Settings.ApiEndpoint.AbsoluteUri);
+ Assert.AreEqual("https://api.raygun.com/entries", RaygunSettings.Settings.ApiEndpoint.AbsoluteUri);
}
[Test]
diff --git a/Mindscape.Raygun4Net.ClientProfile/Mindscape.Raygun4Net.ClientProfile.csproj b/Mindscape.Raygun4Net.ClientProfile/Mindscape.Raygun4Net.ClientProfile.csproj
index cfd1e858..b8709be9 100644
--- a/Mindscape.Raygun4Net.ClientProfile/Mindscape.Raygun4Net.ClientProfile.csproj
+++ b/Mindscape.Raygun4Net.ClientProfile/Mindscape.Raygun4Net.ClientProfile.csproj
@@ -76,6 +76,15 @@
IRaygunMessageBuilder.cs
+
+ Logging\IRaygunLogger.cs
+
+
+ Logging\RaygunLogger.cs
+
+
+ Logging\RaygunLogLevel.cs
+
Messages\RaygunClientMessage.cs
@@ -106,6 +115,21 @@
SimpleJson.cs
+
+ Storage\IRaygunFile.cs
+
+
+ Storage\IRaygunOfflineStorage.cs
+
+
+ Storage\RaygunFile.cs
+
+
+ Storage\IsolatedRaygunOfflineStorage.cs
+
+
+ Utils\Singleton.cs
+
diff --git a/Mindscape.Raygun4Net.ClientProfile/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net.ClientProfile/Properties/AssemblyInfo.cs
index e2a7b698..da52caaf 100644
--- a/Mindscape.Raygun4Net.ClientProfile/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net.ClientProfile/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net.ClientProfile")]
@@ -10,12 +9,12 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Raygun")]
[assembly: AssemblyProduct("Raygun4Net")]
-[assembly: AssemblyCopyright("Copyright © Raygun 2015-2017")]
+[assembly: AssemblyCopyright("Copyright © Raygun 2015-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net.ClientProfile/RaygunClient.cs b/Mindscape.Raygun4Net.ClientProfile/RaygunClient.cs
index 21358125..c50425f4 100644
--- a/Mindscape.Raygun4Net.ClientProfile/RaygunClient.cs
+++ b/Mindscape.Raygun4Net.ClientProfile/RaygunClient.cs
@@ -3,34 +3,33 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using Mindscape.Raygun4Net.Messages;
-
using System.Threading;
using System.Reflection;
-using Mindscape.Raygun4Net.Builders;
-using System.IO;
-using System.IO.IsolatedStorage;
-using System.Text;
+using Mindscape.Raygun4Net.Logging;
+using Mindscape.Raygun4Net.Messages;
+using Mindscape.Raygun4Net.Storage;
namespace Mindscape.Raygun4Net
{
public class RaygunClient : RaygunClientBase
{
+ private static object _sendLock = new object();
+
private readonly string _apiKey;
private readonly List _wrapperExceptions = new List();
+ private IRaygunOfflineStorage _offlineStorage = new IsolatedRaygunOfflineStorage();
+
///
- /// Initializes a new instance of the class.
+ /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
+ /// and requires credentials.
///
- /// The API key.
- public RaygunClient(string apiKey)
- {
- _apiKey = apiKey;
-
- _wrapperExceptions.Add(typeof(TargetInvocationException));
+ public ICredentials ProxyCredentials { get; set; }
- ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
- }
+ ///
+ /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
+ ///
+ public IWebProxy WebProxy { get; set; }
///
/// Initializes a new instance of the class.
@@ -41,26 +40,18 @@ public RaygunClient()
{
}
- protected bool ValidateApiKey()
- {
- if (string.IsNullOrEmpty(_apiKey))
- {
- System.Diagnostics.Debug.WriteLine("ApiKey has not been provided, exception will not be logged");
- return false;
- }
- return true;
- }
-
///
- /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
- /// and requires credentials.
+ /// Initializes a new instance of the class.
///
- public ICredentials ProxyCredentials { get; set; }
+ /// The API key.
+ public RaygunClient(string apiKey)
+ {
+ _apiKey = apiKey;
- ///
- /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
- ///
- public IWebProxy WebProxy { get; set; }
+ _wrapperExceptions.Add(typeof(TargetInvocationException));
+
+ ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
+ }
///
/// Adds a list of outer exceptions that will be stripped, leaving only the valuable inner exception.
@@ -93,9 +84,11 @@ public void RemoveWrapperExceptions(params Type[] wrapperExceptions)
_wrapperExceptions.Remove(wrapper);
}
}
-
+
+ #region Message Send Methods
+
///
- /// Transmits an exception to Raygun.io synchronously, using the version number of the originating assembly.
+ /// Transmits an exception to Raygun synchronously, using the version number of the originating assembly.
///
/// The exception to deliver.
public override void Send(Exception exception)
@@ -104,7 +97,7 @@ public override void Send(Exception exception)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification. This uses the version number of the originating assembly.
///
/// The exception to deliver.
@@ -115,7 +108,7 @@ public void Send(Exception exception, IList tags)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -128,7 +121,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -146,7 +139,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The exception to deliver.
public void SendInBackground(Exception exception)
@@ -155,7 +148,7 @@ public void SendInBackground(Exception exception)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -165,7 +158,7 @@ public void SendInBackground(Exception exception, IList tags)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -176,7 +169,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -208,7 +201,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The RaygunMessage to send. This needs its OccurredOn property
/// set to a valid DateTime and as much of the Details property as is available.
@@ -217,6 +210,89 @@ public void SendInBackground(RaygunMessage raygunMessage)
ThreadPool.QueueUserWorkItem(c => Send(raygunMessage));
}
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public override void Send(RaygunMessage raygunMessage)
+ {
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send error report due to invalid API key.");
+ return;
+ }
+
+ bool canSend = OnSendingMessage(raygunMessage);
+
+ if (!canSend)
+ {
+ return;
+ }
+
+ string message = null;
+
+ try
+ {
+ message = SimpleJson.SerializeObject(raygunMessage);
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to serialize report due to: {ex.Message}");
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ bool successfullySentReport = true;
+
+ try
+ {
+ Send(message);
+ }
+ catch (Exception ex)
+ {
+ successfullySentReport = false;
+
+ RaygunLogger.Instance.Error($"Failed to send report to Raygun due to: {ex.Message}");
+
+ SaveMessage(message);
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (successfullySentReport)
+ {
+ SendStoredMessages();
+ }
+ }
+
+ private void Send(string message)
+ {
+ RaygunLogger.Instance.Verbose("Sending Payload --------------");
+ RaygunLogger.Instance.Verbose(message);
+ RaygunLogger.Instance.Verbose("------------------------------");
+
+ using (var client = CreateWebClient())
+ {
+ client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
+ }
+ }
+
+ #endregion // Message Send Methods
+
+ #region Message Building Methods
+
protected RaygunMessage BuildMessage(Exception exception, IList tags, IDictionary userCustomData)
{
return BuildMessage(exception, tags, userCustomData, null, null);
@@ -261,62 +337,100 @@ private Exception StripWrapperExceptions(Exception exception)
return exception;
}
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public override void Send(RaygunMessage raygunMessage)
+ #endregion // Message Building Methods
+
+ #region Message Offline Storage
+
+ private void SaveMessage(string message)
{
- bool canSend = OnSendingMessage(raygunMessage);
- if (canSend)
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping saving report.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to save report due to invalid API key.");
+ return;
+ }
+
+ // Avoid writing and reading from disk at the same time with `SendStoredMessages`.
+ lock (_sendLock)
{
- string message = null;
try
{
- message = SimpleJson.SerializeObject(raygunMessage);
+ if (!_offlineStorage.Store(message, _apiKey))
+ {
+ RaygunLogger.Instance.Warning("Failed to save report to offline storage.");
+ }
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine(string.Format("Error serializing exception {0}", ex.Message));
-
- if (RaygunSettings.Settings.ThrowOnError)
- {
- throw;
- }
+ RaygunLogger.Instance.Error($"Failed to save report to offline storage due to: {ex.Message}");
}
+ }
+ }
+
+ private void SendStoredMessages()
+ {
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping sending stored reports.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send offline reports due to invalid API key.");
+ return;
+ }
- if (message != null)
+ lock (_sendLock)
+ {
+ try
{
- try
- {
- Send(message);
- }
- catch (Exception ex)
+ var files = _offlineStorage.FetchAll(_apiKey);
+
+ foreach (var file in files)
{
- SaveMessage(message);
- System.Diagnostics.Debug.WriteLine(string.Format("Error Logging Exception to Raygun.io {0}", ex.Message));
+ try
+ {
+ // Send the stored report.
+ Send(file.Contents);
- if (RaygunSettings.Settings.ThrowOnError)
+ // Remove the stored report from local storage.
+ if (_offlineStorage.Remove(file.Name, _apiKey))
+ {
+ RaygunLogger.Instance.Info("Successfully removed report from offline storage.");
+ }
+ else
+ {
+ RaygunLogger.Instance.Warning("Failed to remove report from offline storage.");
+ }
+ }
+ catch (Exception ex)
{
- throw;
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+
+ // If just one message fails to send, then don't delete the message,
+ // and don't attempt sending anymore until later.
+ return;
}
}
-
- SendStoredMessages();
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
}
}
}
- private void Send(string message)
+ #endregion // Message Offline Storage
+
+ protected bool ValidateApiKey()
{
- if (ValidateApiKey())
- {
- using (var client = CreateWebClient())
- {
- client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
- }
- }
+ return !string.IsNullOrEmpty(_apiKey);
}
protected WebClient CreateWebClient()
@@ -352,135 +466,5 @@ protected WebClient CreateWebClient()
}
return client;
}
-
- private void SaveMessage(string message)
- {
- try
- {
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
- {
- string directoryName = "RaygunOfflineStorage";
- string[] directories = isolatedStorage.GetDirectoryNames("*");
- if (!FileExists(directories, directoryName))
- {
- isolatedStorage.CreateDirectory(directoryName);
- }
-
- int number = 1;
- string[] files = isolatedStorage.GetFileNames(directoryName + "\\*.txt");
- while (true)
- {
- bool exists = FileExists(files, "RaygunErrorMessage" + number + ".txt");
- if (!exists)
- {
- string nextFileName = "RaygunErrorMessage" + (number + 1) + ".txt";
- exists = FileExists(files, nextFileName);
- if (exists)
- {
- isolatedStorage.DeleteFile(directoryName + "\\" + nextFileName);
- }
- break;
- }
- number++;
- }
-
- if (number == 11)
- {
- string firstFileName = "RaygunErrorMessage1.txt";
- if (FileExists(files, firstFileName))
- {
- isolatedStorage.DeleteFile(directoryName + "\\" + firstFileName);
- }
- }
- using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(directoryName + "\\RaygunErrorMessage" + number + ".txt", FileMode.OpenOrCreate, FileAccess.Write, isolatedStorage))
- {
- using (StreamWriter writer = new StreamWriter(isoStream, Encoding.Unicode))
- {
- writer.Write(message);
- writer.Flush();
- writer.Close();
- }
- }
- System.Diagnostics.Trace.WriteLine("Saved message: " + "RaygunErrorMessage" + number + ".txt");
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Trace.WriteLine(string.Format("Error saving message to isolated storage {0}", ex.Message));
- }
- }
-
- private bool FileExists(string[] files, string fileName)
- {
- foreach (string str in files)
- {
- if (fileName.Equals(str))
- {
- return true;
- }
- }
- return false;
- }
-
- private static object _sendLock = new object();
-
- private void SendStoredMessages()
- {
- lock (_sendLock)
- {
- try
- {
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
- {
- string directoryName = "RaygunOfflineStorage";
- string[] directories = isolatedStorage.GetDirectoryNames("*");
- if (FileExists(directories, directoryName))
- {
- string[] fileNames = isolatedStorage.GetFileNames(directoryName + "\\*.txt");
- foreach (string name in fileNames)
- {
- IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(directoryName + "\\" + name, FileMode.Open, isolatedStorage);
- using (StreamReader reader = new StreamReader(isoFileStream))
- {
- string text = reader.ReadToEnd();
- try
- {
- Send(text);
- }
- catch
- {
- // If just one message fails to send, then don't delete the message, and don't attempt sending anymore until later.
- return;
- }
- System.Diagnostics.Debug.WriteLine("Sent " + name);
- }
- isolatedStorage.DeleteFile(directoryName + "\\" + name);
- }
- if (isolatedStorage.GetFileNames(directoryName + "\\*.txt").Length == 0)
- {
- System.Diagnostics.Debug.WriteLine("Successfully sent all pending messages");
- isolatedStorage.DeleteDirectory(directoryName);
- }
- }
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine(string.Format("Error sending stored messages to Raygun.io {0}", ex.Message));
- }
- }
- }
-
- private IsolatedStorageFile GetIsolatedStorageScope()
- {
- if (AppDomain.CurrentDomain != null && AppDomain.CurrentDomain.ActivationContext != null)
- {
- return IsolatedStorageFile.GetUserStoreForApplication();
- }
- else
- {
- return IsolatedStorageFile.GetUserStoreForAssembly();
- }
- }
}
}
diff --git a/Mindscape.Raygun4Net.ClientProfile/RaygunSettings.cs b/Mindscape.Raygun4Net.ClientProfile/RaygunSettings.cs
index dbbd72e5..91c8ab3e 100644
--- a/Mindscape.Raygun4Net.ClientProfile/RaygunSettings.cs
+++ b/Mindscape.Raygun4Net.ClientProfile/RaygunSettings.cs
@@ -1,6 +1,7 @@
using System;
using System.Configuration;
using System.Linq;
+using Mindscape.Raygun4Net.Logging;
namespace Mindscape.Raygun4Net
{
@@ -8,7 +9,9 @@ public class RaygunSettings : ConfigurationSection
{
private static readonly RaygunSettings settings = ConfigurationManager.GetSection("RaygunSettings") as RaygunSettings ?? new RaygunSettings();
- private const string DefaultApiEndPoint = "https://api.raygun.io/entries";
+ private const string DefaultApiEndPoint = "https://api.raygun.com/entries";
+
+ public const int MaxCrashReportsStoredOfflineHardLimit = 64;
public static RaygunSettings Settings
{
@@ -50,7 +53,43 @@ public string ApplicationVersion
get { return (string)this["applicationVersion"]; }
set { this["applicationVersion"] = value; }
}
-
+
+ ///
+ /// Gets or sets the max crash reports stored on the device.
+ /// There is a hard upper limit of 64 reports.
+ ///
+ /// The max crash reports stored on device.
+ [ConfigurationProperty("maxCrashReportsStoredOffline", IsRequired = false, DefaultValue = MaxCrashReportsStoredOfflineHardLimit)]
+ public int MaxCrashReportsStoredOffline
+ {
+ get { return (int)this["maxCrashReportsStoredOffline"]; }
+ set { this["maxCrashReportsStoredOffline"] = value; }
+ }
+
+ ///
+ /// Allows for crash reports to be stored to local storage when there is no available network connection.
+ ///
+ /// true if allowing crash reports to be stored offline; otherwise, false.
+ [ConfigurationProperty("crashReportingOfflineStorageEnabled", IsRequired = false, DefaultValue = true)]
+ public bool CrashReportingOfflineStorageEnabled
+ {
+ get { return (bool)this["crashReportingOfflineStorageEnabled"]; }
+ set { this["crashReportingOfflineStorageEnabled"] = value; }
+ }
+
+ ///
+ /// Gets or sets the log level controlling the amount of information printed to system consoles.
+ /// Setting the level to will print the raw Crash Reporting being
+ /// posted to the API endpoints.
+ ///
+ /// The log level.
+ [ConfigurationProperty("logLevel", IsRequired = false, DefaultValue = RaygunLogLevel.Warning)]
+ public RaygunLogLevel LogLevel
+ {
+ get { return (RaygunLogLevel)this["logLevel"]; }
+ set { this["logLevel"] = value; }
+ }
+
///
/// Return false.
///
diff --git a/Mindscape.Raygun4Net.Core.Signed.nuspec b/Mindscape.Raygun4Net.Core.Signed.nuspec
index 86bf2f40..2001d7b0 100644
--- a/Mindscape.Raygun4Net.Core.Signed.nuspec
+++ b/Mindscape.Raygun4Net.Core.Signed.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.Core.Signed
- 5.10.2
+ 5.11.0
Raygun
diff --git a/Mindscape.Raygun4Net.Core.nuspec b/Mindscape.Raygun4Net.Core.nuspec
index bb07f560..d74c42a4 100644
--- a/Mindscape.Raygun4Net.Core.nuspec
+++ b/Mindscape.Raygun4Net.Core.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.Core
- 5.10.2
+ 5.11.0
Raygun
diff --git a/Mindscape.Raygun4Net.Core/Mindscape.Raygun4Net.Core.csproj b/Mindscape.Raygun4Net.Core/Mindscape.Raygun4Net.Core.csproj
index 5590a09b..7467bd7b 100644
--- a/Mindscape.Raygun4Net.Core/Mindscape.Raygun4Net.Core.csproj
+++ b/Mindscape.Raygun4Net.Core/Mindscape.Raygun4Net.Core.csproj
@@ -81,6 +81,15 @@
IRaygunMessageBuilder.cs
+
+ Logging\IRaygunLogger.cs
+
+
+ Logging\RaygunLogger.cs
+
+
+ Logging\RaygunLogLevel.cs
+
Messages\RaygunClientMessage.cs
@@ -123,6 +132,21 @@
SimpleJson.cs
+
+ Storage\IRaygunFile.cs
+
+
+ Storage\IRaygunOfflineStorage.cs
+
+
+ Storage\RaygunFile.cs
+
+
+ Storage\IsolatedRaygunOfflineStorage.cs
+
+
+ Utils\Singleton.cs
+
diff --git a/Mindscape.Raygun4Net.Core/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net.Core/Properties/AssemblyInfo.cs
index c1fb18e9..a65b8dc5 100644
--- a/Mindscape.Raygun4Net.Core/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net.Core/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net.Core")]
@@ -14,8 +13,8 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net.Mvc.Signed.nuspec b/Mindscape.Raygun4Net.Mvc.Signed.nuspec
index e62d9243..117bca1e 100644
--- a/Mindscape.Raygun4Net.Mvc.Signed.nuspec
+++ b/Mindscape.Raygun4Net.Mvc.Signed.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.Mvc.Signed
- 5.10.3
+ 5.11.0
Raygun
@@ -12,7 +12,7 @@
https://github.com/MindscapeHQ/raygun4net
https://raw.github.com/MindscapeHQ/raygun4net/master/LICENSE
-
+
@@ -25,6 +25,6 @@
-
+
diff --git a/Mindscape.Raygun4Net.Mvc.nuspec b/Mindscape.Raygun4Net.Mvc.nuspec
index 68d8074f..79334daa 100644
--- a/Mindscape.Raygun4Net.Mvc.nuspec
+++ b/Mindscape.Raygun4Net.Mvc.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.Mvc
- 5.10.3
+ 5.11.0
Raygun
@@ -12,7 +12,7 @@
https://github.com/MindscapeHQ/raygun4net
https://raw.github.com/MindscapeHQ/raygun4net/master/LICENSE
-
+
@@ -25,6 +25,6 @@
-
+
diff --git a/Mindscape.Raygun4Net.Mvc/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net.Mvc/Properties/AssemblyInfo.cs
index ea67a8f5..27f0b378 100644
--- a/Mindscape.Raygun4Net.Mvc/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net.Mvc/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net.Mvc")]
@@ -14,8 +13,8 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net.Mvc/Properties/AssemblyVersionInfo.cs b/Mindscape.Raygun4Net.Mvc/Properties/AssemblyVersionInfo.cs
index 6af10067..a28f2cb5 100644
--- a/Mindscape.Raygun4Net.Mvc/Properties/AssemblyVersionInfo.cs
+++ b/Mindscape.Raygun4Net.Mvc/Properties/AssemblyVersionInfo.cs
@@ -10,5 +10,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.10.3.0")]
-[assembly: AssemblyFileVersion("5.10.3.0")]
+[assembly: AssemblyVersion("5.11.0.0")]
+[assembly: AssemblyFileVersion("5.11.0.0")]
diff --git a/Mindscape.Raygun4Net.Mvc/readme.txt b/Mindscape.Raygun4Net.Mvc/README.md
similarity index 100%
rename from Mindscape.Raygun4Net.Mvc/readme.txt
rename to Mindscape.Raygun4Net.Mvc/README.md
diff --git a/Mindscape.Raygun4Net.NetCore.Common/Messages/RaygunClientMessage.cs b/Mindscape.Raygun4Net.NetCore.Common/Messages/RaygunClientMessage.cs
index e220efb7..f461c3ea 100644
--- a/Mindscape.Raygun4Net.NetCore.Common/Messages/RaygunClientMessage.cs
+++ b/Mindscape.Raygun4Net.NetCore.Common/Messages/RaygunClientMessage.cs
@@ -5,10 +5,10 @@ public class RaygunClientMessage
public RaygunClientMessage()
{
Name = "Raygun4Net.NetCore";
- Version = "6.2.0";
+ Version = "6.3.1";
ClientUrl = @"https://github.com/MindscapeHQ/raygun4net";
}
-
+
public string Name { get; set; }
public string Version { get; set; }
diff --git a/Mindscape.Raygun4Net.NetCore.Common/Mindscape.Raygun4Net.NetCore.Common.csproj b/Mindscape.Raygun4Net.NetCore.Common/Mindscape.Raygun4Net.NetCore.Common.csproj
index 5b308a4d..c454ab73 100644
--- a/Mindscape.Raygun4Net.NetCore.Common/Mindscape.Raygun4Net.NetCore.Common.csproj
+++ b/Mindscape.Raygun4Net.NetCore.Common/Mindscape.Raygun4Net.NetCore.Common.csproj
@@ -8,7 +8,7 @@
Raygun
.NetStandard library .NetCore applications
Mindscape.Raygun4Net.NetCore.Common
- 6.3.0
+ 6.3.1
false
https://github.com/MindscapeHQ/raygun4net/blob/master/LICENSE
https://github.com/MindscapeHQ/raygun4net
diff --git a/Mindscape.Raygun4Net.NetCore.Common/RaygunClientBase.cs b/Mindscape.Raygun4Net.NetCore.Common/RaygunClientBase.cs
index a7c8c706..51653157 100644
--- a/Mindscape.Raygun4Net.NetCore.Common/RaygunClientBase.cs
+++ b/Mindscape.Raygun4Net.NetCore.Common/RaygunClientBase.cs
@@ -12,24 +12,26 @@ namespace Mindscape.Raygun4Net
{
public abstract class RaygunClientBase
{
+ private static readonly HttpClient Client = new HttpClient();
+
private readonly string _apiKey;
private readonly List _wrapperExceptions = new List();
- protected readonly RaygunSettingsBase _settings;
+ private bool _handlingRecursiveErrorSending;
+ private bool _handlingRecursiveGrouping;
+
+ protected readonly RaygunSettingsBase _settings;
protected internal const string SentKey = "AlreadySentByRaygun";
-
- public RaygunClientBase(RaygunSettingsBase settings)
- {
- _settings = settings;
- _apiKey = settings.ApiKey;
- _wrapperExceptions.Add(typeof(TargetInvocationException));
-
- if (!string.IsNullOrEmpty(settings.ApplicationVersion))
- {
- ApplicationVersion = settings.ApplicationVersion;
- }
- }
+ ///
+ /// Raised just before a message is sent. This can be used to make final adjustments to the , or to cancel the send.
+ ///
+ public event EventHandler SendingMessage;
+
+ ///
+ /// Raised before a message is sent. This can be used to add a custom grouping key to a RaygunMessage before sending it to the Raygun service.
+ ///
+ public event EventHandler CustomGroupingKey;
///
/// Gets or sets the user identity string.
@@ -46,6 +48,19 @@ public RaygunClientBase(RaygunSettingsBase settings)
///
public string ApplicationVersion { get; set; }
+ public RaygunClientBase(RaygunSettingsBase settings)
+ {
+ _settings = settings;
+ _apiKey = settings.ApiKey;
+
+ _wrapperExceptions.Add(typeof(TargetInvocationException));
+
+ if (!string.IsNullOrEmpty(settings.ApplicationVersion))
+ {
+ ApplicationVersion = settings.ApplicationVersion;
+ }
+ }
+
///
/// Adds a list of outer exceptions that will be stripped, leaving only the valuable inner exception.
/// This can be used when a wrapper exception, e.g. TargetInvocationException or HttpUnhandledException,
@@ -90,7 +105,7 @@ protected void FlagAsSent(Exception exception)
try
{
Type[] genericTypes = exception.Data.GetType().GetTypeInfo().GenericTypeArguments;
-
+
if (genericTypes.Length == 0 || genericTypes[0].GetTypeInfo().IsAssignableFrom(typeof(string)))
{
exception.Data[SentKey] = true;
@@ -103,13 +118,6 @@ protected void FlagAsSent(Exception exception)
}
}
- ///
- /// Raised just before a message is sent. This can be used to make final adjustments to the , or to cancel the send.
- ///
- public event EventHandler SendingMessage;
-
- private bool _handlingRecursiveErrorSending;
-
// Returns true if the message can be sent, false if the sending is canceled.
protected bool OnSendingMessage(RaygunMessage raygunMessage)
{
@@ -117,12 +125,12 @@ protected bool OnSendingMessage(RaygunMessage raygunMessage)
if (!_handlingRecursiveErrorSending)
{
- EventHandler handler = SendingMessage;
-
+ var handler = SendingMessage;
+
if (handler != null)
{
- RaygunSendingMessageEventArgs args = new RaygunSendingMessageEventArgs(raygunMessage);
-
+ var args = new RaygunSendingMessageEventArgs(raygunMessage);
+
try
{
handler(this, args);
@@ -132,12 +140,12 @@ protected bool OnSendingMessage(RaygunMessage raygunMessage)
// Catch and send exceptions that occur in the SendingMessage event handler.
// Set the _handlingRecursiveErrorSending flag to prevent infinite errors.
_handlingRecursiveErrorSending = true;
-
+
Send(e);
-
+
_handlingRecursiveErrorSending = false;
}
-
+
result = !args.Cancel;
}
}
@@ -145,25 +153,18 @@ protected bool OnSendingMessage(RaygunMessage raygunMessage)
return result;
}
- ///
- /// Raised before a message is sent. This can be used to add a custom grouping key to a RaygunMessage before sending it to the Raygun service.
- ///
- public event EventHandler CustomGroupingKey;
-
- private bool _handlingRecursiveGrouping;
-
protected async Task OnCustomGroupingKey(Exception exception, RaygunMessage message)
{
string result = null;
-
+
if (!_handlingRecursiveGrouping)
{
var handler = CustomGroupingKey;
-
+
if (handler != null)
{
var args = new RaygunCustomGroupingKeyEventArgs(exception, message);
-
+
try
{
handler(this, args);
@@ -171,19 +172,19 @@ protected async Task OnCustomGroupingKey(Exception exception, RaygunMess
catch (Exception e)
{
_handlingRecursiveGrouping = true;
-
+
await SendAsync(e, null, null);
-
+
_handlingRecursiveGrouping = false;
}
-
+
result = args.CustomGroupingKey;
}
}
-
+
return result;
}
-
+
protected bool ValidateApiKey()
{
if (string.IsNullOrEmpty(_apiKey))
@@ -191,7 +192,7 @@ protected bool ValidateApiKey()
Debug.WriteLine("ApiKey has not been provided, exception will not be logged");
return false;
}
-
+
return true;
}
@@ -231,7 +232,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
{
SendAsync(exception, tags, userCustomData).Wait();
}
-
+
protected virtual async Task SendAsync(Exception exception, IList tags, IDictionary userCustomData)
{
if (CanSend(exception))
@@ -286,9 +287,9 @@ public virtual async Task SendInBackground(Exception exception, IList ta
{
await StripAndSend(exception, tags, userCustomData, userInfo);
});
-
+
FlagAsSent(exception);
-
+
await task;
}
}
@@ -307,7 +308,7 @@ internal void FlagExceptionAsSent(Exception exception)
{
FlagAsSent(exception);
}
-
+
protected virtual async Task BuildMessage(Exception exception, IList tags, IDictionary userCustomData, RaygunIdentifierMessage userInfo)
{
var message = RaygunMessageBuilder.New(_settings)
@@ -322,7 +323,7 @@ protected virtual async Task BuildMessage(Exception exception, IL
.Build();
var customGroupingKey = await OnCustomGroupingKey(exception, message);
-
+
if (string.IsNullOrEmpty(customGroupingKey) == false)
{
message.Details.GroupingKey = customGroupingKey;
@@ -344,7 +345,7 @@ protected IEnumerable StripWrapperExceptions(Exception exception)
if (exception != null && _wrapperExceptions.Any(wrapperException => exception.GetType() == wrapperException && exception.InnerException != null))
{
AggregateException aggregate = exception as AggregateException;
-
+
if (aggregate != null)
{
foreach (Exception e in aggregate.InnerExceptions)
@@ -376,47 +377,47 @@ protected IEnumerable StripWrapperExceptions(Exception exception)
/// set to a valid DateTime and as much of the Details property as is available.
public async Task Send(RaygunMessage raygunMessage)
{
- if (ValidateApiKey())
+ if (!ValidateApiKey())
{
- bool canSend = OnSendingMessage(raygunMessage) && CanSend(raygunMessage);
-
- if (canSend)
- {
- using (var client = new HttpClient())
- {
- var requestMessage = new HttpRequestMessage(HttpMethod.Post, _settings.ApiEndpoint);
+ return;
+ }
- requestMessage.Headers.Add("X-ApiKey", _apiKey);
+ bool canSend = OnSendingMessage(raygunMessage) && CanSend(raygunMessage);
- try
- {
- var message = SimpleJson.SerializeObject(raygunMessage);
- requestMessage.Content = new StringContent(message, Encoding.UTF8, "application/json");
-
- var result = await client.SendAsync(requestMessage);
-
- if (!result.IsSuccessStatusCode)
- {
- Debug.WriteLine($"Error Logging Exception to Raygun {result.ReasonPhrase}");
-
- if (_settings.ThrowOnError)
- {
- throw new Exception("Could not log to Raygun");
- }
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error Logging Exception to Raygun {ex.Message}");
+ if (!canSend)
+ {
+ return;
+ }
- if (_settings.ThrowOnError)
- {
- throw;
- }
- }
+ var requestMessage = new HttpRequestMessage(HttpMethod.Post, _settings.ApiEndpoint);
+ requestMessage.Headers.Add("X-ApiKey", _apiKey);
+
+ try
+ {
+ var message = SimpleJson.SerializeObject(raygunMessage);
+ requestMessage.Content = new StringContent(message, Encoding.UTF8, "application/json");
+
+ var result = await Client.SendAsync(requestMessage);
+
+ if (!result.IsSuccessStatusCode)
+ {
+ Debug.WriteLine($"Error Logging Exception to Raygun {result.ReasonPhrase}");
+
+ if (_settings.ThrowOnError)
+ {
+ throw new Exception("Could not log to Raygun");
}
}
}
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Error Logging Exception to Raygun {ex.Message}");
+
+ if (_settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
}
}
}
diff --git a/Mindscape.Raygun4Net.NetCore/Mindscape.Raygun4Net.NetCore.csproj b/Mindscape.Raygun4Net.NetCore/Mindscape.Raygun4Net.NetCore.csproj
index 861d53f7..da81e068 100644
--- a/Mindscape.Raygun4Net.NetCore/Mindscape.Raygun4Net.NetCore.csproj
+++ b/Mindscape.Raygun4Net.NetCore/Mindscape.Raygun4Net.NetCore.csproj
@@ -4,7 +4,6 @@
Mindscape.Raygun4Net.NetCore
false
netstandard1.6;netstandard2.0
- .NETStandard
false
Debug;Release;Sign
AnyCPU
@@ -14,7 +13,7 @@
Raygun
.NetStandard library for targeting .Net Core applications
Mindscape.Raygun4Net.NetCore
- 6.3.0
+ 6.3.1
false
https://github.com/MindscapeHQ/raygun4net/blob/master/LICENSE
https://github.com/MindscapeHQ/raygun4net
diff --git a/Mindscape.Raygun4Net.NetCore/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net.NetCore/Properties/AssemblyInfo.cs
index 8d493103..6cf760b5 100644
--- a/Mindscape.Raygun4Net.NetCore/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net.NetCore/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net.NetCore")]
@@ -14,8 +13,8 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net.NetCore/Properties/AssemblyVersionInfo.cs b/Mindscape.Raygun4Net.NetCore/Properties/AssemblyVersionInfo.cs
index 10258aba..d3b951b4 100644
--- a/Mindscape.Raygun4Net.NetCore/Properties/AssemblyVersionInfo.cs
+++ b/Mindscape.Raygun4Net.NetCore/Properties/AssemblyVersionInfo.cs
@@ -1,6 +1,4 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
@@ -12,5 +10,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("6.3.0")]
-[assembly: AssemblyFileVersion("6.3.0")]
+[assembly: AssemblyVersion("6.3.1")]
+[assembly: AssemblyFileVersion("6.3.1")]
diff --git a/Mindscape.Raygun4Net.NetCore/readme.txt b/Mindscape.Raygun4Net.NetCore/README.md
similarity index 100%
rename from Mindscape.Raygun4Net.NetCore/readme.txt
rename to Mindscape.Raygun4Net.NetCore/README.md
diff --git a/Mindscape.Raygun4Net.Signed.nuspec b/Mindscape.Raygun4Net.Signed.nuspec
index ca25415f..8bf885d0 100644
--- a/Mindscape.Raygun4Net.Signed.nuspec
+++ b/Mindscape.Raygun4Net.Signed.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.Signed
- 5.10.2
+ 5.11.0
Raygun
@@ -42,6 +42,6 @@
-
+
diff --git a/Mindscape.Raygun4Net.Tests/Mindscape.Raygun4Net.Tests.csproj b/Mindscape.Raygun4Net.Tests/Mindscape.Raygun4Net.Tests.csproj
index 70272621..ed92f3d8 100644
--- a/Mindscape.Raygun4Net.Tests/Mindscape.Raygun4Net.Tests.csproj
+++ b/Mindscape.Raygun4Net.Tests/Mindscape.Raygun4Net.Tests.csproj
@@ -73,6 +73,7 @@
+
diff --git a/Mindscape.Raygun4Net.Tests/RaygunSettingsTests.cs b/Mindscape.Raygun4Net.Tests/RaygunSettingsTests.cs
index 1d8d72e8..5ef5d72c 100644
--- a/Mindscape.Raygun4Net.Tests/RaygunSettingsTests.cs
+++ b/Mindscape.Raygun4Net.Tests/RaygunSettingsTests.cs
@@ -18,7 +18,7 @@ public void Apikey_EmptyByDefault()
[Test]
public void ApiEndPoint_DefaultValue()
{
- Assert.AreEqual("https://api.raygun.io/entries", RaygunSettings.Settings.ApiEndpoint.AbsoluteUri);
+ Assert.AreEqual("https://api.raygun.com/entries", RaygunSettings.Settings.ApiEndpoint.AbsoluteUri);
}
[Test]
diff --git a/Mindscape.Raygun4Net.Tests/Storage/RaygunOfflineStorageTests.cs b/Mindscape.Raygun4Net.Tests/Storage/RaygunOfflineStorageTests.cs
new file mode 100644
index 00000000..49d823ec
--- /dev/null
+++ b/Mindscape.Raygun4Net.Tests/Storage/RaygunOfflineStorageTests.cs
@@ -0,0 +1,161 @@
+using System.Linq;
+using Mindscape.Raygun4Net.Storage;
+using NUnit.Framework;
+
+namespace Mindscape.Raygun4Net.Tests.Storage
+{
+ [TestFixture]
+ public class RaygunOfflineStorageTests
+ {
+ private IRaygunOfflineStorage _storageOne;
+ private IRaygunOfflineStorage _storageTwo;
+
+ private const string TestApiKey = "DummyApiKey";
+
+ [SetUp]
+ public void SetUp()
+ {
+ _storageOne = new IsolatedRaygunOfflineStorage();
+ _storageTwo = new IsolatedRaygunOfflineStorage();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ // Clear all files from storage.
+ var filesOne = _storageOne.FetchAll(TestApiKey);
+ foreach (var file in filesOne)
+ {
+ _storageOne.Remove(file.Name, TestApiKey);
+ }
+
+ var filesTwo = _storageTwo.FetchAll(TestApiKey);
+ foreach (var file in filesTwo)
+ {
+ _storageTwo.Remove(file.Name, TestApiKey);
+ }
+ }
+
+ [Test]
+ public void Store_UsingInvalidMessageValue_ReturnFalse()
+ {
+ Assert.IsFalse(_storageOne.Store(null, TestApiKey));
+ Assert.IsFalse(_storageOne.Store("", TestApiKey));
+ }
+
+ [Test]
+ public void Store_UsingInvalidApiKeyValue_ReturnFalse()
+ {
+ Assert.IsFalse(_storageOne.Store("DummyData", null));
+ Assert.IsFalse(_storageOne.Store("DummyData", ""));
+ }
+
+ [Test]
+ public void FetchAll_UsingInvalidApiKeyValue_ReturnEmptyResult()
+ {
+ Assert.IsEmpty(_storageOne.FetchAll(null));
+ Assert.IsEmpty(_storageOne.FetchAll(""));
+ }
+
+ [Test]
+ public void Remove_UsingInvalidNameValue_ReturnFalse()
+ {
+ Assert.IsFalse(_storageOne.Remove(null, TestApiKey));
+ Assert.IsFalse(_storageOne.Remove("", TestApiKey));
+ }
+
+ [Test]
+ public void Remove_UsingInvalidApiKeyValue_ReturnFalse()
+ {
+ Assert.IsFalse(_storageOne.Remove("DummyName", null));
+ Assert.IsFalse(_storageOne.Remove("DummyName", ""));
+ }
+
+ [Test]
+ public void Store_WriteASingleMessageToStorage_OneMessageIsAvailableFromStorage()
+ {
+ // Ensure there are no files in storage.
+ var files = _storageOne.FetchAll(TestApiKey);
+ Assert.That(files.Count, Is.EqualTo(0));
+
+ RaygunSettings.Settings.MaxCrashReportsStoredOffline = 1;
+
+ // Save one message to storage.
+ _storageOne.Store("DummyData", TestApiKey);
+
+ files = _storageOne.FetchAll(TestApiKey);
+
+ // Ensure only one file was created.
+ Assert.That(files.Count, Is.EqualTo(1));
+ Assert.That(files.First().Contents, Is.EqualTo("DummyData"));
+ }
+
+ [Test]
+ public void Store_WriteMultipleMessagesToStorage_MaxReportsLimitIsRespected()
+ {
+ // Ensure there are no files in storage.
+ var files = _storageOne.FetchAll(TestApiKey);
+ Assert.That(files.Count, Is.EqualTo(0));
+
+ RaygunSettings.Settings.MaxCrashReportsStoredOffline = 1;
+
+ // Save two messages to storage.
+ _storageOne.Store("DummyData1", TestApiKey);
+ _storageOne.Store("DummyData2", TestApiKey);
+
+ files = _storageOne.FetchAll(TestApiKey);
+
+ // Ensure only one file was created.
+ Assert.That(files.Count, Is.EqualTo(1));
+ Assert.That(files.First().Contents, Is.EqualTo("DummyData1"));
+ }
+
+ [Test]
+ public void Remove_TwoMessagesStoredAndOneRemoved_OnlyOneMessageRemainsInStorage()
+ {
+ // Ensure there are no files in storage.
+ var files = _storageOne.FetchAll(TestApiKey);
+ Assert.That(files.Count, Is.EqualTo(0));
+
+ RaygunSettings.Settings.MaxCrashReportsStoredOffline = 2;
+
+ // Save two messages to storage.
+ Assert.IsTrue(_storageOne.Store("DummyData1", TestApiKey));
+ Assert.IsTrue(_storageOne.Store("DummyData2", TestApiKey));
+
+ files = _storageOne.FetchAll(TestApiKey);
+
+ // Ensure two files were created.
+ Assert.That(files.Count, Is.EqualTo(2));
+
+ // Remove the first file.
+ Assert.IsTrue(_storageOne.Remove(files.First().Name, TestApiKey));
+
+ // Ensure only one file remains.
+ files = _storageOne.FetchAll(TestApiKey);
+ Assert.That(files.Count, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void Store_StoreUnderFirstKeyAndFetchWithSecondKey_NoFilesFoundForSecondKey()
+ {
+ const string apiKeyOne = "KEY_ONE";
+ const string apiKeyTwo = "KEY_TWO";
+
+ // Ensure there are no files in storage under the first API key.
+ Assert.That(_storageOne.FetchAll(apiKeyOne).Count, Is.EqualTo(0));
+
+ // Ensure there are no files in storage under the second API key.
+ Assert.That(_storageTwo.FetchAll(apiKeyTwo).Count, Is.EqualTo(0));
+
+ RaygunSettings.Settings.MaxCrashReportsStoredOffline = 1;
+
+ // Save one messages to storage under the first API key.
+ _storageOne.Store("DummyData1", apiKeyOne);
+
+ // There should be one file under the first API key.
+ Assert.That(_storageOne.FetchAll(apiKeyOne).Count, Is.EqualTo(1));
+ Assert.That(_storageTwo.FetchAll(apiKeyTwo).Count, Is.EqualTo(0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net.WebApi.Signed.nuspec b/Mindscape.Raygun4Net.WebApi.Signed.nuspec
index 0d086bc9..b1752a48 100644
--- a/Mindscape.Raygun4Net.WebApi.Signed.nuspec
+++ b/Mindscape.Raygun4Net.WebApi.Signed.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.WebApi.Signed
- 5.10.3
+ 5.11.0
Raygun for ASP.NET Web API
Raygun
@@ -13,13 +13,13 @@
https://github.com/MindscapeHQ/raygun4net
https://raw.github.com/MindscapeHQ/raygun4net/master/LICENSE
-
+
-
+
diff --git a/Mindscape.Raygun4Net.WebApi.nuspec b/Mindscape.Raygun4Net.WebApi.nuspec
index f6ff375a..107bb58a 100644
--- a/Mindscape.Raygun4Net.WebApi.nuspec
+++ b/Mindscape.Raygun4Net.WebApi.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net.WebApi
- 5.10.3
+ 5.11.0
Raygun for ASP.NET Web API
Raygun
@@ -13,13 +13,13 @@
https://github.com/MindscapeHQ/raygun4net
https://raw.github.com/MindscapeHQ/raygun4net/master/LICENSE
-
+
-
+
diff --git a/Mindscape.Raygun4Net.WebApi/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net.WebApi/Properties/AssemblyInfo.cs
index 2ea52d64..370b1c76 100644
--- a/Mindscape.Raygun4Net.WebApi/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net.WebApi/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net.WebApi")]
@@ -14,8 +13,8 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net.WebApi/Properties/AssemblyVersionInfo.cs b/Mindscape.Raygun4Net.WebApi/Properties/AssemblyVersionInfo.cs
index 6af10067..a28f2cb5 100644
--- a/Mindscape.Raygun4Net.WebApi/Properties/AssemblyVersionInfo.cs
+++ b/Mindscape.Raygun4Net.WebApi/Properties/AssemblyVersionInfo.cs
@@ -10,5 +10,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.10.3.0")]
-[assembly: AssemblyFileVersion("5.10.3.0")]
+[assembly: AssemblyVersion("5.11.0.0")]
+[assembly: AssemblyFileVersion("5.11.0.0")]
diff --git a/Mindscape.Raygun4Net.WebApi/readme.txt b/Mindscape.Raygun4Net.WebApi/README.md
similarity index 100%
rename from Mindscape.Raygun4Net.WebApi/readme.txt
rename to Mindscape.Raygun4Net.WebApi/README.md
diff --git a/Mindscape.Raygun4Net.WebApi/RaygunWebApiClient.cs b/Mindscape.Raygun4Net.WebApi/RaygunWebApiClient.cs
index 730d2a07..9ece8e8e 100644
--- a/Mindscape.Raygun4Net.WebApi/RaygunWebApiClient.cs
+++ b/Mindscape.Raygun4Net.WebApi/RaygunWebApiClient.cs
@@ -1,4 +1,5 @@
using System;
+using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading;
@@ -6,32 +7,42 @@
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.ExceptionHandling;
-using Mindscape.Raygun4Net.WebApi.Builders;
+using System.Collections;
using System.Collections.Generic;
+using System.Linq;
+using Mindscape.Raygun4Net.WebApi.Builders;
using Mindscape.Raygun4Net.Messages;
using Mindscape.Raygun4Net.Filters;
-using System.Net;
-using System.Collections;
-using System.Linq;
+using Mindscape.Raygun4Net.Logging;
+using Mindscape.Raygun4Net.Storage;
namespace Mindscape.Raygun4Net.WebApi
{
public class RaygunWebApiClient : RaygunClientBase
{
internal const string UnhandledExceptionTag = "UnhandledException";
-
- protected readonly RaygunRequestMessageOptions _requestMessageOptions = new RaygunRequestMessageOptions();
- private readonly List _wrapperExceptions = new List();
+ private static RaygunWebApiExceptionFilter _exceptionFilter;
+ private static RaygunWebApiActionFilter _actionFilter;
+ private static RaygunWebApiDelegatingHandler _delegatingHandler;
+ private static object _sendLock = new object();
+
+ private readonly List _wrapperExceptions = new List();
private readonly ThreadLocal _currentWebRequest = new ThreadLocal(() => null);
private readonly ThreadLocal _currentRequestMessage = new ThreadLocal(() => null);
-
+
private readonly string _apiKey;
- private static RaygunWebApiExceptionFilter _exceptionFilter;
- private static RaygunWebApiActionFilter _actionFilter;
- private static RaygunWebApiDelegatingHandler _delegatingHandler;
-
+ private IRaygunOfflineStorage _offlineStorage = new IsolatedRaygunOfflineStorage();
+
+ protected readonly RaygunRequestMessageOptions _requestMessageOptions = new RaygunRequestMessageOptions();
+
+ ///
+ /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
+ /// and requires credentials.
+ ///
+ public ICredentials ProxyCredentials { get; set; }
+
///
/// Initializes a new instance of the class.
/// Uses the ApiKey specified in the config file.
@@ -59,7 +70,7 @@ public RaygunWebApiClient()
public RaygunWebApiClient(string apiKey)
{
_apiKey = apiKey;
-
+
ApplicationVersion = RaygunSettings.Settings.ApplicationVersion;
if (string.IsNullOrEmpty(ApplicationVersion))
{
@@ -68,10 +79,12 @@ public RaygunWebApiClient(string apiKey)
// or else we will not be getting the user's library but our own Raygun4Net library.
ApplicationVersion = Assembly.GetCallingAssembly()?.GetName()?.Version?.ToString();
}
-
+
Init();
}
+ #region Attach/Detach Methods
+
///
/// Causes Raygun4Net to listen for exceptions.
///
@@ -86,7 +99,7 @@ public static void Attach(HttpConfiguration config)
// or else we will not be getting the user's library but our own Raygun4Net library.
appVersion = Assembly.GetCallingAssembly()?.GetName()?.Version?.ToString();
}
-
+
AttachInternal(config, null, appVersion);
}
@@ -105,7 +118,7 @@ public static void Attach(HttpConfiguration config, Func gen
// or else we will not be getting the user's library but our own Raygun4Net library.
appVersion = Assembly.GetCallingAssembly()?.GetName()?.Version?.ToString();
}
-
+
if (generateRaygunClient != null)
{
AttachInternal(config, message => generateRaygunClient(), appVersion);
@@ -145,7 +158,7 @@ private static void AttachInternal(
string appVersion)
{
Detach(config);
-
+
if (RaygunSettings.Settings.IsRawDataIgnored == false)
{
_delegatingHandler = new RaygunWebApiDelegatingHandler();
@@ -220,7 +233,7 @@ public static void Detach(HttpConfiguration config)
_actionFilter = null;
}
}
-
+
///
///
///
@@ -278,31 +291,9 @@ private void Init()
UseKeyValuePairRawDataFilter = RaygunSettings.Settings.UseKeyValuePairRawDataFilter;
}
- ///
- /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
- /// and requires credentials.
- ///
- public ICredentials ProxyCredentials { get; set; }
-
- protected override bool CanSend(Exception exception)
- {
- if (RaygunSettings.Settings.ExcludeErrorsFromLocal && _currentWebRequest.Value != null && _currentWebRequest.Value.IsLocal())
- {
- return false;
- }
-
- return base.CanSend(exception);
- }
-
- protected bool CanSend(RaygunMessage message)
- {
- if (message != null && message.Details != null && message.Details.Response != null)
- {
- return !RaygunSettings.Settings.ExcludedStatusCodes.Contains(message.Details.Response.StatusCode);
- }
+ #endregion // Attach/Detach Methods
- return true;
- }
+ #region Message Scrubbing Properties
///
/// Adds a list of outer exceptions that will be stripped, leaving only the valuable inner exception.
@@ -403,8 +394,8 @@ public void IgnoreServerVariableNames(params string[] names)
}
///
- /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.io.
- /// The default is false which means RawData will be sent to Raygun.io.
+ /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.
+ /// The default is false which means RawData will be sent to Raygun.
///
public bool IsRawDataIgnored
{
@@ -444,7 +435,7 @@ public bool UseKeyValuePairRawDataFilter
///
/// Add an implementation to be used when capturing the raw data
- /// of a HTTP request. This filter will be passed the request raw data and is expected to remove
+ /// of a HTTP request. This filter will be passed the request raw data and is expected to remove
/// or replace values whose keys are found in the list supplied to the Filter method.
///
/// Custom raw data filter implementation.
@@ -453,8 +444,12 @@ public void AddRawDataFilter(IRaygunDataFilter filter)
_requestMessageOptions.AddRawDataFilter(filter);
}
+ #endregion // Message Scrubbing Properties
+
+ #region Message Send Methods
+
///
- /// Transmits an exception to Raygun.io synchronously, using the version number of the originating assembly.
+ /// Transmits an exception to Raygun synchronously, using the version number of the originating assembly.
///
/// The exception to deliver.
public override void Send(Exception exception)
@@ -463,7 +458,7 @@ public override void Send(Exception exception)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification. This uses the version number of the originating assembly.
///
/// The exception to deliver.
@@ -474,7 +469,7 @@ public void Send(Exception exception, IList tags)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -488,12 +483,13 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
_currentRequestMessage.Value = BuildRequestMessage();
StripAndSend(exception, tags, userCustomData);
+
FlagAsSent(exception);
}
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The exception to deliver.
public void SendInBackground(Exception exception)
@@ -502,7 +498,7 @@ public void SendInBackground(Exception exception)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -512,7 +508,7 @@ public void SendInBackground(Exception exception, IList tags)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -531,12 +527,13 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
_currentRequestMessage.Value = currentRequestMessage;
StripAndSend(exception, tags, userCustomData, currentTime);
});
+
FlagAsSent(exception);
}
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The RaygunMessage to send. This needs its OccurredOn property
/// set to a valid DateTime and as much of the Details property as is available.
@@ -545,11 +542,94 @@ public void SendInBackground(RaygunMessage raygunMessage)
ThreadPool.QueueUserWorkItem(c => Send(raygunMessage));
}
- internal void FlagExceptionAsSent(Exception exception)
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public override void Send(RaygunMessage raygunMessage)
{
- base.FlagAsSent(exception);
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send error report due to invalid API key");
+ return;
+ }
+
+ bool canSend = OnSendingMessage(raygunMessage) && CanSend(raygunMessage);
+
+ if (!canSend)
+ {
+ return;
+ }
+
+ string message = null;
+
+ try
+ {
+ message = SimpleJson.SerializeObject(raygunMessage);
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to serialize report due to: {ex.Message}");
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ bool successfullySentReport = true;
+
+ try
+ {
+ Send(message);
+ }
+ catch (Exception ex)
+ {
+ successfullySentReport = false;
+
+ RaygunLogger.Instance.Error($"Failed to send report to Raygun due to: {ex.Message}");
+
+ SaveMessage(message);
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (successfullySentReport)
+ {
+ SendStoredMessages();
+ }
+ }
+
+ private void Send(string message)
+ {
+ RaygunLogger.Instance.Verbose("Sending Payload --------------");
+ RaygunLogger.Instance.Verbose(message);
+ RaygunLogger.Instance.Verbose("------------------------------");
+
+ WebClientHelper.Send(message, _apiKey, ProxyCredentials);
+ }
+
+ private void StripAndSend(Exception exception, IList tags, IDictionary userCustomData, DateTime? currentTime = null)
+ {
+ foreach (Exception e in StripWrapperExceptions(exception))
+ {
+ Send(BuildMessage(e, tags, userCustomData, currentTime));
+ }
}
+ #endregion // Message Send Methods
+
+ #region Message Building Methods
+
private RaygunRequestMessage BuildRequestMessage()
{
var message = _currentWebRequest.Value != null ? RaygunWebApiRequestMessageBuilder.Build(_currentWebRequest.Value, _requestMessageOptions) : null;
@@ -557,12 +637,6 @@ private RaygunRequestMessage BuildRequestMessage()
return message;
}
- public RaygunWebApiClient SetCurrentHttpRequest(HttpRequestMessage request)
- {
- _currentWebRequest.Value = request;
- return this;
- }
-
protected RaygunMessage BuildMessage(Exception exception, IList tags, IDictionary userCustomData)
{
return BuildMessage(exception, tags, userCustomData, null);
@@ -592,14 +666,6 @@ protected RaygunMessage BuildMessage(Exception exception, IList tags, ID
return message;
}
- private void StripAndSend(Exception exception, IList tags, IDictionary userCustomData, DateTime? currentTime = null)
- {
- foreach (Exception e in StripWrapperExceptions(exception))
- {
- Send(BuildMessage(e, tags, userCustomData, currentTime));
- }
- }
-
protected IEnumerable StripWrapperExceptions(Exception exception)
{
if (exception != null && _wrapperExceptions.Any(wrapperException => exception.GetType() == wrapperException && (exception.InnerException != null || exception is ReflectionTypeLoadException)))
@@ -654,38 +720,131 @@ protected IEnumerable StripWrapperExceptions(Exception exception)
}
}
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public override void Send(RaygunMessage raygunMessage)
+ #endregion // Message Building Methods
+
+ #region Message Offline Storage
+
+ private void SaveMessage(string message)
{
- try
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
{
- bool canSend = OnSendingMessage(raygunMessage) && CanSend(raygunMessage);
- if (canSend)
- {
- var message = SimpleJson.SerializeObject(raygunMessage);
- WebClientHelper.Send(message, _apiKey, ProxyCredentials);
- }
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping saving report.");
+ return;
}
- catch (Exception ex)
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to save report due to invalid API key.");
+ return;
+ }
+
+ // Avoid writing and reading from disk at the same time with `SendStoredMessages`.
+ lock (_sendLock)
{
try
{
- System.Diagnostics.Trace.WriteLine(string.Format("Error Logging Exception to Raygun.io {0}", ex.Message));
+ if (!_offlineStorage.Store(message, _apiKey))
+ {
+ RaygunLogger.Instance.Warning("Failed to save report to offline storage");
+ }
}
- catch
+ catch (Exception ex)
{
- // ignored
+ RaygunLogger.Instance.Error($"Failed to save report to offline storage due to: {ex.Message}");
}
+ }
+ }
- if (RaygunSettings.Settings.ThrowOnError)
+ private void SendStoredMessages()
+ {
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping sending stored reports.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send offline reports due to invalid API key.");
+ return;
+ }
+
+ lock (_sendLock)
+ {
+ try
{
- throw;
+ var files = _offlineStorage.FetchAll(_apiKey);
+
+ foreach (var file in files)
+ {
+ try
+ {
+ // Send the stored report.
+ Send(file.Contents);
+
+ // Remove the stored report from local storage.
+ if (_offlineStorage.Remove(file.Name, _apiKey))
+ {
+ RaygunLogger.Instance.Info("Successfully removed report from offline storage.");
+ }
+ else
+ {
+ RaygunLogger.Instance.Warning("Failed to remove report from offline storage.");
+ }
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+
+ // If just one message fails to send, then don't delete the message,
+ // and don't attempt sending anymore until later.
+ return;
+ }
+ }
}
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+ }
+ }
+ }
+
+ #endregion // Message Offline Storage
+
+ protected bool ValidateApiKey()
+ {
+ return !string.IsNullOrEmpty(_apiKey);
+ }
+
+ protected override bool CanSend(Exception exception)
+ {
+ if (RaygunSettings.Settings.ExcludeErrorsFromLocal && _currentWebRequest.Value != null && _currentWebRequest.Value.IsLocal())
+ {
+ return false;
+ }
+
+ return base.CanSend(exception);
+ }
+
+ protected bool CanSend(RaygunMessage message)
+ {
+ if (message?.Details?.Response != null)
+ {
+ return !RaygunSettings.Settings.ExcludedStatusCodes.Contains(message.Details.Response.StatusCode);
}
+
+ return true;
+ }
+
+ internal void FlagExceptionAsSent(Exception exception)
+ {
+ base.FlagAsSent(exception);
+ }
+
+ public RaygunWebApiClient SetCurrentHttpRequest(HttpRequestMessage request)
+ {
+ _currentWebRequest.Value = request;
+ return this;
}
}
}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net.nuspec b/Mindscape.Raygun4Net.nuspec
index 0e197ea8..038b5cd9 100644
--- a/Mindscape.Raygun4Net.nuspec
+++ b/Mindscape.Raygun4Net.nuspec
@@ -2,7 +2,7 @@
Mindscape.Raygun4Net
- 5.10.2
+ 5.11.0
Raygun
@@ -43,6 +43,6 @@
-
+
diff --git a/Mindscape.Raygun4Net/Builders/RaygunEnvironmentMessageBuilder.cs b/Mindscape.Raygun4Net/Builders/RaygunEnvironmentMessageBuilder.cs
index 742b9174..137b7708 100644
--- a/Mindscape.Raygun4Net/Builders/RaygunEnvironmentMessageBuilder.cs
+++ b/Mindscape.Raygun4Net/Builders/RaygunEnvironmentMessageBuilder.cs
@@ -9,6 +9,7 @@
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.Devices;
+using Mindscape.Raygun4Net.Logging;
using Mindscape.Raygun4Net.Messages;
namespace Mindscape.Raygun4Net.Builders
@@ -16,24 +17,24 @@ namespace Mindscape.Raygun4Net.Builders
public class RaygunEnvironmentMessageBuilder
{
private static RaygunEnvironmentMessage _message;
-
+
public static RaygunEnvironmentMessage Build()
{
bool mediumTrust = RaygunSettings.Settings.MediumTrust || !HasUnrestrictedFeatureSet;
-
+
//
// Gather the environment information that only needs to be collected once
//
-
+
if (_message == null)
{
_message = new RaygunEnvironmentMessage();
-
+
// Different environments can fail to load the environment details.
// For now if they fail to load for whatever reason then just
// swallow the exception. A good addition would be to handle
// these cases and load them correctly depending on where its running.
- // see http://raygun.io/forums/thread/3655
+ // see http://raygun.com/forums/thread/3655
try
{
@@ -42,9 +43,9 @@ public static RaygunEnvironmentMessage Build()
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving window dimensions: {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving window dimensions: {ex.Message}");
}
-
+
try
{
DateTime now = DateTime.Now;
@@ -53,9 +54,9 @@ public static RaygunEnvironmentMessage Build()
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving time and locale: {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving time and locale: {ex.Message}");
}
-
+
try
{
if (!mediumTrust)
@@ -64,50 +65,50 @@ public static RaygunEnvironmentMessage Build()
// moved to net40 minimum we can move this out of here
_message.ProcessorCount = Environment.ProcessorCount;
_message.Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
-
+
ComputerInfo info = new ComputerInfo();
_message.TotalPhysicalMemory = info.TotalPhysicalMemory / 0x100000; // in MB
_message.TotalVirtualMemory = info.TotalVirtualMemory / 0x100000; // in MB
-
+
_message.Cpu = GetCpu();
_message.OSVersion = GetOSVersion();
}
}
catch (SecurityException)
{
- System.Diagnostics.Trace.WriteLine("RaygunClient error: couldn't access environment variables. If you are running in Medium Trust, in web.config in RaygunSettings set mediumtrust=\"true\"");
+ RaygunLogger.Instance.Error("RaygunClient error: couldn't access environment variables. If you are running in Medium Trust, in web.config in RaygunSettings set mediumtrust=\"true\"");
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving environment info: {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving environment info: {ex.Message}");
}
}
//
// Gather the environment info that must be collected at the time of a report being generated.
//
-
+
try
{
if (!mediumTrust)
{
ComputerInfo info = new ComputerInfo();
-
+
_message.AvailablePhysicalMemory = info.AvailablePhysicalMemory / 0x100000; // in MB
_message.AvailableVirtualMemory = info.AvailableVirtualMemory / 0x100000; // in MB
-
+
_message.DiskSpaceFree = GetDiskSpace();
}
}
catch (SecurityException)
{
- System.Diagnostics.Trace.WriteLine("RaygunClient error: couldn't access environment variables. If you are running in Medium Trust, in web.config in RaygunSettings set mediumtrust=\"true\"");
+ RaygunLogger.Instance.Error("RaygunClient error: couldn't access environment variables. If you are running in Medium Trust, in web.config in RaygunSettings set mediumtrust=\"true\"");
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving environment info: {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving environment info: {ex.Message}");
}
-
+
return _message;
}
@@ -125,7 +126,7 @@ private static string GetCpu()
}
catch (ManagementException ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving CPU {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving CPU {ex.Message}");
}
}
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
@@ -145,7 +146,7 @@ private static string GetOSVersion()
}
catch (ManagementException ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving OSVersion {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving OSVersion {ex.Message}");
}
}
return Environment.OSVersion.Version.ToString(3);
diff --git a/Mindscape.Raygun4Net/Builders/RaygunRequestMessageBuilder.cs b/Mindscape.Raygun4Net/Builders/RaygunRequestMessageBuilder.cs
index 4508300b..61ddde1c 100644
--- a/Mindscape.Raygun4Net/Builders/RaygunRequestMessageBuilder.cs
+++ b/Mindscape.Raygun4Net/Builders/RaygunRequestMessageBuilder.cs
@@ -10,6 +10,7 @@
using System.Web;
using Mindscape.Raygun4Net.Messages;
using Mindscape.Raygun4Net.Filters;
+using Mindscape.Raygun4Net.Logging;
namespace Mindscape.Raygun4Net.Builders
{
@@ -44,7 +45,7 @@ public static RaygunRequestMessage Build(HttpRequest request, RaygunRequestMessa
}
catch (Exception e)
{
- System.Diagnostics.Trace.WriteLine("Failed to get basic request info: {0}", e.Message);
+ RaygunLogger.Instance.Error($"Failed to get basic request info: { e.Message}");
}
return message;
@@ -92,7 +93,7 @@ private static string GetIpAddress(HttpRequest request)
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine("Failed to get IP address: {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Failed to get IP address: {ex.Message}");
}
return strIp;
diff --git a/Mindscape.Raygun4Net/Logging/IRaygunLogger.cs b/Mindscape.Raygun4Net/Logging/IRaygunLogger.cs
new file mode 100644
index 00000000..e3f110ab
--- /dev/null
+++ b/Mindscape.Raygun4Net/Logging/IRaygunLogger.cs
@@ -0,0 +1,12 @@
+
+namespace Mindscape.Raygun4Net.Logging
+{
+ public interface IRaygunLogger
+ {
+ void Error(string message);
+ void Warning(string message);
+ void Info(string message);
+ void Debug(string message);
+ void Verbose(string message);
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Logging/RaygunLogLevel.cs b/Mindscape.Raygun4Net/Logging/RaygunLogLevel.cs
new file mode 100644
index 00000000..b4bdc481
--- /dev/null
+++ b/Mindscape.Raygun4Net/Logging/RaygunLogLevel.cs
@@ -0,0 +1,12 @@
+namespace Mindscape.Raygun4Net.Logging
+{
+ public enum RaygunLogLevel
+ {
+ None = 0,
+ Error,
+ Warning,
+ Info,
+ Debug,
+ Verbose
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Logging/RaygunLogger.cs b/Mindscape.Raygun4Net/Logging/RaygunLogger.cs
new file mode 100644
index 00000000..3d06067a
--- /dev/null
+++ b/Mindscape.Raygun4Net/Logging/RaygunLogger.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Diagnostics;
+using Mindscape.Raygun4Net.Utils;
+
+namespace Mindscape.Raygun4Net.Logging
+{
+ public class RaygunLogger : Singleton, IRaygunLogger
+ {
+ private const string RaygunPrefix = "Raygun: ";
+
+ public void Error(string message)
+ {
+ Log(RaygunLogLevel.Error, message);
+ }
+
+ public void Warning(string message)
+ {
+ Log(RaygunLogLevel.Warning, message);
+ }
+
+ public void Info(string message)
+ {
+ Log(RaygunLogLevel.Info, message);
+ }
+
+ public void Debug(string message)
+ {
+ Log(RaygunLogLevel.Debug, message);
+ }
+
+ public void Verbose(string message)
+ {
+ Log(RaygunLogLevel.Verbose, message);
+ }
+
+ private void Log(RaygunLogLevel level, string message)
+ {
+ if (RaygunSettings.Settings.LogLevel == RaygunLogLevel.None)
+ {
+ return;
+ }
+
+ if (level <= RaygunSettings.Settings.LogLevel)
+ {
+ try
+ {
+ Trace.WriteLine($"{RaygunPrefix}{message}");
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Mindscape.Raygun4Net.csproj b/Mindscape.Raygun4Net/Mindscape.Raygun4Net.csproj
index 35d37489..300159d8 100644
--- a/Mindscape.Raygun4Net/Mindscape.Raygun4Net.csproj
+++ b/Mindscape.Raygun4Net/Mindscape.Raygun4Net.csproj
@@ -77,6 +77,9 @@
+
+
+
@@ -115,6 +118,11 @@
+
+
+
+
+
diff --git a/Mindscape.Raygun4Net/ProfilingSupport/HttpApplicationInitializer.cs b/Mindscape.Raygun4Net/ProfilingSupport/HttpApplicationInitializer.cs
index 648239b1..f59bcfd4 100644
--- a/Mindscape.Raygun4Net/ProfilingSupport/HttpApplicationInitializer.cs
+++ b/Mindscape.Raygun4Net/ProfilingSupport/HttpApplicationInitializer.cs
@@ -2,6 +2,7 @@
using System.IO;
using System.Threading;
using System.Web;
+using Mindscape.Raygun4Net.Logging;
using Mindscape.Raygun4Net.ProfilingSupport;
namespace Mindscape.Raygun4Net
@@ -42,7 +43,7 @@ private void BeginRequest(object sender, EventArgs e)
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine($"Error during begin request of APM sampling: {ex.Message}");
+ RaygunLogger.Instance.Error($"Error during begin request of APM sampling: {ex.Message}");
}
}
@@ -57,7 +58,7 @@ private void EndRequest(object sender, EventArgs e)
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine($"Error during end request of APM sampling: {ex.Message}");
+ RaygunLogger.Instance.Error($"Error during end request of APM sampling: {ex.Message}");
}
}
@@ -71,7 +72,7 @@ private bool InitProfilingSupport()
{
if (APM.ProfilerAttached)
{
- System.Diagnostics.Trace.WriteLine("Detected Raygun APM profiler is attached, initializing profiler support.");
+ RaygunLogger.Instance.Info("Detected Raygun APM profiler is attached, initializing profiler support.");
_samplingManager = new SamplingManager();
_refreshTimer = new Timer(RefreshAgentSettings, null, TimeSpan.Zero, AgentPollingDelay);
@@ -87,7 +88,7 @@ private bool InitProfilingSupport()
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine($"Error initialising APM profiler support: {ex.Message}");
+ RaygunLogger.Instance.Error($"Error initialising APM profiler support: {ex.Message}");
}
return false;
@@ -116,17 +117,17 @@ private void RefreshAgentSettings(object state)
}
else
{
- System.Diagnostics.Trace.WriteLine($"Could not locate sampling settings for site {_appIdentifier}");
+ RaygunLogger.Instance.Warning($"Could not locate sampling settings for site {_appIdentifier}");
}
}
else
{
- System.Diagnostics.Trace.WriteLine($"Could not locate Raygun APM configuration file {SettingsFilePath}");
+ RaygunLogger.Instance.Warning($"Could not locate Raygun APM configuration file {SettingsFilePath}");
}
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine($"Error refreshing agent settings: {ex.Message}");
+ RaygunLogger.Instance.Error($"Error refreshing agent settings: {ex.Message}");
}
}
}
diff --git a/Mindscape.Raygun4Net/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net/Properties/AssemblyInfo.cs
index 4c93dc56..5d6b0337 100644
--- a/Mindscape.Raygun4Net/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net")]
@@ -10,12 +9,12 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Raygun")]
[assembly: AssemblyProduct("Raygun4Net")]
-[assembly: AssemblyCopyright("Copyright © Raygun 2012-2019")]
+[assembly: AssemblyCopyright("Copyright © Raygun 2012-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net/RaygunClient.cs b/Mindscape.Raygun4Net/RaygunClient.cs
index 9f0a22c9..c574656d 100644
--- a/Mindscape.Raygun4Net/RaygunClient.cs
+++ b/Mindscape.Raygun4Net/RaygunClient.cs
@@ -3,15 +3,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using Mindscape.Raygun4Net.Messages;
-
using System.Web;
using System.Threading;
using System.Reflection;
+using Mindscape.Raygun4Net.Messages;
using Mindscape.Raygun4Net.Builders;
-using System.IO;
-using System.IO.IsolatedStorage;
-using System.Text;
+using Mindscape.Raygun4Net.Logging;
+using Mindscape.Raygun4Net.Storage;
namespace Mindscape.Raygun4Net
{
@@ -19,12 +17,35 @@ public class RaygunClient : RaygunClientBase
{
internal const string UnhandledExceptionTag = "UnhandledException";
+ [ThreadStatic]
+ private static RaygunRequestMessage _currentRequestMessage;
+ private static object _sendLock = new object();
+
private readonly string _apiKey;
private readonly RaygunRequestMessageOptions _requestMessageOptions = new RaygunRequestMessageOptions();
private readonly List _wrapperExceptions = new List();
- [ThreadStatic]
- private static RaygunRequestMessage _currentRequestMessage;
+ private IRaygunOfflineStorage _offlineStorage = new IsolatedRaygunOfflineStorage();
+
+ ///
+ /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
+ /// and requires credentials.
+ ///
+ public ICredentials ProxyCredentials { get; set; }
+
+ ///
+ /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
+ ///
+ public IWebProxy WebProxy { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Uses the ApiKey specified in the config file.
+ ///
+ public RaygunClient()
+ : this(RaygunSettings.Settings.ApiKey)
+ {
+ }
///
/// Initializes a new instance of the class.
@@ -42,56 +63,30 @@ public RaygunClient(string apiKey)
var ignoredNames = RaygunSettings.Settings.IgnoreFormFieldNames.Split(',');
IgnoreFormFieldNames(ignoredNames);
}
+
if (!string.IsNullOrEmpty(RaygunSettings.Settings.IgnoreHeaderNames))
{
var ignoredNames = RaygunSettings.Settings.IgnoreHeaderNames.Split(',');
IgnoreHeaderNames(ignoredNames);
}
+
if (!string.IsNullOrEmpty(RaygunSettings.Settings.IgnoreCookieNames))
{
var ignoredNames = RaygunSettings.Settings.IgnoreCookieNames.Split(',');
IgnoreCookieNames(ignoredNames);
}
+
if (!string.IsNullOrEmpty(RaygunSettings.Settings.IgnoreServerVariableNames))
{
var ignoredNames = RaygunSettings.Settings.IgnoreServerVariableNames.Split(',');
IgnoreServerVariableNames(ignoredNames);
}
+
IsRawDataIgnored = RaygunSettings.Settings.IsRawDataIgnored;
ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
}
- ///
- /// Initializes a new instance of the class.
- /// Uses the ApiKey specified in the config file.
- ///
- public RaygunClient()
- : this(RaygunSettings.Settings.ApiKey)
- {
- }
-
- protected bool ValidateApiKey()
- {
- if (string.IsNullOrEmpty(_apiKey))
- {
- System.Diagnostics.Debug.WriteLine("ApiKey has not been provided, exception will not be logged");
- return false;
- }
- return true;
- }
-
- ///
- /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
- /// and requires credentials.
- ///
- public ICredentials ProxyCredentials { get; set; }
-
- ///
- /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
- ///
- public IWebProxy WebProxy { get; set; }
-
///
/// Adds a list of outer exceptions that will be stripped, leaving only the valuable inner exception.
/// This can be used when a wrapper exception, e.g. TargetInvocationException or HttpUnhandledException,
@@ -124,6 +119,8 @@ public void RemoveWrapperExceptions(params Type[] wrapperExceptions)
}
}
+ #region Message Scrubbing Properties
+
///
/// Adds a list of keys to ignore when attaching the Form data of an HTTP POST request. This allows
/// you to remove sensitive data from the transmitted copy of the Form on the HttpRequest by specifying the keys you want removed.
@@ -169,8 +166,8 @@ public void IgnoreServerVariableNames(params string[] names)
}
///
- /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.io.
- /// The default is false which means RawData will be sent to Raygun.io.
+ /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.
+ /// The default is false which means RawData will be sent to Raygun.
///
public bool IsRawDataIgnored
{
@@ -181,30 +178,12 @@ public bool IsRawDataIgnored
}
}
- protected override bool CanSend(Exception exception)
- {
- if (RaygunSettings.Settings.ExcludeErrorsFromLocal && HttpContext.Current != null)
- {
- try
- {
- if (HttpContext.Current.Request.IsLocal)
- {
- return false;
- }
- }
- catch
- {
- if (RaygunSettings.Settings.ThrowOnError)
- {
- throw;
- }
- }
- }
- return base.CanSend(exception);
- }
+ #endregion // Message Scrubbing Properties
+
+ #region Message Send Methods
///
- /// Transmits an exception to Raygun.io synchronously, using the version number of the originating assembly.
+ /// Transmits an exception to Raygun synchronously, using the version number of the originating assembly.
///
/// The exception to deliver.
public override void Send(Exception exception)
@@ -213,7 +192,7 @@ public override void Send(Exception exception)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification. This uses the version number of the originating assembly.
///
/// The exception to deliver.
@@ -224,7 +203,7 @@ public void Send(Exception exception, IList tags)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -237,7 +216,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -252,12 +231,13 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
_currentRequestMessage = BuildRequestMessage();
Send(BuildMessage(exception, tags, userCustomData, userInfo, null));
+
FlagAsSent(exception);
}
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The exception to deliver.
public void SendInBackground(Exception exception)
@@ -266,7 +246,7 @@ public void SendInBackground(Exception exception)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -276,7 +256,7 @@ public void SendInBackground(Exception exception, IList tags)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -287,7 +267,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -301,7 +281,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
// otherwise it will be disposed while we are using it on the other thread.
RaygunRequestMessage currentRequestMessage = BuildRequestMessage();
DateTime currentTime = DateTime.UtcNow;
-
+
ThreadPool.QueueUserWorkItem(c =>
{
try
@@ -324,7 +304,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The RaygunMessage to send. This needs its OccurredOn property
/// set to a valid DateTime and as much of the Details property as is available.
@@ -333,10 +313,94 @@ public void SendInBackground(RaygunMessage raygunMessage)
ThreadPool.QueueUserWorkItem(c => Send(raygunMessage));
}
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public override void Send(RaygunMessage raygunMessage)
+ {
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send error report due to invalid API key");
+ return;
+ }
+
+ bool canSend = OnSendingMessage(raygunMessage);
+
+ if (!canSend)
+ {
+ return;
+ }
+
+ string message = null;
+
+ try
+ {
+ message = SimpleJson.SerializeObject(raygunMessage);
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to serialize report due to: {ex.Message}");
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ bool successfullySentReport = true;
+
+ try
+ {
+ Send(message);
+ }
+ catch (Exception ex)
+ {
+ successfullySentReport = false;
+
+ RaygunLogger.Instance.Error($"Failed to send report to Raygun due to: {ex.Message}");
+
+ SaveMessage(message);
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (successfullySentReport)
+ {
+ SendStoredMessages();
+ }
+ }
+
+ private void Send(string message)
+ {
+ RaygunLogger.Instance.Verbose("Sending Payload --------------");
+ RaygunLogger.Instance.Verbose(message);
+ RaygunLogger.Instance.Verbose("------------------------------");
+
+ using (var client = CreateWebClient())
+ {
+ client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
+ }
+ }
+
+ #endregion // Message Send Methods
+
+ #region Message Building Methods
+
private RaygunRequestMessage BuildRequestMessage()
{
RaygunRequestMessage requestMessage = null;
HttpContext context = HttpContext.Current;
+
if (context != null)
{
HttpRequest request = null;
@@ -346,7 +410,7 @@ private RaygunRequestMessage BuildRequestMessage()
}
catch (HttpException ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving HttpRequest {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Failed to retrieve the HttpRequest due to: {ex.Message}");
}
if (request != null)
@@ -404,62 +468,122 @@ private Exception StripWrapperExceptions(Exception exception)
return exception;
}
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public override void Send(RaygunMessage raygunMessage)
+ #endregion // Message Building Methods
+
+ #region Message Offline Storage
+
+ private void SaveMessage(string message)
{
- bool canSend = OnSendingMessage(raygunMessage);
- if (canSend)
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping saving report.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to save report due to invalid API key.");
+ return;
+ }
+
+ // Avoid writing and reading from disk at the same time with `SendStoredMessages`.
+ lock (_sendLock)
{
- string message = null;
try
{
- message = SimpleJson.SerializeObject(raygunMessage);
+ if (!_offlineStorage.Store(message, _apiKey))
+ {
+ RaygunLogger.Instance.Warning("Failed to save report to offline storage");
+ }
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine(string.Format("Error serializing exception {0}", ex.Message));
-
- if (RaygunSettings.Settings.ThrowOnError)
- {
- throw;
- }
+ RaygunLogger.Instance.Error($"Failed to save report to offline storage due to: {ex.Message}");
}
+ }
+ }
- if (message != null)
+ private void SendStoredMessages()
+ {
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping sending stored reports.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send offline reports due to invalid API key.");
+ return;
+ }
+
+ lock (_sendLock)
+ {
+ try
{
- try
- {
- Send(message);
- }
- catch (Exception ex)
+ var files = _offlineStorage.FetchAll(_apiKey);
+
+ foreach (var file in files)
{
- SaveMessage(message);
- System.Diagnostics.Trace.WriteLine(string.Format("Error Logging Exception to Raygun.io {0}", ex.Message));
+ try
+ {
+ // Send the stored report.
+ Send(file.Contents);
- if (RaygunSettings.Settings.ThrowOnError)
+ // Remove the stored report from local storage.
+ if (_offlineStorage.Remove(file.Name, _apiKey))
+ {
+ RaygunLogger.Instance.Info("Successfully removed report from offline storage.");
+ }
+ else
+ {
+ RaygunLogger.Instance.Warning("Failed to remove report from offline storage.");
+ }
+ }
+ catch (Exception ex)
{
- throw;
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+
+ // If just one message fails to send, then don't delete the message,
+ // and don't attempt sending anymore until later.
+ return;
}
}
-
- SendStoredMessages();
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
}
}
}
- private void Send(string message)
+ #endregion // Message Offline Storage
+
+ protected bool ValidateApiKey()
{
- if (ValidateApiKey())
+ return !string.IsNullOrEmpty(_apiKey);
+ }
+
+ protected override bool CanSend(Exception exception)
+ {
+ if (RaygunSettings.Settings.ExcludeErrorsFromLocal && HttpContext.Current != null)
{
- using (var client = CreateWebClient())
+ try
{
- client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
+ if (HttpContext.Current.Request.IsLocal)
+ {
+ return false;
+ }
+ }
+ catch
+ {
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
}
}
+ return base.CanSend(exception);
}
protected WebClient CreateWebClient()
@@ -495,135 +619,5 @@ protected WebClient CreateWebClient()
}
return client;
}
-
- private void SaveMessage(string message)
- {
- try
- {
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
- {
- string directoryName = "RaygunOfflineStorage";
- string[] directories = isolatedStorage.GetDirectoryNames("*");
- if (!FileExists(directories, directoryName))
- {
- isolatedStorage.CreateDirectory(directoryName);
- }
-
- int number = 1;
- string[] files = isolatedStorage.GetFileNames(directoryName + "\\*.txt");
- while (true)
- {
- bool exists = FileExists(files, "RaygunErrorMessage" + number + ".txt");
- if (!exists)
- {
- string nextFileName = "RaygunErrorMessage" + (number + 1) + ".txt";
- exists = FileExists(files, nextFileName);
- if (exists)
- {
- isolatedStorage.DeleteFile(directoryName + "\\" + nextFileName);
- }
- break;
- }
- number++;
- }
-
- if (number == 11)
- {
- string firstFileName = "RaygunErrorMessage1.txt";
- if (FileExists(files, firstFileName))
- {
- isolatedStorage.DeleteFile(directoryName + "\\" + firstFileName);
- }
- }
- using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(directoryName + "\\RaygunErrorMessage" + number + ".txt", FileMode.OpenOrCreate, FileAccess.Write, isolatedStorage))
- {
- using (StreamWriter writer = new StreamWriter(isoStream, Encoding.Unicode))
- {
- writer.Write(message);
- writer.Flush();
- writer.Close();
- }
- }
- System.Diagnostics.Trace.WriteLine("Saved message: " + "RaygunErrorMessage" + number + ".txt");
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Trace.WriteLine(string.Format("Error saving message to isolated storage {0}", ex.Message));
- }
- }
-
- private bool FileExists(string[] files, string fileName)
- {
- foreach (string str in files)
- {
- if (fileName.Equals(str))
- {
- return true;
- }
- }
- return false;
- }
-
- private static object _sendLock = new object();
-
- private void SendStoredMessages()
- {
- lock (_sendLock)
- {
- try
- {
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
- {
- string directoryName = "RaygunOfflineStorage";
- string[] directories = isolatedStorage.GetDirectoryNames("*");
- if (FileExists(directories, directoryName))
- {
- string[] fileNames = isolatedStorage.GetFileNames(directoryName + "\\*.txt");
- foreach (string name in fileNames)
- {
- IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(directoryName + "\\" + name, FileMode.Open, isolatedStorage);
- using (StreamReader reader = new StreamReader(isoFileStream))
- {
- string text = reader.ReadToEnd();
- try
- {
- Send(text);
- }
- catch
- {
- // If just one message fails to send, then don't delete the message, and don't attempt sending anymore until later.
- return;
- }
- System.Diagnostics.Debug.WriteLine("Sent " + name);
- }
- isolatedStorage.DeleteFile(directoryName + "\\" + name);
- }
- if (isolatedStorage.GetFileNames(directoryName + "\\*.txt").Length == 0)
- {
- System.Diagnostics.Debug.WriteLine("Successfully sent all pending messages");
- isolatedStorage.DeleteDirectory(directoryName);
- }
- }
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine(string.Format("Error sending stored messages to Raygun.io {0}", ex.Message));
- }
- }
- }
-
- private IsolatedStorageFile GetIsolatedStorageScope()
- {
- if (AppDomain.CurrentDomain != null && AppDomain.CurrentDomain.ActivationContext != null)
- {
- return IsolatedStorageFile.GetUserStoreForApplication();
- }
- else
- {
- return IsolatedStorageFile.GetUserStoreForAssembly();
- }
- }
}
}
diff --git a/Mindscape.Raygun4Net/RaygunClientBase.cs b/Mindscape.Raygun4Net/RaygunClientBase.cs
index 4bf0633c..54598f11 100644
--- a/Mindscape.Raygun4Net/RaygunClientBase.cs
+++ b/Mindscape.Raygun4Net/RaygunClientBase.cs
@@ -1,13 +1,27 @@
using System;
using System.Net;
+using Mindscape.Raygun4Net.Logging;
using Mindscape.Raygun4Net.Messages;
namespace Mindscape.Raygun4Net
{
public abstract class RaygunClientBase
{
+ private bool _handlingRecursiveErrorSending;
+ private bool _handlingRecursiveGrouping;
+
protected internal const string SentKey = "AlreadySentByRaygun";
+ ///
+ /// Raised just before a message is sent. This can be used to make final adjustments to the , or to cancel the send.
+ ///
+ public event EventHandler SendingMessage;
+
+ ///
+ /// Raised before a message is sent. This can be used to add a custom grouping key to a RaygunMessage before sending it to the Raygun service.
+ ///
+ public event EventHandler CustomGroupingKey;
+
///
/// Gets or sets the user identity string.
///
@@ -28,6 +42,19 @@ public abstract class RaygunClientBase
///
public string ApplicationVersion { get; set; }
+ ///
+ /// Transmits an exception to Raygun synchronously.
+ ///
+ /// The exception to deliver.
+ public abstract void Send(Exception exception);
+
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public abstract void Send(RaygunMessage raygunMessage);
+
protected virtual bool CanSend(Exception exception)
{
return exception == null || exception.Data == null || !exception.Data.Contains(SentKey) || false.Equals(exception.Data[SentKey]);
@@ -47,18 +74,11 @@ protected void FlagAsSent(Exception exception)
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine(String.Format("Failed to flag exception as sent: {0}", ex.Message));
+ RaygunLogger.Instance.Debug($"Failed to flag exception as sent: {ex.Message}");
}
}
}
- ///
- /// Raised just before a message is sent. This can be used to make final adjustments to the , or to cancel the send.
- ///
- public event EventHandler SendingMessage;
-
- private bool _handlingRecursiveErrorSending;
-
// Returns true if the message can be sent, false if the sending is canceled.
protected bool OnSendingMessage(RaygunMessage raygunMessage)
{
@@ -89,12 +109,6 @@ protected bool OnSendingMessage(RaygunMessage raygunMessage)
return result;
}
- ///
- /// Raised before a message is sent. This can be used to add a custom grouping key to a RaygunMessage before sending it to the Raygun service.
- ///
- public event EventHandler CustomGroupingKey;
-
- private bool _handlingRecursiveGrouping;
protected string OnCustomGroupingKey(Exception exception, RaygunMessage message)
{
string result = null;
@@ -119,18 +133,5 @@ protected string OnCustomGroupingKey(Exception exception, RaygunMessage message)
}
return result;
}
-
- ///
- /// Transmits an exception to Raygun.io synchronously.
- ///
- /// The exception to deliver.
- public abstract void Send(Exception exception);
-
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public abstract void Send(RaygunMessage raygunMessage);
}
}
diff --git a/Mindscape.Raygun4Net/RaygunMessageBuilder.cs b/Mindscape.Raygun4Net/RaygunMessageBuilder.cs
index b892265f..35b0f235 100644
--- a/Mindscape.Raygun4Net/RaygunMessageBuilder.cs
+++ b/Mindscape.Raygun4Net/RaygunMessageBuilder.cs
@@ -7,6 +7,7 @@
using System.Reflection;
using System.Web;
using Mindscape.Raygun4Net.Builders;
+using Mindscape.Raygun4Net.Logging;
using Mindscape.Raygun4Net.Messages;
namespace Mindscape.Raygun4Net
@@ -92,7 +93,7 @@ public IRaygunMessageBuilder SetExceptionDetails(Exception exception)
}
catch (Exception ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving response info {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving response info {ex.Message}");
}
return this;
diff --git a/Mindscape.Raygun4Net/RaygunSettings.cs b/Mindscape.Raygun4Net/RaygunSettings.cs
index e4474466..0f9c52d8 100644
--- a/Mindscape.Raygun4Net/RaygunSettings.cs
+++ b/Mindscape.Raygun4Net/RaygunSettings.cs
@@ -2,6 +2,7 @@
using System.Configuration;
using System.Linq;
using Mindscape.Raygun4Net.Breadcrumbs;
+using Mindscape.Raygun4Net.Logging;
namespace Mindscape.Raygun4Net
{
@@ -11,6 +12,8 @@ public class RaygunSettings : ConfigurationSection
private const string DefaultApiEndPoint = "https://api.raygun.com/entries";
+ public const int MaxCrashReportsStoredOfflineHardLimit = 64;
+
public static RaygunSettings Settings
{
get { return settings; }
@@ -170,6 +173,42 @@ public string ApplicationIdentifier
set { this["applicationIdentifier"] = value; }
}
+ ///
+ /// Gets or sets the max crash reports stored on the device.
+ /// There is a hard upper limit of 64 reports.
+ ///
+ /// The max crash reports stored on device.
+ [ConfigurationProperty("maxCrashReportsStoredOffline", IsRequired = false, DefaultValue = MaxCrashReportsStoredOfflineHardLimit)]
+ public int MaxCrashReportsStoredOffline
+ {
+ get { return (int)this["maxCrashReportsStoredOffline"]; }
+ set { this["maxCrashReportsStoredOffline"] = value; }
+ }
+
+ ///
+ /// Allows for crash reports to be stored to local storage when there is no available network connection.
+ ///
+ /// true if allowing crash reports to be stored offline; otherwise, false.
+ [ConfigurationProperty("crashReportingOfflineStorageEnabled", IsRequired = false, DefaultValue = true)]
+ public bool CrashReportingOfflineStorageEnabled
+ {
+ get { return (bool)this["crashReportingOfflineStorageEnabled"]; }
+ set { this["crashReportingOfflineStorageEnabled"] = value; }
+ }
+
+ ///
+ /// Gets or sets the log level controlling the amount of information printed to system consoles.
+ /// Setting the level to will print the raw Crash Reporting being
+ /// posted to the API endpoints.
+ ///
+ /// The log level.
+ [ConfigurationProperty("logLevel", IsRequired = false, DefaultValue = RaygunLogLevel.Warning)]
+ public RaygunLogLevel LogLevel
+ {
+ get { return (RaygunLogLevel)this["logLevel"]; }
+ set { this["logLevel"] = value; }
+ }
+
///
/// Return false.
///
diff --git a/Mindscape.Raygun4Net/Storage/IRaygunFile.cs b/Mindscape.Raygun4Net/Storage/IRaygunFile.cs
new file mode 100644
index 00000000..8b2a1105
--- /dev/null
+++ b/Mindscape.Raygun4Net/Storage/IRaygunFile.cs
@@ -0,0 +1,15 @@
+namespace Mindscape.Raygun4Net.Storage
+{
+ public interface IRaygunFile
+ {
+ ///
+ /// The filename as seen in local storage.
+ ///
+ string Name { get; }
+
+ ///
+ /// The raw data stored locally.
+ ///
+ string Contents { get; }
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Storage/IRaygunOfflineStorage.cs b/Mindscape.Raygun4Net/Storage/IRaygunOfflineStorage.cs
new file mode 100644
index 00000000..51c9f5f6
--- /dev/null
+++ b/Mindscape.Raygun4Net/Storage/IRaygunOfflineStorage.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace Mindscape.Raygun4Net.Storage
+{
+ public interface IRaygunOfflineStorage
+ {
+ ///
+ /// Persist the > to local storage.
+ /// Messages must be saved to a location unique per its API key.
+ /// This is to ensure messages are not sent using the wrong API key.
+ ///
+ /// The serialized error report to store locally.
+ /// The key for which these file are associated with.
+ ///
+ bool Store(string message, string apiKey);
+
+ ///
+ /// Retrieve all files from local storage.
+ ///
+ /// The key for which these file are associated with.
+ /// A container of files that are currently stored locally.
+ IList FetchAll(string apiKey);
+
+ ///
+ /// Delete a file from local storage that has the following .
+ ///
+ /// The filename of the local file.
+ /// The key for which these file are associated with.
+ ///
+ bool Remove(string name, string apiKey);
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Storage/IsolatedRaygunOfflineStorage.cs b/Mindscape.Raygun4Net/Storage/IsolatedRaygunOfflineStorage.cs
new file mode 100644
index 00000000..f426e4ef
--- /dev/null
+++ b/Mindscape.Raygun4Net/Storage/IsolatedRaygunOfflineStorage.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Mindscape.Raygun4Net.Storage
+{
+ public class IsolatedRaygunOfflineStorage : IRaygunOfflineStorage
+ {
+ private string _folderNameHash = null;
+ private const string RaygunBaseDirectory = "Raygun";
+ private const string RaygunFileFormat = ".json";
+
+ private int _currentFileCounter = 0;
+
+ public bool Store(string message, string apiKey)
+ {
+ // Do not store invalid messages.
+ if (string.IsNullOrEmpty(message) || string.IsNullOrEmpty(apiKey))
+ {
+ return false;
+ }
+
+ using (var storage = GetIsolatedStorageScope())
+ {
+ // Get the directory within isolated storage to hold our data.
+ var localDirectory = GetLocalDirectory(apiKey);
+
+ if (string.IsNullOrEmpty(localDirectory))
+ {
+ return false;
+ }
+
+ // Create the destination if it's not there.
+ if (!EnsureDirectoryExists(storage, localDirectory))
+ {
+ return false;
+ }
+
+ var searchPattern = Path.Combine(localDirectory, $"*{RaygunFileFormat}");
+ var maxReports = Math.Min(RaygunSettings.Settings.MaxCrashReportsStoredOffline, RaygunSettings.MaxCrashReportsStoredOfflineHardLimit);
+
+ // We can only save the report if we have not reached the report count limit.
+ if (storage.GetFileNames(searchPattern).Length >= maxReports)
+ {
+ return false;
+ }
+
+ // Build up our file information.
+ var filename = GetUniqueAscendingJsonName();
+ var localFilePath = Path.Combine(localDirectory, filename);
+
+ // Write the contents to storage.
+ using (var stream = new IsolatedStorageFileStream(localFilePath, FileMode.OpenOrCreate, FileAccess.Write, storage))
+ {
+ using (var writer = new StreamWriter(stream, Encoding.Unicode))
+ {
+ writer.Write(message);
+ writer.Flush();
+ writer.Close();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private bool EnsureDirectoryExists(IsolatedStorageFile storage, string localDirectory)
+ {
+ bool success = true;
+
+ try
+ {
+ storage.CreateDirectory(localDirectory);
+ }
+ catch
+ {
+ success = false;
+ }
+
+ return success;
+ }
+
+ public IList FetchAll(string apiKey)
+ {
+ var files = new List();
+
+ if (string.IsNullOrEmpty(apiKey))
+ {
+ return files;
+ }
+
+ using (var storage = GetIsolatedStorageScope())
+ {
+ // Get the directory within isolated storage to hold our data.
+ var localDirectory = GetLocalDirectory(apiKey);
+
+ if (string.IsNullOrEmpty(localDirectory))
+ {
+ return files;
+ }
+
+ // We must ensure the local directory exists before we look for files..
+ if (!EnsureDirectoryExists(storage, localDirectory))
+ {
+ return files;
+ }
+
+ // Look for all the files within our working local directory.
+ var fileNames = storage.GetFileNames(Path.Combine(localDirectory, $"*{RaygunFileFormat}"));
+
+ // Take action on each file.
+ foreach (var name in fileNames)
+ {
+ var stream = new IsolatedStorageFileStream(Path.Combine(localDirectory, name), FileMode.Open, storage);
+
+ // Read the contents and put it into our own structure.
+ using (var reader = new StreamReader(stream))
+ {
+ string contents = reader.ReadToEnd();
+
+ files.Add(new RaygunFile()
+ {
+ Name = name,
+ Contents = contents
+ });
+ }
+ }
+ }
+
+ return files;
+ }
+
+ public bool Remove(string name, string apiKey)
+ {
+ // We cannot remove based on invalid params.
+ if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(apiKey))
+ {
+ return false;
+ }
+
+ using (var storage = GetIsolatedStorageScope())
+ {
+ // Get a list of the current files in storage.
+ var localDirectory = GetLocalDirectory(apiKey);
+
+ if (string.IsNullOrEmpty(localDirectory))
+ {
+ return false;
+ }
+
+ var localFilePath = Path.Combine(localDirectory, name);
+
+ // We must ensure the local file exists before delete it.
+ if (storage.GetFileNames(localFilePath)?.Length == 0)
+ {
+ return false;
+ }
+
+ storage.DeleteFile(localFilePath);
+
+ return true;
+ }
+ }
+
+ private IsolatedStorageFile GetIsolatedStorageScope()
+ {
+ return AppDomain.CurrentDomain?.ActivationContext != null ?
+ IsolatedStorageFile.GetUserStoreForApplication() :
+ IsolatedStorageFile.GetUserStoreForAssembly();
+ }
+
+ private string GetLocalDirectory(string apiKey)
+ {
+ // Attempt to perform the hash operation once.
+ if (string.IsNullOrEmpty(_folderNameHash))
+ {
+ _folderNameHash = PerformHash(apiKey);
+ }
+
+ // If successful return the correct path.
+ return (_folderNameHash == null) ? null : Path.Combine(RaygunBaseDirectory, _folderNameHash);
+ }
+
+ private string PerformHash(string input)
+ {
+ // Use input string to calculate MD5 hash
+ using (var hasher = MD5.Create())
+ {
+ var inputBytes = Encoding.ASCII.GetBytes(input);
+ var hashBytes = hasher.ComputeHash(inputBytes);
+
+ // Convert the byte array to hexadecimal string
+ var builder = new StringBuilder();
+
+ foreach (var hashByte in hashBytes)
+ {
+ builder.Append(hashByte.ToString("X2"));
+ }
+
+ return builder.ToString();
+ }
+ }
+
+ private string GetUniqueAscendingJsonName()
+ {
+ return $"{DateTime.UtcNow.Ticks}-{_currentFileCounter++}-{Guid.NewGuid().ToString()}{RaygunFileFormat}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Storage/RaygunFile.cs b/Mindscape.Raygun4Net/Storage/RaygunFile.cs
new file mode 100644
index 00000000..8adc537d
--- /dev/null
+++ b/Mindscape.Raygun4Net/Storage/RaygunFile.cs
@@ -0,0 +1,8 @@
+namespace Mindscape.Raygun4Net.Storage
+{
+ public class RaygunFile : IRaygunFile
+ {
+ public string Name { get; set; }
+ public string Contents { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net/Utils/Singleton.cs b/Mindscape.Raygun4Net/Utils/Singleton.cs
new file mode 100644
index 00000000..1c51a90a
--- /dev/null
+++ b/Mindscape.Raygun4Net/Utils/Singleton.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Mindscape.Raygun4Net.Utils
+{
+ public abstract class Singleton where T : class
+ {
+ private static T _instance;
+ private static object _lock = new object();
+
+ protected Singleton() { }
+
+ public static T Instance
+ {
+ get
+ {
+ lock (_lock)
+ {
+ if (_instance == null)
+ {
+ _instance = Activator.CreateInstance();
+ }
+
+ return _instance;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net2.Tests/RaygunSettingsTests.cs b/Mindscape.Raygun4Net2.Tests/RaygunSettingsTests.cs
index ece22783..357c7a00 100644
--- a/Mindscape.Raygun4Net2.Tests/RaygunSettingsTests.cs
+++ b/Mindscape.Raygun4Net2.Tests/RaygunSettingsTests.cs
@@ -18,7 +18,7 @@ public void Apikey_EmptyByDefault()
[Test]
public void ApiEndPoint_DefaultValue()
{
- Assert.AreEqual("https://api.raygun.io/entries", RaygunSettings.Settings.ApiEndpoint.AbsoluteUri);
+ Assert.AreEqual("https://api.raygun.com/entries", RaygunSettings.Settings.ApiEndpoint.AbsoluteUri);
}
[Test]
diff --git a/Mindscape.Raygun4Net2/Builders/RaygunEnvironmentMessageBuilder.cs b/Mindscape.Raygun4Net2/Builders/RaygunEnvironmentMessageBuilder.cs
index a2e1daa7..21341459 100644
--- a/Mindscape.Raygun4Net2/Builders/RaygunEnvironmentMessageBuilder.cs
+++ b/Mindscape.Raygun4Net2/Builders/RaygunEnvironmentMessageBuilder.cs
@@ -19,7 +19,7 @@ public static RaygunEnvironmentMessage Build()
// For now if they fail to load for whatever reason then just
// swallow the exception. A good addition would be to handle
// these cases and load them correctly depending on where its running.
- // see http://raygun.io/forums/thread/3655
+ // see http://raygun.com/forums/thread/3655
try
{
diff --git a/Mindscape.Raygun4Net2/Mindscape.Raygun4Net2.csproj b/Mindscape.Raygun4Net2/Mindscape.Raygun4Net2.csproj
index d1f5c5c8..5dabe592 100644
--- a/Mindscape.Raygun4Net2/Mindscape.Raygun4Net2.csproj
+++ b/Mindscape.Raygun4Net2/Mindscape.Raygun4Net2.csproj
@@ -69,6 +69,15 @@
IRaygunMessageBuilder.cs
+
+ Logging\IRaygunLogger.cs
+
+
+ Logging\RaygunLogger.cs
+
+
+ Logging\RaygunLogLevel.cs
+
Messages\RaygunClientMessage.cs
@@ -156,6 +165,21 @@
SimpleJson.cs
+
+ Storage\IRaygunFile.cs
+
+
+ Storage\IRaygunOfflineStorage.cs
+
+
+ Storage\RaygunFile.cs
+
+
+ Storage\IsolatedRaygunOfflineStorage.cs
+
+
+ Utils\Singleton.cs
+
diff --git a/Mindscape.Raygun4Net2/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net2/Properties/AssemblyInfo.cs
index 1501c9dd..a4d5d7d4 100644
--- a/Mindscape.Raygun4Net2/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net2/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net2.0")]
@@ -10,12 +9,12 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Raygun")]
[assembly: AssemblyProduct("Raygun4Net")]
-[assembly: AssemblyCopyright("Copyright © Raygun 2014-2019")]
+[assembly: AssemblyCopyright("Copyright © Raygun 2014-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net2/RaygunClient.cs b/Mindscape.Raygun4Net2/RaygunClient.cs
index 2f7bed78..c65a3e1a 100644
--- a/Mindscape.Raygun4Net2/RaygunClient.cs
+++ b/Mindscape.Raygun4Net2/RaygunClient.cs
@@ -3,22 +3,35 @@
using System.Collections.Generic;
using System.Net;
using System.Reflection;
-using System.Text;
using System.Threading;
using System.Web;
using Mindscape.Raygun4Net.Builders;
+using Mindscape.Raygun4Net.Logging;
using Mindscape.Raygun4Net.Messages;
+using Mindscape.Raygun4Net.Storage;
namespace Mindscape.Raygun4Net
{
public class RaygunClient : RaygunClientBase
{
+ [ThreadStatic]
+ private static RaygunRequestMessage _currentRequestMessage;
+ private static object _sendLock = new object();
+
private readonly string _apiKey;
private readonly RaygunRequestMessageOptions _requestMessageOptions = new RaygunRequestMessageOptions();
private readonly List _wrapperExceptions = new List();
- [ThreadStatic]
- private static RaygunRequestMessage _currentRequestMessage;
+ private IRaygunOfflineStorage _offlineStorage = new IsolatedRaygunOfflineStorage();
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Uses the ApiKey specified in the config file.
+ ///
+ public RaygunClient()
+ : this(RaygunSettings.Settings.ApiKey)
+ {
+ }
///
/// Initializes a new instance of the class.
@@ -36,41 +49,28 @@ public RaygunClient(string apiKey)
var ignoredNames = RaygunSettings.Settings.IgnoreFormFieldNames.Split(',');
IgnoreFormFieldNames(ignoredNames);
}
+
if (!string.IsNullOrEmpty(RaygunSettings.Settings.IgnoreHeaderNames))
{
var ignoredNames = RaygunSettings.Settings.IgnoreHeaderNames.Split(',');
IgnoreHeaderNames(ignoredNames);
}
+
if (!string.IsNullOrEmpty(RaygunSettings.Settings.IgnoreCookieNames))
{
var ignoredNames = RaygunSettings.Settings.IgnoreCookieNames.Split(',');
IgnoreCookieNames(ignoredNames);
}
+
if (!string.IsNullOrEmpty(RaygunSettings.Settings.IgnoreServerVariableNames))
{
var ignoredNames = RaygunSettings.Settings.IgnoreServerVariableNames.Split(',');
IgnoreServerVariableNames(ignoredNames);
}
- IsRawDataIgnored = RaygunSettings.Settings.IsRawDataIgnored;
- }
- ///
- /// Initializes a new instance of the class.
- /// Uses the ApiKey specified in the config file.
- ///
- public RaygunClient()
- : this(RaygunSettings.Settings.ApiKey)
- {
- }
+ IsRawDataIgnored = RaygunSettings.Settings.IsRawDataIgnored;
- protected bool ValidateApiKey()
- {
- if (string.IsNullOrEmpty(_apiKey))
- {
- System.Diagnostics.Debug.WriteLine("ApiKey has not been provided, exception will not be logged");
- return false;
- }
- return true;
+ ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
}
///
@@ -104,6 +104,8 @@ public void RemoveWrapperExceptions(params Type[] wrapperExceptions)
}
}
+ #region Message Scrubbing Properties
+
///
/// Adds a list of keys to ignore when attaching the Form data of an HTTP POST request. This allows
/// you to remove sensitive data from the transmitted copy of the Form on the HttpRequest by specifying the keys you want removed.
@@ -149,8 +151,8 @@ public void IgnoreServerVariableNames(params string[] names)
}
///
- /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.io.
- /// The default is false which means RawData will be sent to Raygun.io.
+ /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.
+ /// The default is false which means RawData will be sent to Raygun.
///
public bool IsRawDataIgnored
{
@@ -161,17 +163,12 @@ public bool IsRawDataIgnored
}
}
- protected override bool CanSend(Exception exception)
- {
- if (RaygunSettings.Settings.ExcludeErrorsFromLocal && HttpContext.Current != null && HttpContext.Current.Request.IsLocal)
- {
- return false;
- }
- return base.CanSend(exception);
- }
+ #endregion // Message Scrubbing Properties
+
+ #region Message Send Methods
///
- /// Transmits an exception to Raygun.io synchronously.
+ /// Transmits an exception to Raygun synchronously.
///
/// The exception to deliver.
public override void Send(Exception exception)
@@ -180,7 +177,7 @@ public override void Send(Exception exception)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification.
///
/// The exception to deliver.
@@ -191,7 +188,7 @@ public void Send(Exception exception, IList tags)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
///
/// The exception to deliver.
@@ -205,7 +202,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The exception to deliver.
public void SendInBackground(Exception exception)
@@ -214,7 +211,7 @@ public void SendInBackground(Exception exception)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -224,7 +221,7 @@ public void SendInBackground(Exception exception, IList tags)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -243,7 +240,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The RaygunMessage to send. This needs its OccurredOn property
/// set to a valid DateTime and as much of the Details property as is available.
@@ -252,6 +249,93 @@ public void SendInBackground(RaygunMessage raygunMessage)
ThreadPool.QueueUserWorkItem(c => Send(raygunMessage));
}
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public override void Send(RaygunMessage raygunMessage)
+ {
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send error report due to invalid API key");
+ return;
+ }
+
+ bool canSend = OnSendingMessage(raygunMessage);
+
+ if (!canSend)
+ {
+ return;
+ }
+
+ string message = null;
+
+ try
+ {
+ message = SimpleJson.SerializeObject(raygunMessage);
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to serialize report due to: {ex.Message}");
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ bool successfullySentReport = true;
+
+ try
+ {
+ Send(message);
+ }
+ catch (Exception ex)
+ {
+ successfullySentReport = false;
+
+ RaygunLogger.Instance.Error($"Failed to send report to Raygun due to: {ex.Message}");
+
+ SaveMessage(message);
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (successfullySentReport)
+ {
+ SendStoredMessages();
+ }
+ }
+
+ private void Send(string message)
+ {
+ RaygunLogger.Instance.Verbose("Sending Payload --------------");
+ RaygunLogger.Instance.Verbose(message);
+ RaygunLogger.Instance.Verbose("------------------------------");
+
+ using (var client = new WebClient())
+ {
+ client.Headers.Add("X-ApiKey", _apiKey);
+ client.Headers.Add("content-type", "application/json; charset=utf-8");
+ client.Encoding = System.Text.Encoding.UTF8;
+
+ client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
+ }
+ }
+
+ #endregion // Message Send Methods
+
+ #region Message Building Methods
+
private RaygunRequestMessage BuildRequestMessage()
{
RaygunRequestMessage requestMessage = null;
@@ -315,56 +399,122 @@ private Exception StripWrapperExceptions(Exception exception)
return exception;
}
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public override void Send(RaygunMessage raygunMessage)
+ #endregion // Message Building Methods
+
+ #region Message Offline Storage
+
+ private void SaveMessage(string message)
{
- if (ValidateApiKey())
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
{
- bool canSend = OnSendingMessage(raygunMessage);
- if (canSend)
- {
- string message = null;
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping saving report.");
+ return;
+ }
- try
- {
- message = SimpleJson.SerializeObject(raygunMessage);
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine(string.Format("Error serializing raygun message: {0}", ex.Message));
- }
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to save report due to invalid API key.");
+ return;
+ }
- if (message != null)
+ // Avoid writing and reading from disk at the same time with `SendStoredMessages`.
+ lock (_sendLock)
+ {
+ try
+ {
+ if (!_offlineStorage.Store(message, _apiKey))
{
- SendMessage(message);
+ RaygunLogger.Instance.Warning("Failed to save report to offline storage");
}
}
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to save report to offline storage due to: {ex.Message}");
+ }
}
}
- private bool SendMessage(string message)
+ private void SendStoredMessages()
{
- using (var client = new WebClient())
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
{
- client.Headers.Add("X-ApiKey", _apiKey);
- client.Headers.Add("content-type", "application/json; charset=utf-8");
- client.Encoding = System.Text.Encoding.UTF8;
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping sending stored reports.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send offline reports due to invalid API key.");
+ return;
+ }
+ lock (_sendLock)
+ {
try
{
- client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
+ var files = _offlineStorage.FetchAll(_apiKey);
+
+ foreach (var file in files)
+ {
+ try
+ {
+ // Send the stored report.
+ Send(file.Contents);
+
+ // Remove the stored report from local storage.
+ if (_offlineStorage.Remove(file.Name, _apiKey))
+ {
+ RaygunLogger.Instance.Info("Successfully removed report from offline storage.");
+ }
+ else
+ {
+ RaygunLogger.Instance.Warning("Failed to remove report from offline storage.");
+ }
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+
+ // If just one message fails to send, then don't delete the message,
+ // and don't attempt sending anymore until later.
+ return;
+ }
+ }
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine(string.Format("Error Logging Exception to Raygun.io {0}", ex.Message));
- return false;
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+ }
+ }
+ }
+
+ #endregion // Message Offline Storage
+
+ protected override bool CanSend(Exception exception)
+ {
+ if (RaygunSettings.Settings.ExcludeErrorsFromLocal && HttpContext.Current != null)
+ {
+ try
+ {
+ if (HttpContext.Current.Request.IsLocal)
+ {
+ return false;
+ }
+ }
+ catch
+ {
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
}
}
- return true;
+ return base.CanSend(exception);
+ }
+
+ protected bool ValidateApiKey()
+ {
+ return !string.IsNullOrEmpty(_apiKey);
}
}
}
diff --git a/Mindscape.Raygun4Net2/RaygunSettings.cs b/Mindscape.Raygun4Net2/RaygunSettings.cs
index f83622d5..61040825 100644
--- a/Mindscape.Raygun4Net2/RaygunSettings.cs
+++ b/Mindscape.Raygun4Net2/RaygunSettings.cs
@@ -1,5 +1,6 @@
using System;
using System.Configuration;
+using Mindscape.Raygun4Net.Logging;
namespace Mindscape.Raygun4Net
{
@@ -7,7 +8,9 @@ public class RaygunSettings : ConfigurationSection
{
private static readonly RaygunSettings settings = ConfigurationManager.GetSection("RaygunSettings") as RaygunSettings ?? new RaygunSettings();
- private const string DefaultApiEndPoint = "https://api.raygun.io/entries";
+ private const string DefaultApiEndPoint = "https://api.raygun.com/entries";
+
+ public const int MaxCrashReportsStoredOfflineHardLimit = 64;
public static RaygunSettings Settings
{
@@ -99,5 +102,41 @@ public string ApplicationIdentifier
get { return (string)this["applicationIdentifier"]; }
set { this["applicationIdentifier"] = value; }
}
+
+ ///
+ /// Gets or sets the max crash reports stored on the device.
+ /// There is a hard upper limit of 64 reports.
+ ///
+ /// The max crash reports stored on device.
+ [ConfigurationProperty("maxCrashReportsStoredOffline", IsRequired = false, DefaultValue = MaxCrashReportsStoredOfflineHardLimit)]
+ public int MaxCrashReportsStoredOffline
+ {
+ get { return (int)this["maxCrashReportsStoredOffline"]; }
+ set { this["maxCrashReportsStoredOffline"] = value; }
+ }
+
+ ///
+ /// Allows for crash reports to be stored to local storage when there is no available network connection.
+ ///
+ /// true if allowing crash reports to be stored offline; otherwise, false.
+ [ConfigurationProperty("crashReportingOfflineStorageEnabled", IsRequired = false, DefaultValue = true)]
+ public bool CrashReportingOfflineStorageEnabled
+ {
+ get { return (bool)this["crashReportingOfflineStorageEnabled"]; }
+ set { this["crashReportingOfflineStorageEnabled"] = value; }
+ }
+
+ ///
+ /// Gets or sets the log level controlling the amount of information printed to system consoles.
+ /// Setting the level to will print the raw Crash Reporting being
+ /// posted to the API endpoints.
+ ///
+ /// The log level.
+ [ConfigurationProperty("logLevel", IsRequired = false, DefaultValue = RaygunLogLevel.Warning)]
+ public RaygunLogLevel LogLevel
+ {
+ get { return (RaygunLogLevel)this["logLevel"]; }
+ set { this["logLevel"] = value; }
+ }
}
}
diff --git a/Mindscape.Raygun4Net4.ClientProfile/Mindscape.Raygun4Net4.ClientProfile.csproj b/Mindscape.Raygun4Net4.ClientProfile/Mindscape.Raygun4Net4.ClientProfile.csproj
index cb6eb7a2..342f95cb 100644
--- a/Mindscape.Raygun4Net4.ClientProfile/Mindscape.Raygun4Net4.ClientProfile.csproj
+++ b/Mindscape.Raygun4Net4.ClientProfile/Mindscape.Raygun4Net4.ClientProfile.csproj
@@ -91,6 +91,15 @@
IRaygunMessageBuilder.cs
+
+ Logging\IRaygunLogger.cs
+
+
+ Logging\RaygunLogger.cs
+
+
+ Logging\RaygunLogLevel.cs
+
Messages\RaygunClientMessage.cs
@@ -118,6 +127,21 @@
SimpleJson.cs
+
+ Storage\IRaygunFile.cs
+
+
+ Storage\IRaygunOfflineStorage.cs
+
+
+ Storage\RaygunFile.cs
+
+
+ Storage\IsolatedRaygunOfflineStorage.cs
+
+
+ Utils\Singleton.cs
+
diff --git a/Mindscape.Raygun4Net4.ClientProfile/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net4.ClientProfile/Properties/AssemblyInfo.cs
index 7a6f228d..133f707d 100644
--- a/Mindscape.Raygun4Net4.ClientProfile/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net4.ClientProfile/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net4.ClientProfile")]
@@ -10,12 +9,12 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Raygun")]
[assembly: AssemblyProduct("Raygun4Net")]
-[assembly: AssemblyCopyright("Copyright © Raygun 2015-2019")]
+[assembly: AssemblyCopyright("Copyright © Raygun 2015-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net4.ClientProfile/RaygunClient.cs b/Mindscape.Raygun4Net4.ClientProfile/RaygunClient.cs
index b708d266..68b1013b 100644
--- a/Mindscape.Raygun4Net4.ClientProfile/RaygunClient.cs
+++ b/Mindscape.Raygun4Net4.ClientProfile/RaygunClient.cs
@@ -3,34 +3,33 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using Mindscape.Raygun4Net.Messages;
-
using System.Threading;
using System.Reflection;
-using Mindscape.Raygun4Net.Builders;
-using System.IO;
-using System.IO.IsolatedStorage;
-using System.Text;
+using Mindscape.Raygun4Net.Messages;
+using Mindscape.Raygun4Net.Logging;
+using Mindscape.Raygun4Net.Storage;
namespace Mindscape.Raygun4Net
{
public class RaygunClient : RaygunClientBase
{
+ private static object _sendLock = new object();
+
private readonly string _apiKey;
private readonly List _wrapperExceptions = new List();
+ private IRaygunOfflineStorage _offlineStorage = new IsolatedRaygunOfflineStorage();
+
///
- /// Initializes a new instance of the class.
+ /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
+ /// and requires credentials.
///
- /// The API key.
- public RaygunClient(string apiKey)
- {
- _apiKey = apiKey;
-
- _wrapperExceptions.Add(typeof(TargetInvocationException));
+ public ICredentials ProxyCredentials { get; set; }
- ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
- }
+ ///
+ /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
+ ///
+ public IWebProxy WebProxy { get; set; }
///
/// Initializes a new instance of the class.
@@ -41,26 +40,18 @@ public RaygunClient()
{
}
- protected bool ValidateApiKey()
- {
- if (string.IsNullOrEmpty(_apiKey))
- {
- System.Diagnostics.Debug.WriteLine("ApiKey has not been provided, exception will not be logged");
- return false;
- }
- return true;
- }
-
///
- /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
- /// and requires credentials.
+ /// Initializes a new instance of the class.
///
- public ICredentials ProxyCredentials { get; set; }
+ /// The API key.
+ public RaygunClient(string apiKey)
+ {
+ _apiKey = apiKey;
- ///
- /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
- ///
- public IWebProxy WebProxy { get; set; }
+ _wrapperExceptions.Add(typeof(TargetInvocationException));
+
+ ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
+ }
///
/// Adds a list of outer exceptions that will be stripped, leaving only the valuable inner exception.
@@ -94,8 +85,10 @@ public void RemoveWrapperExceptions(params Type[] wrapperExceptions)
}
}
+ #region Message Send Methods
+
///
- /// Transmits an exception to Raygun.io synchronously, using the version number of the originating assembly.
+ /// Transmits an exception to Raygun synchronously, using the version number of the originating assembly.
///
/// The exception to deliver.
public override void Send(Exception exception)
@@ -104,7 +97,7 @@ public override void Send(Exception exception)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification. This uses the version number of the originating assembly.
///
/// The exception to deliver.
@@ -115,7 +108,7 @@ public void Send(Exception exception, IList tags)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -128,7 +121,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -146,7 +139,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The exception to deliver.
public void SendInBackground(Exception exception)
@@ -155,7 +148,7 @@ public void SendInBackground(Exception exception)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -165,7 +158,7 @@ public void SendInBackground(Exception exception, IList tags)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -176,7 +169,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -208,7 +201,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The RaygunMessage to send. This needs its OccurredOn property
/// set to a valid DateTime and as much of the Details property as is available.
@@ -217,6 +210,97 @@ public void SendInBackground(RaygunMessage raygunMessage)
ThreadPool.QueueUserWorkItem(c => Send(raygunMessage));
}
+ private void StripAndSend(Exception exception, IList tags, IDictionary userCustomData, RaygunIdentifierMessage userInfo, DateTime? currentTime)
+ {
+ foreach (Exception e in StripWrapperExceptions(exception))
+ {
+ Send(BuildMessage(e, tags, userCustomData, userInfo, currentTime));
+ }
+ }
+
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public override void Send(RaygunMessage raygunMessage)
+ {
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send error report due to invalid API key.");
+ return;
+ }
+
+ bool canSend = OnSendingMessage(raygunMessage);
+
+ if (!canSend)
+ {
+ return;
+ }
+
+ string message = null;
+
+ try
+ {
+ message = SimpleJson.SerializeObject(raygunMessage);
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to serialize report due to: {ex.Message}");
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ bool successfullySentReport = true;
+
+ try
+ {
+ Send(message);
+ }
+ catch (Exception ex)
+ {
+ successfullySentReport = false;
+
+ RaygunLogger.Instance.Error($"Failed to send report to Raygun due to: {ex.Message}");
+
+ SaveMessage(message);
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (successfullySentReport)
+ {
+ SendStoredMessages();
+ }
+ }
+
+ private void Send(string message)
+ {
+ RaygunLogger.Instance.Verbose("Sending Payload --------------");
+ RaygunLogger.Instance.Verbose(message);
+ RaygunLogger.Instance.Verbose("------------------------------");
+
+ using (var client = CreateWebClient())
+ {
+ client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
+ }
+ }
+
+ #endregion // Message Send Methods
+
+ #region Message Building Methods
+
protected RaygunMessage BuildMessage(Exception exception, IList tags, IDictionary userCustomData)
{
return BuildMessage(exception, tags, userCustomData, null, null);
@@ -243,14 +327,6 @@ protected RaygunMessage BuildMessage(Exception exception, IList tags, ID
return message;
}
- private void StripAndSend(Exception exception, IList tags, IDictionary userCustomData, RaygunIdentifierMessage userInfo, DateTime? currentTime)
- {
- foreach (Exception e in StripWrapperExceptions(exception))
- {
- Send(BuildMessage(e, tags, userCustomData, userInfo, currentTime));
- }
- }
-
protected IEnumerable StripWrapperExceptions(Exception exception)
{
if (exception != null && _wrapperExceptions.Any(wrapperException => exception.GetType() == wrapperException && exception.InnerException != null))
@@ -280,62 +356,100 @@ protected IEnumerable StripWrapperExceptions(Exception exception)
}
}
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public override void Send(RaygunMessage raygunMessage)
+ #endregion // Message Building Methods
+
+ #region Message Offline Storage
+
+ private void SaveMessage(string message)
{
- bool canSend = OnSendingMessage(raygunMessage);
- if (canSend)
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping saving report.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to save report due to invalid API key.");
+ return;
+ }
+
+ // Avoid writing and reading from disk at the same time with `SendStoredMessages`.
+ lock (_sendLock)
{
- string message = null;
try
{
- message = SimpleJson.SerializeObject(raygunMessage);
+ if (!_offlineStorage.Store(message, _apiKey))
+ {
+ RaygunLogger.Instance.Warning("Failed to save report to offline storage.");
+ }
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine(string.Format("Error serializing exception {0}", ex.Message));
-
- if (RaygunSettings.Settings.ThrowOnError)
- {
- throw;
- }
+ RaygunLogger.Instance.Error($"Failed to save report to offline storage due to: {ex.Message}");
}
+ }
+ }
- if (message != null)
+ private void SendStoredMessages()
+ {
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping sending stored reports.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send offline reports due to invalid API key.");
+ return;
+ }
+
+ lock (_sendLock)
+ {
+ try
{
- try
- {
- Send(message);
- }
- catch (Exception ex)
+ var files = _offlineStorage.FetchAll(_apiKey);
+
+ foreach (var file in files)
{
- SaveMessage(message);
- System.Diagnostics.Debug.WriteLine(string.Format("Error Logging Exception to Raygun.io {0}", ex.Message));
+ try
+ {
+ // Send the stored report.
+ Send(file.Contents);
- if (RaygunSettings.Settings.ThrowOnError)
+ // Remove the stored report from local storage.
+ if (_offlineStorage.Remove(file.Name, _apiKey))
+ {
+ RaygunLogger.Instance.Info("Successfully removed report from offline storage.");
+ }
+ else
+ {
+ RaygunLogger.Instance.Warning("Failed to remove report from offline storage.");
+ }
+ }
+ catch (Exception ex)
{
- throw;
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+
+ // If just one message fails to send, then don't delete the message,
+ // and don't attempt sending anymore until later.
+ return;
}
}
-
- SendStoredMessages();
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
}
}
}
- private void Send(string message)
+ #endregion // Message Offline Storage
+
+ protected bool ValidateApiKey()
{
- if (ValidateApiKey())
- {
- using (var client = CreateWebClient())
- {
- client.UploadString(RaygunSettings.Settings.ApiEndpoint, message);
- }
- }
+ return !string.IsNullOrEmpty(_apiKey);
}
protected WebClient CreateWebClient()
@@ -371,120 +485,5 @@ protected WebClient CreateWebClient()
}
return client;
}
-
- private void SaveMessage(string message)
- {
- try
- {
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
- {
- string directoryName = "RaygunOfflineStorage";
- if (!isolatedStorage.DirectoryExists(directoryName))
- {
- isolatedStorage.CreateDirectory(directoryName);
- }
-
- int number = 1;
- while (true)
- {
- bool exists = isolatedStorage.FileExists(directoryName + "\\RaygunErrorMessage" + number + ".txt");
- if (!exists)
- {
- string nextFileName = directoryName + "\\RaygunErrorMessage" + (number + 1) + ".txt";
- exists = isolatedStorage.FileExists(nextFileName);
- if (exists)
- {
- isolatedStorage.DeleteFile(nextFileName);
- }
- break;
- }
- number++;
- }
-
- if (number == 11)
- {
- string firstFileName = directoryName + "\\RaygunErrorMessage1.txt";
- if (isolatedStorage.FileExists(firstFileName))
- {
- isolatedStorage.DeleteFile(firstFileName);
- }
- }
- using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(directoryName + "\\RaygunErrorMessage" + number + ".txt", FileMode.OpenOrCreate, FileAccess.Write, isolatedStorage))
- {
- using (StreamWriter writer = new StreamWriter(isoStream, Encoding.Unicode))
- {
- writer.Write(message);
- writer.Flush();
- writer.Close();
- }
- }
- System.Diagnostics.Trace.WriteLine("Saved message: " + "RaygunErrorMessage" + number + ".txt");
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Trace.WriteLine(string.Format("Error saving message to isolated storage {0}", ex.Message));
- }
- }
-
- private static object _sendLock = new object();
-
- private void SendStoredMessages()
- {
- lock (_sendLock)
- {
- try
- {
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
- {
- string directoryName = "RaygunOfflineStorage";
- if (isolatedStorage.DirectoryExists(directoryName))
- {
- string[] fileNames = isolatedStorage.GetFileNames(directoryName + "\\*.txt");
- foreach (string name in fileNames)
- {
- IsolatedStorageFileStream isoFileStream = isolatedStorage.OpenFile(directoryName + "\\" + name, FileMode.Open);
- using (StreamReader reader = new StreamReader(isoFileStream))
- {
- string text = reader.ReadToEnd();
- try
- {
- Send(text);
- }
- catch
- {
- // If just one message fails to send, then don't delete the message, and don't attempt sending anymore until later.
- return;
- }
- System.Diagnostics.Debug.WriteLine("Sent " + name);
- }
- isolatedStorage.DeleteFile(directoryName + "\\" + name);
- }
- if (isolatedStorage.GetFileNames(directoryName + "\\*.txt").Length == 0)
- {
- System.Diagnostics.Debug.WriteLine("Successfully sent all pending messages");
- isolatedStorage.DeleteDirectory(directoryName);
- }
- }
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine(string.Format("Error sending stored messages to Raygun.io {0}", ex.Message));
- }
- }
- }
-
- private IsolatedStorageFile GetIsolatedStorageScope()
- {
- if (AppDomain.CurrentDomain != null && AppDomain.CurrentDomain.ActivationContext != null)
- {
- return IsolatedStorageFile.GetUserStoreForApplication();
- }
- else
- {
- return IsolatedStorageFile.GetUserStoreForAssembly();
- }
- }
}
}
diff --git a/Mindscape.Raygun4Net4/Properties/AssemblyInfo.cs b/Mindscape.Raygun4Net4/Properties/AssemblyInfo.cs
index 3f7ca51b..98c2acae 100644
--- a/Mindscape.Raygun4Net4/Properties/AssemblyInfo.cs
+++ b/Mindscape.Raygun4Net4/Properties/AssemblyInfo.cs
@@ -1,8 +1,7 @@
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-// General Information about an assembly is controlled through the following
+// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Raygun4Net4.0")]
@@ -10,12 +9,12 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Raygun")]
[assembly: AssemblyProduct("Raygun4Net")]
-[assembly: AssemblyCopyright("Copyright © Raygun 2014-2019")]
+[assembly: AssemblyCopyright("Copyright © Raygun 2014-2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
diff --git a/Mindscape.Raygun4Net4/RaygunClient.cs b/Mindscape.Raygun4Net4/RaygunClient.cs
index 27c699cc..42ed4a0d 100644
--- a/Mindscape.Raygun4Net4/RaygunClient.cs
+++ b/Mindscape.Raygun4Net4/RaygunClient.cs
@@ -4,16 +4,14 @@
using System.Linq;
using System.Net;
using Mindscape.Raygun4Net.Messages;
-
using System.Web;
using System.Threading;
using System.Reflection;
using Mindscape.Raygun4Net.Builders;
-using System.IO;
-using System.IO.IsolatedStorage;
-using System.Text;
using Mindscape.Raygun4Net.Breadcrumbs;
using Mindscape.Raygun4Net.Filters;
+using Mindscape.Raygun4Net.Logging;
+using Mindscape.Raygun4Net.Storage;
namespace Mindscape.Raygun4Net
{
@@ -21,14 +19,37 @@ public class RaygunClient : RaygunClientBase
{
internal const string UnhandledExceptionTag = "UnhandledException";
+ [ThreadStatic] private static RaygunRequestMessage _currentRequestMessage;
+ [ThreadStatic] private static List _currentBreadcrumbs;
+
+ private static readonly RaygunBreadcrumbs _breadcrumbs = new RaygunBreadcrumbs(new DefaultBreadcrumbStorage());
+ private static object _sendLock = new object();
+
private readonly string _apiKey;
private readonly RaygunRequestMessageOptions _requestMessageOptions = new RaygunRequestMessageOptions();
private readonly List _wrapperExceptions = new List();
- [ThreadStatic] private static RaygunRequestMessage _currentRequestMessage;
- [ThreadStatic] private static List _currentBreadcrumbs;
+ private IRaygunOfflineStorage _offlineStorage = new IsolatedRaygunOfflineStorage();
- private static readonly RaygunBreadcrumbs _breadcrumbs = new RaygunBreadcrumbs(new DefaultBreadcrumbStorage());
+ ///
+ /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
+ /// and requires credentials.
+ ///
+ public ICredentials ProxyCredentials { get; set; }
+
+ ///
+ /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
+ ///
+ public IWebProxy WebProxy { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Uses the ApiKey specified in the config file.
+ ///
+ public RaygunClient()
+ : this(RaygunSettings.Settings.ApiKey)
+ {
+ }
///
/// Initializes a new instance of the class.
@@ -86,37 +107,6 @@ public RaygunClient(string apiKey)
ThreadPool.QueueUserWorkItem(state => { SendStoredMessages(); });
}
- ///
- /// Initializes a new instance of the class.
- /// Uses the ApiKey specified in the config file.
- ///
- public RaygunClient()
- : this(RaygunSettings.Settings.ApiKey)
- {
- }
-
- protected bool ValidateApiKey()
- {
- if (string.IsNullOrEmpty(_apiKey))
- {
- System.Diagnostics.Debug.WriteLine("ApiKey has not been provided, exception will not be logged");
- return false;
- }
-
- return true;
- }
-
- ///
- /// Gets or sets the username/password credentials which are used to authenticate with the system default Proxy server, if one is set
- /// and requires credentials.
- ///
- public ICredentials ProxyCredentials { get; set; }
-
- ///
- /// Gets or sets an IWebProxy instance which can be used to override the default system proxy server settings
- ///
- public IWebProxy WebProxy { get; set; }
-
///
/// Adds a list of outer exceptions that will be stripped, leaving only the valuable inner exception.
/// This can be used when a wrapper exception, e.g. TargetInvocationException or HttpUnhandledException,
@@ -149,6 +139,8 @@ public void RemoveWrapperExceptions(params Type[] wrapperExceptions)
}
}
+ #region Message Scrubbing Properties
+
///
/// Adds a list of keys to remove from the following sections of the
///
@@ -218,8 +210,8 @@ public void IgnoreServerVariableNames(params string[] names)
}
///
- /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.io.
- /// The default is false which means RawData will be sent to Raygun.io.
+ /// Specifies whether or not RawData from web requests is ignored when sending reports to Raygun.
+ /// The default is false which means RawData will be sent to Raygun.
///
public bool IsRawDataIgnored
{
@@ -259,7 +251,7 @@ public bool UseKeyValuePairRawDataFilter
///
/// Add an implementation to be used when capturing the raw data
- /// of a HTTP request. This filter will be passed the request raw data and is expected to remove
+ /// of a HTTP request. This filter will be passed the request raw data and is expected to remove
/// or replace values whose keys are found in the list supplied to the Filter method.
///
/// Custom raw data filter implementation.
@@ -268,28 +260,9 @@ public void AddRawDataFilter(IRaygunDataFilter filter)
_requestMessageOptions.AddRawDataFilter(filter);
}
- protected override bool CanSend(Exception exception)
- {
- if (RaygunSettings.Settings.ExcludeErrorsFromLocal && HttpContext.Current != null)
- {
- try
- {
- if (HttpContext.Current.Request.IsLocal)
- {
- return false;
- }
- }
- catch
- {
- if (RaygunSettings.Settings.ThrowOnError)
- {
- throw;
- }
- }
- }
+ #endregion // Message Scrubbing Properties
- return base.CanSend(exception);
- }
+ #region Breadcrumbs
public static void RecordBreadcrumb(string message)
{
@@ -306,8 +279,12 @@ public static void ClearBreadcrumbs()
_breadcrumbs.Clear();
}
+ #endregion // Breadcrumbs
+
+ #region Message Send Methods
+
///
- /// Transmits an exception to Raygun.io synchronously, using the version number of the originating assembly.
+ /// Transmits an exception to Raygun synchronously, using the version number of the originating assembly.
///
/// The exception to deliver.
public override void Send(Exception exception)
@@ -316,7 +293,7 @@ public override void Send(Exception exception)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification. This uses the version number of the originating assembly.
///
/// The exception to deliver.
@@ -327,7 +304,7 @@ public void Send(Exception exception, IList tags)
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -340,7 +317,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Transmits an exception to Raygun.io synchronously specifying a list of string tags associated
+ /// Transmits an exception to Raygun synchronously specifying a list of string tags associated
/// with the message for identification, as well as sending a key-value collection of custom data.
/// This uses the version number of the originating assembly.
///
@@ -361,7 +338,7 @@ public void Send(Exception exception, IList tags, IDictionary userCustom
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The exception to deliver.
public void SendInBackground(Exception exception)
@@ -370,7 +347,7 @@ public void SendInBackground(Exception exception)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -380,7 +357,7 @@ public void SendInBackground(Exception exception, IList tags)
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -391,7 +368,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits an exception to Raygun.io.
+ /// Asynchronously transmits an exception to Raygun.
///
/// The exception to deliver.
/// A list of strings associated with the message.
@@ -446,7 +423,7 @@ public void SendInBackground(Exception exception, IList tags, IDictionar
}
///
- /// Asynchronously transmits a message to Raygun.io.
+ /// Asynchronously transmits a message to Raygun.
///
/// The RaygunMessage to send. This needs its OccurredOn property
/// set to a valid DateTime and as much of the Details property as is available.
@@ -455,6 +432,99 @@ public void SendInBackground(RaygunMessage raygunMessage)
ThreadPool.QueueUserWorkItem(c => Send(raygunMessage));
}
+ private void StripAndSend(Exception exception, IList tags, IDictionary userCustomData, RaygunIdentifierMessage userInfo, DateTime? currentTime)
+ {
+ foreach (Exception e in StripWrapperExceptions(exception))
+ {
+ Send(BuildMessage(e, tags, userCustomData, userInfo, currentTime));
+ }
+ }
+
+ ///
+ /// Posts a RaygunMessage to the Raygun API endpoint.
+ ///
+ /// The RaygunMessage to send. This needs its OccurredOn property
+ /// set to a valid DateTime and as much of the Details property as is available.
+ public override void Send(RaygunMessage raygunMessage)
+ {
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to send error report due to invalid API key");
+ return;
+ }
+
+ bool canSend = OnSendingMessage(raygunMessage);
+
+ if (!canSend)
+ {
+ return;
+ }
+
+ string message = null;
+
+ try
+ {
+ message = SimpleJson.SerializeObject(raygunMessage);
+ }
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to serialize report due to: {ex.Message}");
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ bool successfullySentReport = true;
+
+ try
+ {
+ Send(message);
+ }
+ catch (Exception ex)
+ {
+ successfullySentReport = false;
+
+ RaygunLogger.Instance.Error($"Failed to send report to Raygun due to: {ex.Message}");
+
+ SaveMessage(message);
+
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
+ }
+
+ if (successfullySentReport)
+ {
+ SendStoredMessages();
+ }
+ }
+
+ private void Send(string message)
+ {
+ RaygunLogger.Instance.Verbose("Sending Payload --------------");
+ RaygunLogger.Instance.Verbose(message);
+ RaygunLogger.Instance.Verbose("------------------------------");
+
+ if (WebProxy != null)
+ {
+ WebClientHelper.WebProxy = WebProxy;
+ }
+
+ WebClientHelper.Send(message, _apiKey, ProxyCredentials);
+ }
+
+ #endregion // Message Send Methods
+
+ #region Message Building Methods
+
private RaygunRequestMessage BuildRequestMessage()
{
RaygunRequestMessage requestMessage = null;
@@ -468,7 +538,7 @@ private RaygunRequestMessage BuildRequestMessage()
}
catch (HttpException ex)
{
- System.Diagnostics.Trace.WriteLine("Error retrieving HttpRequest {0}", ex.Message);
+ RaygunLogger.Instance.Error($"Error retrieving HttpRequest {ex.Message}");
}
if (request != null)
@@ -518,14 +588,6 @@ protected RaygunMessage BuildMessage(Exception exception, IList tags, ID
return message;
}
- private void StripAndSend(Exception exception, IList tags, IDictionary userCustomData, RaygunIdentifierMessage userInfo, DateTime? currentTime)
- {
- foreach (Exception e in StripWrapperExceptions(exception))
- {
- Send(BuildMessage(e, tags, userCustomData, userInfo, currentTime));
- }
- }
-
protected IEnumerable StripWrapperExceptions(Exception exception)
{
if (exception != null && _wrapperExceptions.Any(wrapperException => exception.GetType() == wrapperException && (exception.InnerException != null || exception is ReflectionTypeLoadException)))
@@ -580,201 +642,123 @@ protected IEnumerable StripWrapperExceptions(Exception exception)
}
}
- ///
- /// Posts a RaygunMessage to the Raygun.io api endpoint.
- ///
- /// The RaygunMessage to send. This needs its OccurredOn property
- /// set to a valid DateTime and as much of the Details property as is available.
- public override void Send(RaygunMessage raygunMessage)
+ #endregion // Message Building Methods
+
+ #region Message Offline Storage
+
+ private void SaveMessage(string message)
{
- bool canSend = OnSendingMessage(raygunMessage);
- if (canSend)
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping saving report.");
+ return;
+ }
+
+ if (!ValidateApiKey())
+ {
+ RaygunLogger.Instance.Warning("Failed to save report due to invalid API key.");
+ return;
+ }
+
+ // Avoid writing and reading from disk at the same time with `SendStoredMessages`.
+ lock (_sendLock)
{
- string message = null;
try
{
- message = SimpleJson.SerializeObject(raygunMessage);
- }
- catch (Exception serializeException)
- {
- System.Diagnostics.Trace.WriteLine($"Error serializing Raygun message due to: {serializeException}");
-
- if (RaygunSettings.Settings.ThrowOnError)
+ if (!_offlineStorage.Store(message, _apiKey))
{
- throw;
+ RaygunLogger.Instance.Warning("Failed to save report to offline storage");
}
}
-
- if (message != null)
+ catch (Exception ex)
{
- try
- {
- if (WebProxy != null)
- {
- WebClientHelper.WebProxy = WebProxy;
- }
-
- WebClientHelper.Send(message, _apiKey, ProxyCredentials);
- }
- catch (Exception sendMessageException)
- {
- try
- {
- System.Diagnostics.Trace.WriteLine($"Error sending exception to Raygun.io due to: {sendMessageException}");
-
- // Attempt to store the message in isolated storage.
- SaveMessage(message);
- }
- catch (Exception saveMessageException)
- {
- // Ignored
- System.Diagnostics.Trace.WriteLine($"Error saving Raygun message due to: {saveMessageException}");
- }
-
- if (RaygunSettings.Settings.ThrowOnError)
- {
- throw;
- }
- }
-
- SendStoredMessages();
+ RaygunLogger.Instance.Error($"Failed to save report to offline storage due to: {ex.Message}");
}
}
}
- private void SaveMessage(string message)
+ private void SendStoredMessages()
{
- try
+ if (!RaygunSettings.Settings.CrashReportingOfflineStorageEnabled)
+ {
+ RaygunLogger.Instance.Warning("Offline storage is disabled, skipping sending stored reports.");
+ return;
+ }
+
+ if (!ValidateApiKey())
{
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
+ RaygunLogger.Instance.Warning("Failed to send offline reports due to invalid API key.");
+ return;
+ }
+
+ lock (_sendLock)
+ {
+ try
{
- string directoryName = "RaygunOfflineStorage";
- if (!isolatedStorage.DirectoryExists(directoryName))
- {
- isolatedStorage.CreateDirectory(directoryName);
- }
+ var files = _offlineStorage.FetchAll(_apiKey);
- int number = 1;
- while (true)
+ foreach (var file in files)
{
- bool exists = isolatedStorage.FileExists(directoryName + "\\RaygunErrorMessage" + number + ".txt");
- if (!exists)
+ try
{
- string nextFileName = directoryName + "\\RaygunErrorMessage" + (number + 1) + ".txt";
- exists = isolatedStorage.FileExists(nextFileName);
- if (exists)
+ // Send the stored report.
+ Send(file.Contents);
+
+ // Remove the stored report from local storage.
+ if (_offlineStorage.Remove(file.Name, _apiKey))
{
- isolatedStorage.DeleteFile(nextFileName);
+ RaygunLogger.Instance.Info("Successfully removed report from offline storage.");
+ }
+ else
+ {
+ RaygunLogger.Instance.Warning("Failed to remove report from offline storage.");
}
-
- break;
}
-
- number++;
- }
-
- if (number == 11)
- {
- string firstFileName = directoryName + "\\RaygunErrorMessage1.txt";
- if (isolatedStorage.FileExists(firstFileName))
+ catch (Exception ex)
{
- isolatedStorage.DeleteFile(firstFileName);
- }
- }
-
- string reportFilePath = directoryName + "\\RaygunErrorMessage" + number + ".txt";
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
- using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(reportFilePath, FileMode.OpenOrCreate, FileAccess.Write, isolatedStorage))
- {
- using (StreamWriter writer = new StreamWriter(isoStream, Encoding.Unicode))
- {
- writer.Write(message);
- writer.Flush();
- writer.Close();
+ // If just one message fails to send, then don't delete the message,
+ // and don't attempt sending anymore until later.
+ return;
}
}
-
- System.Diagnostics.Trace.WriteLine("Saved Raygun message to: " + reportFilePath);
}
- }
- catch (Exception ex)
- {
- System.Diagnostics.Trace.WriteLine($"Error saving message to isolated storage {ex}");
+ catch (Exception ex)
+ {
+ RaygunLogger.Instance.Error($"Failed to send stored report to Raygun due to: {ex.Message}");
+ }
}
}
- private static object _sendLock = new object();
+ #endregion // Message Offline Storage
- private void SendStoredMessages()
+ protected bool ValidateApiKey()
{
- lock (_sendLock)
+ return !string.IsNullOrEmpty(_apiKey);
+ }
+
+ protected override bool CanSend(Exception exception)
+ {
+ if (RaygunSettings.Settings.ExcludeErrorsFromLocal && HttpContext.Current != null)
{
- if (!ValidateApiKey())
- {
- System.Diagnostics.Debug.WriteLine("ApiKey has not been provided, skipping sending stored Raygun messages");
- return;
- }
-
try
{
- using (IsolatedStorageFile isolatedStorage = GetIsolatedStorageScope())
+ if (HttpContext.Current.Request.IsLocal)
{
- string directoryName = "RaygunOfflineStorage";
- if (isolatedStorage.DirectoryExists(directoryName))
- {
- string[] fileNames = isolatedStorage.GetFileNames(directoryName + "\\*.txt");
- foreach (string name in fileNames)
- {
- IsolatedStorageFileStream isoFileStream = isolatedStorage.OpenFile(directoryName + "\\" + name, FileMode.Open);
- using (StreamReader reader = new StreamReader(isoFileStream))
- {
- string text = reader.ReadToEnd();
- try
- {
- if (WebProxy != null)
- {
- WebClientHelper.WebProxy = WebProxy;
- }
-
- WebClientHelper.Send(text, _apiKey, ProxyCredentials);
- }
- catch
- {
- // If just one message fails to send, then don't delete the message, and don't attempt sending anymore until later.
- return;
- }
-
- System.Diagnostics.Debug.WriteLine("Sent " + name);
- }
-
- isolatedStorage.DeleteFile(directoryName + "\\" + name);
- }
-
- if (isolatedStorage.GetFileNames(directoryName + "\\*.txt").Length == 0)
- {
- System.Diagnostics.Debug.WriteLine("Successfully sent all pending messages");
- isolatedStorage.DeleteDirectory(directoryName);
- }
- }
+ return false;
}
}
- catch (Exception ex)
+ catch
{
- System.Diagnostics.Debug.WriteLine($"Error sending stored messages to Raygun.io due to: {ex}");
+ if (RaygunSettings.Settings.ThrowOnError)
+ {
+ throw;
+ }
}
}
- }
- private IsolatedStorageFile GetIsolatedStorageScope()
- {
- if (AppDomain.CurrentDomain != null && AppDomain.CurrentDomain.ActivationContext != null)
- {
- return IsolatedStorageFile.GetUserStoreForApplication();
- }
- else
- {
- return IsolatedStorageFile.GetUserStoreForAssembly();
- }
+ return base.CanSend(exception);
}
}
}
\ No newline at end of file
diff --git a/Mindscape.Raygun4Net4/WebClientHelper.cs b/Mindscape.Raygun4Net4/WebClientHelper.cs
index e087a576..2930c21e 100644
--- a/Mindscape.Raygun4Net4/WebClientHelper.cs
+++ b/Mindscape.Raygun4Net4/WebClientHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Net;
+using Mindscape.Raygun4Net.Logging;
namespace Mindscape.Raygun4Net
{
@@ -25,7 +26,7 @@ public static void Send(string message, string apiKey, ICredentials proxyCredent
{
if (string.IsNullOrEmpty(apiKey))
{
- System.Diagnostics.Trace.WriteLine("ApiKey has not been provided, the Raygun message will not be sent");
+ RaygunLogger.Instance.Warning("ApiKey has not been provided, the Raygun message will not be sent");
return;
}