diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index dc06b9344..46cf2f09f 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -68,9 +68,8 @@
..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll
True
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Api/app.config b/src/NzbDrone.Api/app.config
index 764d548b6..c1684a7be 100644
--- a/src/NzbDrone.Api/app.config
+++ b/src/NzbDrone.Api/app.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.Api/packages.config b/src/NzbDrone.Api/packages.config
index 97cec19fa..1fe9f88c1 100644
--- a/src/NzbDrone.Api/packages.config
+++ b/src/NzbDrone.Api/packages.config
@@ -5,6 +5,6 @@
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Automation.Test/app.config b/src/NzbDrone.Automation.Test/app.config
index 764d548b6..c1684a7be 100644
--- a/src/NzbDrone.Automation.Test/app.config
+++ b/src/NzbDrone.Automation.Test/app.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs
index 03f0cd7c9..7aa3525bb 100644
--- a/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs
+++ b/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs
@@ -1,12 +1,34 @@
-using System;
+using System;
using System.IO;
+using System.Linq;
using System.Reflection;
namespace NzbDrone.Common.EnvironmentInfo
{
public static class BuildInfo
{
- public static Version Version => Assembly.GetExecutingAssembly().GetName().Version;
+ static BuildInfo()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+
+ Version = assembly.GetName().Version;
+
+ var attributes = assembly.GetCustomAttributes(true);
+
+ Branch = "unknow";
+
+ var config = attributes.OfType().FirstOrDefault();
+ if (config != null)
+ {
+ Branch = config.Configuration;
+ }
+
+ Release = $"{Version}-{Branch}";
+ }
+
+ public static Version Version { get; }
+ public static String Branch { get; }
+ public static string Release { get; }
public static DateTime BuildDateTime
{
@@ -29,4 +51,4 @@ namespace NzbDrone.Common.EnvironmentInfo
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Common/HashUtil.cs b/src/NzbDrone.Common/HashUtil.cs
index 062e561c1..dec657684 100644
--- a/src/NzbDrone.Common/HashUtil.cs
+++ b/src/NzbDrone.Common/HashUtil.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Text;
namespace NzbDrone.Common
@@ -26,5 +26,11 @@ namespace NzbDrone.Common
}
return string.Format("{0:x8}", mCrc);
}
+
+ public static string AnonymousToken()
+ {
+ var seed = $"{Environment.ProcessorCount}_{Environment.OSVersion.Platform}_{Environment.MachineName}_{Environment.UserName}";
+ return HashUtil.CalculateCrc(seed);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs b/src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs
new file mode 100644
index 000000000..40937eb3f
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs
@@ -0,0 +1,43 @@
+using Newtonsoft.Json.Linq;
+using NzbDrone.Common.Serializer;
+
+namespace NzbDrone.Common.Instrumentation
+{
+ public class CleansingJsonVisitor : JsonVisitor
+ {
+ public override void Visit(JArray json)
+ {
+ for (var i = 0; i < json.Count; i++)
+ {
+ if (json[i].Type == JTokenType.String)
+ {
+ var text = json[i].Value();
+ json[i] = new JValue(CleanseLogMessage.Cleanse(text));
+ }
+ }
+ foreach (JToken token in json)
+ {
+ Visit(token);
+ }
+ }
+
+ public override void Visit(JProperty property)
+ {
+ if (property.Value.Type == JTokenType.String)
+ {
+ property.Value = CleanseValue(property.Value as JValue);
+ }
+ else
+ {
+ base.Visit(property);
+ }
+ }
+
+ private JValue CleanseValue(JValue value)
+ {
+ var text = value.Value();
+ var cleansed = CleanseLogMessage.Cleanse(text);
+ return new JValue(cleansed);
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs b/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs
new file mode 100644
index 000000000..4848d6fa7
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs
@@ -0,0 +1,55 @@
+using System.Linq;
+using NLog;
+using NLog.Fluent;
+
+namespace NzbDrone.Common.Instrumentation.Extensions
+{
+ public static class SentryLoggerExtensions
+ {
+ public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
+
+ public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
+ {
+ return logBuilder.Property("Sentry", fingerprint);
+ }
+
+ public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
+ {
+ return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
+ }
+
+ public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
+ {
+ return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
+ }
+
+ public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
+ {
+ return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
+ }
+
+ public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
+ {
+ return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
+ }
+
+ private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
+ {
+ SentryLogger.Log(level)
+ .CopyLogEvent(logBuilder.LogEventInfo)
+ .SentryFingerprint(fingerprint)
+ .Write();
+
+ return logBuilder.Property("Sentry", null);
+ }
+
+ private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
+ {
+ return logBuilder.LoggerName(logEvent.LoggerName)
+ .TimeStamp(logEvent.TimeStamp)
+ .Message(logEvent.Message, logEvent.Parameters)
+ .Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
+ .Exception(logEvent.Exception);
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
index 0feb98754..d7aaab069 100644
--- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
+++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
@@ -6,6 +6,7 @@ using NLog.Config;
using NLog.Targets;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
+using NzbDrone.Common.Instrumentation.Sentry;
namespace NzbDrone.Common.Instrumentation
{
@@ -18,6 +19,7 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration = new LoggingConfiguration();
}
+ const string FILE_LOG_LAYOUT = @"${date:format=yyyy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
public static void Register(IStartupContext startupContext, bool updateApp, bool inConsole)
{
@@ -37,8 +39,7 @@ namespace NzbDrone.Common.Instrumentation
RegisterDebugger();
}
- //Disabling for now - until its fixed or we yank it out
- //RegisterExceptron();
+ RegisterSentry(updateApp);
if (updateApp)
{
@@ -57,6 +58,36 @@ namespace NzbDrone.Common.Instrumentation
LogManager.ReconfigExistingLoggers();
}
+ private static void RegisterSentry(bool updateClient)
+ {
+ string dsn;
+
+ if (updateClient)
+ {
+ dsn = "https://40e47616afd84525bf49306123cb0926@sentry.radarr.video/10";
+ }
+ else
+ {
+ dsn = RuntimeInfo.IsProduction
+ ? "https://ef145e92efdd4155a0771c11c099695e@sentry.radarr.video/2"
+ : "https://dee5b3fe26844368ac4458faa7d00a1f@sentry.radarr.video/9";
+ }
+
+ var target = new SentryTarget(dsn)
+ {
+ Name = "sentryTarget",
+ Layout = "${message}"
+ };
+
+ var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Debug, target);
+ LogManager.Configuration.AddTarget("sentryTarget", target);
+ LogManager.Configuration.LoggingRules.Add(loggingRule);
+
+ // Events logged to Sentry go only to Sentry.
+ var loggingRuleSentry = new LoggingRule("Sentry", LogLevel.Debug, target) { Final = true };
+ LogManager.Configuration.LoggingRules.Insert(0, loggingRuleSentry);
+ }
+
private static void RegisterDebugger()
{
DebuggerTarget target = new DebuggerTarget();
@@ -84,8 +115,6 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration.LoggingRules.Add(loggingRule);
}
- const string FILE_LOG_LAYOUT = @"${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
-
private static void RegisterAppFile(IAppFolderInfo appFolderInfo)
{
RegisterAppFile(appFolderInfo, "appFileInfo", "radarr.txt", 5, LogLevel.Info);
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/MachineNameUserFactory.cs b/src/NzbDrone.Common/Instrumentation/Sentry/MachineNameUserFactory.cs
new file mode 100644
index 000000000..f3e0c5fbc
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/MachineNameUserFactory.cs
@@ -0,0 +1,12 @@
+using SharpRaven.Data;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+ public class MachineNameUserFactory : ISentryUserFactory
+ {
+ public SentryUser Create()
+ {
+ return new SentryUser(HashUtil.AnonymousToken());
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/RadarrJsonPacketFactory.cs b/src/NzbDrone.Common/Instrumentation/Sentry/RadarrJsonPacketFactory.cs
new file mode 100644
index 000000000..880ed9de0
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/RadarrJsonPacketFactory.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using SharpRaven.Data;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+ public class RadarrJsonPacketFactory : IJsonPacketFactory
+ {
+ private readonly SentryPacketCleanser _cleanser;
+
+ public RadarrJsonPacketFactory()
+ {
+ _cleanser = new SentryPacketCleanser();
+ }
+
+ private static string ShortenPath(string path)
+ {
+
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return null;
+ }
+
+ var index = path.IndexOf("\\src\\", StringComparison.Ordinal);
+
+ if (index <= 0)
+ {
+ return path;
+ }
+
+ return path.Substring(index + "\\src".Length);
+ }
+
+ public JsonPacket Create(string project, SentryEvent @event)
+ {
+ var packet = new RadarrSentryPacket(project, @event);
+
+ try
+ {
+ foreach (var exception in packet.Exceptions)
+ {
+ foreach (var frame in exception.Stacktrace.Frames)
+ {
+ frame.Filename = ShortenPath(frame.Filename);
+ }
+ }
+
+ _cleanser.CleansePacket(packet);
+ }
+ catch (Exception)
+ {
+
+ }
+
+ return packet;
+ }
+
+ [Obsolete]
+ public JsonPacket Create(string project, SentryMessage message, ErrorLevel level = ErrorLevel.Info, IDictionary tags = null,
+ string[] fingerprint = null, object extra = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ [Obsolete]
+ public JsonPacket Create(string project, Exception exception, SentryMessage message = null, ErrorLevel level = ErrorLevel.Error,
+ IDictionary tags = null, string[] fingerprint = null, object extra = null)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/RadarrSentryPacket.cs b/src/NzbDrone.Common/Instrumentation/Sentry/RadarrSentryPacket.cs
new file mode 100644
index 000000000..d0d7ef3bc
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/RadarrSentryPacket.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using SharpRaven.Data;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+ public class RadarrSentryPacket : JsonPacket
+ {
+ private readonly JsonSerializerSettings _setting;
+
+ public RadarrSentryPacket(string project, SentryEvent @event) :
+ base(project, @event)
+ {
+ _setting = new JsonSerializerSettings
+ {
+ DefaultValueHandling = DefaultValueHandling.Ignore
+ };
+ }
+
+ public override string ToString(Formatting formatting)
+ {
+ return JsonConvert.SerializeObject(this, formatting, _setting);
+ }
+
+ public override string ToString()
+ {
+ return ToString(Formatting.None);
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryDebounce.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryDebounce.cs
new file mode 100644
index 000000000..07c53e9d3
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryDebounce.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using NzbDrone.Common.Cache;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+ public class SentryDebounce
+ {
+ private readonly TimeSpan _ttl;
+ private readonly Cached _cache;
+
+ public SentryDebounce()
+ {
+ _cache = new Cached();
+ _ttl = RuntimeInfo.IsProduction ? TimeSpan.FromHours(1) : TimeSpan.FromSeconds(10);
+ }
+
+ public bool Allowed(IEnumerable fingerPrint)
+ {
+ var key = string.Join("|", fingerPrint);
+ var exists = _cache.Find(key);
+
+ if (exists)
+ {
+ return false;
+ }
+
+ _cache.Set(key, true, _ttl);
+ return true;
+ }
+
+ public void Clear()
+ {
+ _cache.Clear();
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs
new file mode 100644
index 000000000..c765753c5
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs
@@ -0,0 +1,27 @@
+using Newtonsoft.Json.Linq;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+ public class SentryPacketCleanser
+ {
+ public void CleansePacket(RadarrSentryPacket packet)
+ {
+ packet.Message = CleanseLogMessage.Cleanse(packet.Message);
+
+ if (packet.Fingerprint != null)
+ {
+ for (var i = 0; i < packet.Fingerprint.Length; i++)
+ {
+ packet.Fingerprint[i] = CleanseLogMessage.Cleanse(packet.Fingerprint[i]);
+ }
+ }
+
+ if (packet.Extra != null)
+ {
+ var target = JObject.FromObject(packet.Extra);
+ new CleansingJsonVisitor().Visit(target);
+ packet.Extra = target;
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
new file mode 100644
index 000000000..d9f9e4aec
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data.SQLite;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using NLog;
+using NLog.Common;
+using NLog.Targets;
+using NzbDrone.Common.EnvironmentInfo;
+using SharpRaven;
+using SharpRaven.Data;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+ [Target("Sentry")]
+ public class SentryTarget : TargetWithLayout
+ {
+ private readonly RavenClient _client;
+
+ // don't report uninformative SQLite exceptions
+ // busy/locked are benign https://forums.sonarr.tv/t/owin-sqlite-error-5-database-is-locked/5423/11
+ // The others will be user configuration problems and silt up Sentry
+ private static readonly HashSet FilteredSQLiteErrors = new HashSet
+ {
+ SQLiteErrorCode.Busy,
+ SQLiteErrorCode.Locked,
+ SQLiteErrorCode.Perm,
+ SQLiteErrorCode.ReadOnly,
+ SQLiteErrorCode.IoErr,
+ SQLiteErrorCode.Corrupt,
+ SQLiteErrorCode.Full,
+ SQLiteErrorCode.CantOpen,
+ SQLiteErrorCode.Auth
+ };
+
+ // use string and not Type so we don't need a reference to the project
+ // where these are defined
+ private static readonly HashSet FilteredExceptionTypeNames = new HashSet
+ {
+ // UnauthorizedAccessExceptions will just be user configuration issues
+ "UnauthorizedAccessException",
+
+ // Filter out people stuck in boot loops
+ "CorruptDatabaseException",
+
+ // This also filters some people in boot loops
+ "TinyIoCResolutionException"
+ };
+
+ public static readonly List FilteredExceptionMessages = new List
+ {
+ // Swallow the many, many exceptions flowing through from Jackett
+ "Jackett.Common.IndexerException",
+
+ // Fix openflixr being stupid with permissions
+ "openflixr"
+ };
+
+ // exception types in this list will additionally have the exception message added to the
+ // sentry fingerprint. Make sure that this message doesn't vary by exception
+ // (e.g. containing a path or a url) so that the sentry grouping is sensible
+ private static readonly HashSet IncludeExceptionMessageTypes = new HashSet
+ {
+ "SQLiteException"
+ };
+
+ private static readonly IDictionary LoggingLevelMap = new Dictionary
+ {
+ {LogLevel.Debug, ErrorLevel.Debug},
+ {LogLevel.Error, ErrorLevel.Error},
+ {LogLevel.Fatal, ErrorLevel.Fatal},
+ {LogLevel.Info, ErrorLevel.Info},
+ {LogLevel.Trace, ErrorLevel.Debug},
+ {LogLevel.Warn, ErrorLevel.Warning},
+ };
+
+ private readonly SentryDebounce _debounce;
+ private bool _unauthorized;
+
+ public bool FilterEvents { get; set; }
+
+
+ public SentryTarget(string dsn)
+ {
+ _client = new RavenClient(new Dsn(dsn), new RadarrJsonPacketFactory(), new SentryRequestFactory(), new MachineNameUserFactory())
+ {
+ Compression = true,
+ Environment = RuntimeInfo.IsProduction ? "production" : "development",
+ Release = BuildInfo.Release,
+ ErrorOnCapture = OnError
+ };
+
+
+ _client.Tags.Add("osfamily", OsInfo.Os.ToString());
+ _client.Tags.Add("runtime", PlatformInfo.PlatformName);
+ _client.Tags.Add("culture", Thread.CurrentThread.CurrentCulture.Name);
+ _client.Tags.Add("branch", BuildInfo.Branch);
+ _client.Tags.Add("version", BuildInfo.Version.ToString());
+
+ _debounce = new SentryDebounce();
+
+ FilterEvents = true;
+ }
+
+ private void OnError(Exception ex)
+ {
+ var webException = ex as WebException;
+
+ if (webException != null)
+ {
+ var response = webException.Response as HttpWebResponse;
+ var statusCode = response?.StatusCode;
+ if (statusCode == HttpStatusCode.Unauthorized)
+ {
+ _unauthorized = true;
+ _debounce.Clear();
+ }
+ }
+
+ InternalLogger.Error(ex, "Unable to send error to Sentry");
+ }
+
+ private static List GetFingerPrint(LogEventInfo logEvent)
+ {
+ if (logEvent.Properties.ContainsKey("Sentry"))
+ {
+ return ((string[])logEvent.Properties["Sentry"]).ToList();
+ }
+
+ var fingerPrint = new List
+ {
+ logEvent.Level.Ordinal.ToString(),
+ logEvent.LoggerName,
+ logEvent.Message
+ };
+
+ var ex = logEvent.Exception;
+
+ if (ex != null)
+ {
+ fingerPrint.Add(ex.GetType().FullName);
+ fingerPrint.Add(ex.TargetSite.ToString());
+ if (ex.InnerException != null)
+ {
+ fingerPrint.Add(ex.InnerException.GetType().FullName);
+ }
+ else if (IncludeExceptionMessageTypes.Contains(ex.GetType().Name))
+ {
+ fingerPrint.Add(ex?.Message);
+ }
+ }
+
+ return fingerPrint;
+ }
+
+ private bool IsSentryMessage(LogEventInfo logEvent)
+ {
+ if (logEvent.Properties.ContainsKey("Sentry"))
+ {
+ return logEvent.Properties["Sentry"] != null;
+ }
+
+ if (logEvent.Level >= LogLevel.Error && logEvent.Exception != null)
+ {
+ if (FilterEvents)
+ {
+ var sqlEx = logEvent.Exception as SQLiteException;
+ if (sqlEx != null && FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
+ {
+ return false;
+ }
+
+ if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
+ {
+ return false;
+ }
+
+ if (FilteredExceptionMessages.Any(x => logEvent.Exception.Message.Contains(x)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+
+ protected override void Write(LogEventInfo logEvent)
+ {
+ if (_unauthorized)
+ {
+ return;
+ }
+
+ try
+ {
+ // don't report non-critical events without exceptions
+ if (!IsSentryMessage(logEvent))
+ {
+ return;
+ }
+
+ var fingerPrint = GetFingerPrint(logEvent);
+ if (!_debounce.Allowed(fingerPrint))
+ {
+ return;
+ }
+
+ var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
+ extras.Remove("Sentry");
+ _client.Logger = logEvent.LoggerName;
+
+ if (logEvent.Exception != null)
+ {
+ foreach (DictionaryEntry data in logEvent.Exception.Data)
+ {
+ extras.Add(data.Key.ToString(), data.Value.ToString());
+ }
+ }
+
+ var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters);
+
+ var sentryEvent = new SentryEvent(logEvent.Exception)
+ {
+ Level = LoggingLevelMap[logEvent.Level],
+ Message = sentryMessage,
+ Extra = extras,
+ Fingerprint =
+ {
+ logEvent.Level.ToString(),
+ logEvent.LoggerName,
+ logEvent.Message
+ }
+ };
+
+ if (logEvent.Exception != null)
+ {
+ sentryEvent.Fingerprint.Add(logEvent.Exception.GetType().FullName);
+ }
+
+ if (logEvent.Properties.ContainsKey("Sentry"))
+ {
+ sentryEvent.Fingerprint.Clear();
+ Array.ForEach((string[])logEvent.Properties["Sentry"], sentryEvent.Fingerprint.Add);
+ }
+
+ var osName = Environment.GetEnvironmentVariable("OS_NAME");
+ var osVersion = Environment.GetEnvironmentVariable("OS_VERSION");
+ var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION");
+
+ sentryEvent.Tags.Add("os_name", osName);
+ sentryEvent.Tags.Add("os_version", $"{osName} {osVersion}");
+ sentryEvent.Tags.Add("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}");
+
+ _client.Capture(sentryEvent);
+ }
+ catch (Exception e)
+ {
+ OnError(e);
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index e5a2c67c6..c22d7758c 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -39,9 +39,8 @@
4
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
@@ -50,6 +49,9 @@
..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\Org.Mentalis.dll
True
+
+ ..\packages\SharpRaven.2.4.0\lib\net40\SharpRaven.dll
+
..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\SocksWebProxy.dll
True
@@ -57,6 +59,9 @@
+
+ ..\Libraries\Sqlite\System.Data.SQLite.dll
+
@@ -199,11 +204,18 @@
+
+
+
+
+
+
+
@@ -224,6 +236,7 @@
+
diff --git a/src/NzbDrone.Common/Properties/SharedAssemblyInfo.cs b/src/NzbDrone.Common/Properties/SharedAssemblyInfo.cs
index cdb59506a..d1cae3e83 100644
--- a/src/NzbDrone.Common/Properties/SharedAssemblyInfo.cs
+++ b/src/NzbDrone.Common/Properties/SharedAssemblyInfo.cs
@@ -1,7 +1,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
-[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyConfiguration("debug")]
[assembly: AssemblyCompany("radarr.video")]
[assembly: AssemblyProduct("Radarr")]
[assembly: AssemblyVersion("10.0.0.*")]
diff --git a/src/NzbDrone.Common/Serializer/JsonVisitor.cs b/src/NzbDrone.Common/Serializer/JsonVisitor.cs
new file mode 100644
index 000000000..532e23164
--- /dev/null
+++ b/src/NzbDrone.Common/Serializer/JsonVisitor.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json.Linq;
+
+namespace NzbDrone.Common.Serializer
+{
+
+ public class JsonVisitor
+ {
+ protected void Dispatch(JToken json)
+ {
+ switch (json.Type)
+ {
+ case JTokenType.Object:
+ Visit(json as JObject);
+ break;
+
+ case JTokenType.Array:
+ Visit(json as JArray);
+ break;
+
+ case JTokenType.Raw:
+ Visit(json as JRaw);
+ break;
+
+ case JTokenType.Constructor:
+ Visit(json as JConstructor);
+ break;
+
+ case JTokenType.Property:
+ Visit(json as JProperty);
+ break;
+
+ case JTokenType.Comment:
+ case JTokenType.Integer:
+ case JTokenType.Float:
+ case JTokenType.String:
+ case JTokenType.Boolean:
+ case JTokenType.Null:
+ case JTokenType.Undefined:
+ case JTokenType.Date:
+ case JTokenType.Bytes:
+ case JTokenType.Guid:
+ case JTokenType.Uri:
+ case JTokenType.TimeSpan:
+ Visit(json as JValue);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ public virtual void Visit(JToken json)
+ {
+ Dispatch(json);
+ }
+
+ public virtual void Visit(JContainer json)
+ {
+ Dispatch(json);
+ }
+
+ public virtual void Visit(JArray json)
+ {
+ foreach (JToken token in json)
+ {
+ Visit(token);
+ }
+ }
+ public virtual void Visit(JConstructor json)
+ {
+ }
+
+ public virtual void Visit(JObject json)
+ {
+ foreach (JProperty property in json.Properties())
+ {
+ Visit(property);
+ }
+ }
+
+ public virtual void Visit(JProperty property)
+ {
+ Visit(property.Value);
+ }
+
+ public virtual void Visit(JValue value)
+ {
+
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/app.config b/src/NzbDrone.Common/app.config
index 44298137a..8460dd432 100644
--- a/src/NzbDrone.Common/app.config
+++ b/src/NzbDrone.Common/app.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config
index 3e7c62189..a999e5d85 100644
--- a/src/NzbDrone.Common/packages.config
+++ b/src/NzbDrone.Common/packages.config
@@ -2,7 +2,8 @@
-
+
+
\ No newline at end of file
diff --git a/src/NzbDrone.Console/NzbDrone.Console.csproj b/src/NzbDrone.Console/NzbDrone.Console.csproj
index 3229c4b61..a0f023d3e 100644
--- a/src/NzbDrone.Console/NzbDrone.Console.csproj
+++ b/src/NzbDrone.Console/NzbDrone.Console.csproj
@@ -72,9 +72,8 @@
False
..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Console/packages.config b/src/NzbDrone.Console/packages.config
index 48ed9e74d..7001c53f2 100644
--- a/src/NzbDrone.Console/packages.config
+++ b/src/NzbDrone.Console/packages.config
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 43add3761..c6a23f381 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -80,9 +80,8 @@
..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.Configuration.dll
True
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Core.Test/packages.config b/src/NzbDrone.Core.Test/packages.config
index 029d08f0a..1a8597e28 100644
--- a/src/NzbDrone.Core.Test/packages.config
+++ b/src/NzbDrone.Core.Test/packages.config
@@ -9,7 +9,7 @@
-
+
diff --git a/src/NzbDrone.Core/App.config b/src/NzbDrone.Core/App.config
index fa5aafdb9..043c42fe9 100644
--- a/src/NzbDrone.Core/App.config
+++ b/src/NzbDrone.Core/App.config
@@ -5,7 +5,7 @@
-
+
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index f980e81b0..bf42ded4e 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -76,9 +76,8 @@
False
..\packages\ImageResizer.3.4.3\lib\ImageResizer.dll
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Core/packages.config b/src/NzbDrone.Core/packages.config
index 4f7874aa4..9edadfdf6 100644
--- a/src/NzbDrone.Core/packages.config
+++ b/src/NzbDrone.Core/packages.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.Host/NzbDrone.Host.csproj b/src/NzbDrone.Host/NzbDrone.Host.csproj
index 329294639..ff7a2b57c 100644
--- a/src/NzbDrone.Host/NzbDrone.Host.csproj
+++ b/src/NzbDrone.Host/NzbDrone.Host.csproj
@@ -81,9 +81,8 @@
..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll
True
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Host/app.config b/src/NzbDrone.Host/app.config
index af924b668..885d99232 100644
--- a/src/NzbDrone.Host/app.config
+++ b/src/NzbDrone.Host/app.config
@@ -14,7 +14,7 @@
-
+
diff --git a/src/NzbDrone.Host/packages.config b/src/NzbDrone.Host/packages.config
index 6c3e3c3fc..c9daf9da8 100644
--- a/src/NzbDrone.Host/packages.config
+++ b/src/NzbDrone.Host/packages.config
@@ -5,7 +5,7 @@
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
index 2a9077d59..a24ba785e 100644
--- a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
+++ b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
@@ -72,9 +72,8 @@
..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll
True
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Integration.Test/packages.config b/src/NzbDrone.Integration.Test/packages.config
index 937417ead..e771f9344 100644
--- a/src/NzbDrone.Integration.Test/packages.config
+++ b/src/NzbDrone.Integration.Test/packages.config
@@ -9,7 +9,7 @@
-
+
diff --git a/src/NzbDrone.Libraries.Test/NzbDrone.Libraries.Test.csproj b/src/NzbDrone.Libraries.Test/NzbDrone.Libraries.Test.csproj
index 2c09a7691..ec9cec9e5 100644
--- a/src/NzbDrone.Libraries.Test/NzbDrone.Libraries.Test.csproj
+++ b/src/NzbDrone.Libraries.Test/NzbDrone.Libraries.Test.csproj
@@ -46,9 +46,8 @@
..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll
True
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll
diff --git a/src/NzbDrone.Libraries.Test/app.config b/src/NzbDrone.Libraries.Test/app.config
index 764d548b6..c1684a7be 100644
--- a/src/NzbDrone.Libraries.Test/app.config
+++ b/src/NzbDrone.Libraries.Test/app.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.Libraries.Test/packages.config b/src/NzbDrone.Libraries.Test/packages.config
index f18ac4a21..378e2d631 100644
--- a/src/NzbDrone.Libraries.Test/packages.config
+++ b/src/NzbDrone.Libraries.Test/packages.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Mono.Test/app.config b/src/NzbDrone.Mono.Test/app.config
index 7875f9d72..b6d9543c5 100644
--- a/src/NzbDrone.Mono.Test/app.config
+++ b/src/NzbDrone.Mono.Test/app.config
@@ -8,7 +8,7 @@
-
+
diff --git a/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj b/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj
index 1e6091565..6cc2405d2 100644
--- a/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj
+++ b/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj
@@ -38,9 +38,8 @@
4
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
diff --git a/src/NzbDrone.SignalR/app.config b/src/NzbDrone.SignalR/app.config
index 764d548b6..c1684a7be 100644
--- a/src/NzbDrone.SignalR/app.config
+++ b/src/NzbDrone.SignalR/app.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.SignalR/packages.config b/src/NzbDrone.SignalR/packages.config
index 6f5b1e06a..7c276ed86 100644
--- a/src/NzbDrone.SignalR/packages.config
+++ b/src/NzbDrone.SignalR/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Test.Common/App.config b/src/NzbDrone.Test.Common/App.config
index 1318017d2..416bc9627 100644
--- a/src/NzbDrone.Test.Common/App.config
+++ b/src/NzbDrone.Test.Common/App.config
@@ -7,7 +7,7 @@
-
+
diff --git a/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj
index e92511b41..e9fbbe0e7 100644
--- a/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj
+++ b/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj
@@ -45,9 +45,8 @@
..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll
True
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Test.Common/packages.config b/src/NzbDrone.Test.Common/packages.config
index 7bc3af495..14802ed9f 100644
--- a/src/NzbDrone.Test.Common/packages.config
+++ b/src/NzbDrone.Test.Common/packages.config
@@ -3,7 +3,7 @@
-
+
diff --git a/src/NzbDrone.Update/NzbDrone.Update.csproj b/src/NzbDrone.Update/NzbDrone.Update.csproj
index 907888c68..912ad3efb 100644
--- a/src/NzbDrone.Update/NzbDrone.Update.csproj
+++ b/src/NzbDrone.Update/NzbDrone.Update.csproj
@@ -38,9 +38,8 @@
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone.Update/app.config b/src/NzbDrone.Update/app.config
index 810f98ed6..76f6c07e9 100644
--- a/src/NzbDrone.Update/app.config
+++ b/src/NzbDrone.Update/app.config
@@ -11,7 +11,7 @@
-
+
diff --git a/src/NzbDrone.Update/packages.config b/src/NzbDrone.Update/packages.config
index b0d27c27b..63e82cffe 100644
--- a/src/NzbDrone.Update/packages.config
+++ b/src/NzbDrone.Update/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Windows.Test/app.config b/src/NzbDrone.Windows.Test/app.config
index 7875f9d72..b6d9543c5 100644
--- a/src/NzbDrone.Windows.Test/app.config
+++ b/src/NzbDrone.Windows.Test/app.config
@@ -8,7 +8,7 @@
-
+
diff --git a/src/NzbDrone/NzbDrone.csproj b/src/NzbDrone/NzbDrone.csproj
index 2e3ec02ab..350d008a9 100644
--- a/src/NzbDrone/NzbDrone.csproj
+++ b/src/NzbDrone/NzbDrone.csproj
@@ -72,9 +72,8 @@
False
..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll
-
- False
- ..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll
+
+ ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll
..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll
diff --git a/src/NzbDrone/packages.config b/src/NzbDrone/packages.config
index 48ed9e74d..7001c53f2 100644
--- a/src/NzbDrone/packages.config
+++ b/src/NzbDrone/packages.config
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file