using System; using System.Diagnostics; using System.IO; using NLog; using NLog.Config; using NLog.Targets; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Sentry; namespace NzbDrone.Common.Instrumentation { public static class NzbDroneLogger { private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}"; private static bool _isConfigured; static NzbDroneLogger() { LogManager.Configuration = new LoggingConfiguration(); } public static void Register(IStartupContext startupContext, bool updateApp, bool inConsole) { if (_isConfigured) { throw new InvalidOperationException("Loggers have already been registered."); } _isConfigured = true; GlobalExceptionHandlers.Register(); var appFolderInfo = new AppFolderInfo(startupContext); if (Debugger.IsAttached) { RegisterDebugger(); } RegisterSentry(updateApp, appFolderInfo); if (updateApp) { RegisterUpdateFile(appFolderInfo); } else { if (inConsole && (OsInfo.IsNotWindows || RuntimeInfo.IsUserInteractive)) { RegisterConsole(); } RegisterAppFile(appFolderInfo); } RegisterAuthLogger(); LogManager.ReconfigExistingLoggers(); } private static void RegisterSentry(bool updateClient, IAppFolderInfo appFolderInfo) { string dsn; if (updateClient) { dsn = RuntimeInfo.IsProduction ? "https://80777986b95f44a1a90d1eb2f3af1e36@sentry.sonarr.tv/11" : "https://6168f0946aba4e60ac23e469ac08eac5@sentry.sonarr.tv/9"; } else { dsn = RuntimeInfo.IsProduction ? "https://e2adcbe52caf46aeaebb6b1dcdfe10a1@sentry.sonarr.tv/8" : "https://4ee3580e01d8407c96a7430fbc953512@sentry.sonarr.tv/10"; } Target target; try { target = new SentryTarget(dsn, appFolderInfo) { Name = "sentryTarget", Layout = "${message}" }; } catch (Exception ex) { Console.WriteLine("Failed to load dependency, may need an OS update: " + ex.ToString()); LogManager.GetLogger(nameof(NzbDroneLogger)).Debug(ex, "Failed to load dependency, may need an OS update"); // We still need the logging rules, so use a null target. target = new NullTarget(); } var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Warn, 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() { var target = new DebuggerTarget(); target.Name = "debuggerLogger"; target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}"; var loggingRule = new LoggingRule("*", LogLevel.Trace, target); LogManager.Configuration.AddTarget("debugger", target); LogManager.Configuration.LoggingRules.Add(loggingRule); } private static void RegisterConsole() { var level = LogLevel.Trace; var coloredConsoleTarget = new ColoredConsoleTarget(); coloredConsoleTarget.Name = "consoleLogger"; coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}"; var loggingRule = new LoggingRule("*", level, coloredConsoleTarget); LogManager.Configuration.AddTarget("console", coloredConsoleTarget); LogManager.Configuration.LoggingRules.Add(loggingRule); } private static void RegisterAppFile(IAppFolderInfo appFolderInfo) { RegisterAppFile(appFolderInfo, "appFileInfo", "sonarr.txt", 5, LogLevel.Info); RegisterAppFile(appFolderInfo, "appFileDebug", "sonarr.debug.txt", 50, LogLevel.Off); RegisterAppFile(appFolderInfo, "appFileTrace", "sonarr.trace.txt", 50, LogLevel.Off); } private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel) { var fileTarget = new NzbDroneFileTarget(); fileTarget.Name = name; fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName); fileTarget.AutoFlush = true; fileTarget.KeepFileOpen = false; fileTarget.ConcurrentWrites = false; fileTarget.ConcurrentWriteAttemptDelay = 50; fileTarget.ConcurrentWriteAttempts = 10; fileTarget.ArchiveAboveSize = 1024000; fileTarget.MaxArchiveFiles = maxArchiveFiles; fileTarget.EnableFileDelete = true; fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling; fileTarget.Layout = FILE_LOG_LAYOUT; var loggingRule = new LoggingRule("*", minLogLevel, fileTarget); LogManager.Configuration.AddTarget(name, fileTarget); LogManager.Configuration.LoggingRules.Add(loggingRule); } private static void RegisterUpdateFile(IAppFolderInfo appFolderInfo) { var fileTarget = new FileTarget(); fileTarget.Name = "updateFileLogger"; fileTarget.FileName = Path.Combine(appFolderInfo.GetUpdateLogFolder(), DateTime.Now.ToString("yyyy.MM.dd-HH.mm") + ".txt"); fileTarget.AutoFlush = true; fileTarget.KeepFileOpen = false; fileTarget.ConcurrentWrites = false; fileTarget.ConcurrentWriteAttemptDelay = 50; fileTarget.ConcurrentWriteAttempts = 100; fileTarget.Layout = FILE_LOG_LAYOUT; var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget); LogManager.Configuration.AddTarget("updateFile", fileTarget); LogManager.Configuration.LoggingRules.Add(loggingRule); } private static void RegisterAuthLogger() { var consoleTarget = LogManager.Configuration.FindTargetByName("console"); var fileTarget = LogManager.Configuration.FindTargetByName("appFileInfo"); var target = consoleTarget ?? fileTarget ?? new NullTarget(); // Send Auth to Console and info app file, but not the log database var rule = new LoggingRule("Auth", LogLevel.Info, target) { Final = true }; if (consoleTarget != null && fileTarget != null) { rule.Targets.Add(fileTarget); } LogManager.Configuration.LoggingRules.Insert(0, rule); } public static Logger GetLogger(Type obj) { return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", "")); } public static Logger GetLogger(object obj) { return GetLogger(obj.GetType()); } } }