From 01b0365884da7b2cd4527d921532a9ba4be4054e Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 18 Jan 2020 21:54:06 -0500 Subject: [PATCH] New: Add Sentry --- src/NzbDrone.Api/NzbDrone.Api.csproj | 5 +- src/NzbDrone.Api/app.config | 2 +- src/NzbDrone.Api/packages.config | 2 +- src/NzbDrone.Automation.Test/app.config | 2 +- .../EnvironmentInfo/BuildInfo.cs | 28 +- src/NzbDrone.Common/HashUtil.cs | 10 +- .../Instrumentation/CleansingJsonVisitor.cs | 43 +++ .../Extensions/SentryLoggerExtensions.cs | 55 ++++ .../Instrumentation/NzbDroneLogger.cs | 37 ++- .../Sentry/MachineNameUserFactory.cs | 12 + .../Sentry/RadarrJsonPacketFactory.cs | 72 +++++ .../Sentry/RadarrSentryPacket.cs | 29 ++ .../Instrumentation/Sentry/SentryDebounce.cs | 38 +++ .../Sentry/SentryPacketCleanser.cs | 27 ++ .../Instrumentation/Sentry/SentryTarget.cs | 268 ++++++++++++++++++ src/NzbDrone.Common/NzbDrone.Common.csproj | 19 +- .../Properties/SharedAssemblyInfo.cs | 2 +- src/NzbDrone.Common/Serializer/JsonVisitor.cs | 95 +++++++ src/NzbDrone.Common/app.config | 2 +- src/NzbDrone.Common/packages.config | 3 +- src/NzbDrone.Console/NzbDrone.Console.csproj | 5 +- src/NzbDrone.Console/packages.config | 2 +- .../NzbDrone.Core.Test.csproj | 5 +- src/NzbDrone.Core.Test/packages.config | 2 +- src/NzbDrone.Core/App.config | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 5 +- src/NzbDrone.Core/packages.config | 2 +- src/NzbDrone.Host/NzbDrone.Host.csproj | 5 +- src/NzbDrone.Host/app.config | 2 +- src/NzbDrone.Host/packages.config | 2 +- .../NzbDrone.Integration.Test.csproj | 5 +- src/NzbDrone.Integration.Test/packages.config | 2 +- .../NzbDrone.Libraries.Test.csproj | 5 +- src/NzbDrone.Libraries.Test/app.config | 2 +- src/NzbDrone.Libraries.Test/packages.config | 2 +- src/NzbDrone.Mono.Test/app.config | 2 +- src/NzbDrone.SignalR/NzbDrone.SignalR.csproj | 5 +- src/NzbDrone.SignalR/app.config | 2 +- src/NzbDrone.SignalR/packages.config | 2 +- src/NzbDrone.Test.Common/App.config | 2 +- .../NzbDrone.Test.Common.csproj | 5 +- src/NzbDrone.Test.Common/packages.config | 2 +- src/NzbDrone.Update/NzbDrone.Update.csproj | 5 +- src/NzbDrone.Update/app.config | 2 +- src/NzbDrone.Update/packages.config | 2 +- src/NzbDrone.Windows.Test/app.config | 2 +- src/NzbDrone/NzbDrone.csproj | 5 +- src/NzbDrone/packages.config | 2 +- 48 files changed, 768 insertions(+), 69 deletions(-) create mode 100644 src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Extensions/SentryLoggerExtensions.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/MachineNameUserFactory.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/RadarrJsonPacketFactory.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/RadarrSentryPacket.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/SentryDebounce.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs create mode 100644 src/NzbDrone.Common/Serializer/JsonVisitor.cs 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