diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4b1904b08..4da8e5095 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -99,6 +99,35 @@ $(MSBuildProjectName.Replace('Readarr','NzbDrone')) + + + + + + + + + + + + + + + + + + true + + + + true + + true + + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 6e456510b..21be647e6 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -43,7 +43,7 @@ - + diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs index a2400c55b..2c730bd93 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs @@ -4,6 +4,7 @@ using System.Linq; using FluentAssertions; using NLog; using NUnit.Framework; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation.Sentry; using NzbDrone.Test.Common; @@ -27,7 +28,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests [SetUp] public void Setup() { - _subject = new SentryTarget("https://aaaaaaaaaaaaaaaaaaaaaaaaaa@sentry.io/111111"); + _subject = new SentryTarget("https://aaaaaaaaaaaaaaaaaaaaaaaaaa@sentry.io/111111", Mocker.GetMock().Object); } private LogEventInfo GivenLogEvent(LogLevel level, Exception ex, string message) diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs index de463c442..22617e7e9 100644 --- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs +++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs @@ -41,7 +41,7 @@ namespace NzbDrone.Common.Instrumentation RegisterDebugger(); } - RegisterSentry(updateApp); + RegisterSentry(updateApp, appFolderInfo); if (updateApp) { @@ -62,7 +62,7 @@ namespace NzbDrone.Common.Instrumentation LogManager.ReconfigExistingLoggers(); } - private static void RegisterSentry(bool updateClient) + private static void RegisterSentry(bool updateClient, IAppFolderInfo appFolderInfo) { string dsn; @@ -77,7 +77,7 @@ namespace NzbDrone.Common.Instrumentation : "https://31e00a6c63ea42c8b5fe70358526a30d@sentry.servarr.com/4"; } - var target = new SentryTarget(dsn) + var target = new SentryTarget(dsn, appFolderInfo) { Name = "sentryTarget", Layout = "${message}" diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index 83b5b7eb7..494614fd4 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -9,6 +9,7 @@ using NLog; using NLog.Common; using NLog.Targets; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; using Sentry; namespace NzbDrone.Common.Instrumentation.Sentry @@ -99,7 +100,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry public bool FilterEvents { get; set; } public bool SentryEnabled { get; set; } - public SentryTarget(string dsn) + public SentryTarget(string dsn, IAppFolderInfo appFolderInfo) { _sdk = SentrySdk.Init(o => { @@ -107,9 +108,33 @@ namespace NzbDrone.Common.Instrumentation.Sentry o.AttachStacktrace = true; o.MaxBreadcrumbs = 200; o.Release = $"{BuildInfo.AppName}@{BuildInfo.Release}"; - o.BeforeSend = x => SentryCleanser.CleanseEvent(x); - o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x); + o.SetBeforeSend(x => SentryCleanser.CleanseEvent(x)); + o.SetBeforeBreadcrumb(x => SentryCleanser.CleanseBreadcrumb(x)); o.Environment = BuildInfo.Branch; + + // Crash free run statistics (sends a ping for healthy and for crashes sessions) + o.AutoSessionTracking = true; + + // Caches files in the event device is offline + // Sentry creates a 'sentry' sub directory, no need to concat here + o.CacheDirectoryPath = appFolderInfo.GetAppDataPath(); + + // default environment is production + if (!RuntimeInfo.IsProduction) + { + if (RuntimeInfo.IsDevelopment) + { + o.Environment = "development"; + } + else if (RuntimeInfo.IsTesting) + { + o.Environment = "testing"; + } + else + { + o.Environment = "other"; + } + } }); InitializeScope(); @@ -127,7 +152,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry { SentrySdk.ConfigureScope(scope => { - scope.User = new User + scope.User = new SentryUser { Id = HashUtil.AnonymousToken() }; @@ -169,9 +194,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry private void OnError(Exception ex) { - var webException = ex as WebException; - - if (webException != null) + if (ex is WebException webException) { var response = webException.Response as HttpWebResponse; var statusCode = response?.StatusCode; @@ -290,13 +313,21 @@ namespace NzbDrone.Common.Instrumentation.Sentry } } + var level = LoggingLevelMap[logEvent.Level]; var sentryEvent = new SentryEvent(logEvent.Exception) { - Level = LoggingLevelMap[logEvent.Level], + Level = level, Logger = logEvent.LoggerName, Message = logEvent.FormattedMessage }; + if (level is SentryLevel.Fatal && logEvent.Exception is not null) + { + // Usages of 'fatal' here indicates the process will crash. In Sentry this is represented with + // the 'unhandled' exception flag + logEvent.Exception.SetSentryMechanism("Logger.Fatal", "Logger.Fatal was called", false); + } + sentryEvent.SetExtras(extras); sentryEvent.SetFingerprint(fingerPrint);