From f2fdf50b3b7138f47e1777e31ef97b737775fb63 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 19:07:34 +0100 Subject: [PATCH 1/9] Create separate constants for the two logging file names --- Jellyfin.Server/Program.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0271861054..970443c8b6 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -39,9 +39,14 @@ namespace Jellyfin.Server public static class Program { /// - /// The name of logging configuration file. + /// The name of logging configuration file containing application defaults. /// - public static readonly string LoggingConfigFile = "logging.json"; + public static readonly string LoggingConfigFileDefault = "logging.default.json"; + + /// + /// The name of the logging configuration file containing user override settings. + /// + public static readonly string LoggingConfigFileUser = "logging.user.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); @@ -443,7 +448,7 @@ namespace Jellyfin.Server private static async Task CreateConfiguration(IApplicationPaths appPaths) { const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; - string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFile); + string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault); if (!File.Exists(configPath)) { @@ -465,7 +470,7 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) - .AddJsonFile(LoggingConfigFile, false, true) + .AddJsonFile(LoggingConfigFileDefault, false, true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From 1a9908d094a83b66c1b56e78bc496a1ff056c7d9 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 19:11:42 +0100 Subject: [PATCH 2/9] Add migration to create "logging.user.json" --- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Routines/CreateUserLoggingConfigFile.cs | 93 +++++++++++++++++++ .../Routines/DisableZealousLogging.cs | 29 ------ 3 files changed, 95 insertions(+), 30 deletions(-) create mode 100644 Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs delete mode 100644 Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index caaa58ae15..ac7f3d77ae 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -15,7 +15,8 @@ namespace Jellyfin.Server.Migrations /// internal static readonly IUpdater[] Migrations = { - new Routines.DisableTranscodingThrottling() + new Routines.DisableTranscodingThrottling(), + new Routines.CreateUserLoggingConfigFile() }; /// diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs new file mode 100644 index 0000000000..7a089680e4 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Common.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// Migration to initialize the user logging configuration file "logging.user.json". + /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json, + /// otherwise a blank file will be created. + /// + internal class CreateUserLoggingConfigFile : IUpdater + { + /// + /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file. + /// + private const string EmptyLoggingConfig = @"{ ""Serilog"": { } }"; + + /// + /// File history for logging.json as existed during this migration creation. The contents for each has been minified. + /// + private readonly List _defaultConfigHistory = new List + { + // 9a6c27947353585391e211aa88b925f81e8cd7b9 + @"{""Serilog"":{""MinimumLevel"":{""Default"":""Information"",""Override"":{""Microsoft"":""Warning"",""System"":""Warning""}},""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // 71bdcd730705a714ee208eaad7290b7c68df3885 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // a44936f97f8afc2817d3491615a7cfe1e31c251c + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}", + // 7af3754a11ad5a4284f107997fb5419a010ce6f3 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}", + // 60691349a11f541958e0b2247c9abc13cb40c9fb + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}", + // 65fe243afbcc4b596cf8726708c1965cd34b5f68 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {ThreadId} {SourceContext}: {Message:lj} {NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {ThreadId} {SourceContext}:{Message} {NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + // 96c9af590494aa8137d5a061aaf1e68feee60b67 + @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", + }; + + /// + public string Name => "CreateLoggingConfigHeirarchy"; + + /// + public void Perform(CoreAppHost host, ILogger logger) + { + var logDirectory = host.Resolve().ConfigurationDirectoryPath; + var oldConfigPath = Path.Combine(logDirectory, "logging.json"); + var userConfigPath = Path.Combine(logDirectory, Program.LoggingConfigFileUser); + + // Check if there are existing settings in the old "logging.json" file that should be migrated + bool shouldMigrateOldFile = ShouldKeepOldConfig(oldConfigPath); + + // Create the user settings file "logging.user.json" + if (shouldMigrateOldFile) + { + // Use the existing logging.json file + File.Copy(oldConfigPath, userConfigPath); + } + else + { + // Write an empty JSON file + File.WriteAllText(userConfigPath, EmptyLoggingConfig); + } + } + + /// + /// Check if the existing logging.json file should be migrated to logging.user.json. + /// + private bool ShouldKeepOldConfig(string oldConfigPath) + { + // Cannot keep the old logging file if it doesn't exist + if (!File.Exists(oldConfigPath)) + { + return false; + } + + // Check if the existing logging.json file has been modified by the user by comparing it to all the + // versions in our git history. Until now, the file has never been migrated after first creation so users + // could have any version from the git history. + var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); + var existingConfigIsUnmodified = _defaultConfigHistory + .Select(historicalConfigText => JToken.Parse(historicalConfigText)) + .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); + + // The existing config file should be kept and used only if it has been modified by the user + return !existingConfigIsUnmodified; + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs b/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs deleted file mode 100644 index 501f8f8654..0000000000 --- a/Jellyfin.Server/Migrations/Routines/DisableZealousLogging.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.IO; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Serilog; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Jellyfin.Server.Migrations.Routines -{ - /// - /// Updater that takes care of bringing configuration up to 10.5.0 standards. - /// - internal class DisableZealousLogging : IUpdater - { - /// - public string Name => "DisableZealousLogging"; - - /// - // This tones down logging from some components - public void Perform(CoreAppHost host, ILogger logger) - { - string configPath = Path.Combine(host.ServerConfigurationManager.ApplicationPaths.ConfigurationDirectoryPath, Program.LoggingConfigFile); - // TODO: fix up the config - throw new NotImplementedException("don't know how to fix logging yet"); - } - } -} From 6660006f01aee44ea33d1539000c5e4ea06e1115 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 19:28:36 +0100 Subject: [PATCH 3/9] Load user logging config file into application configuration --- Jellyfin.Server/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 970443c8b6..2590fdb215 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -470,7 +470,8 @@ namespace Jellyfin.Server return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) - .AddJsonFile(LoggingConfigFileDefault, false, true) + .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) + .AddJsonFile(LoggingConfigFileUser, optional: true, reloadOnChange: true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From 4c2b543b307b55b2220472c59396b9b4a604cfb7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 6 Mar 2020 21:51:50 +0100 Subject: [PATCH 4/9] Rename IUpdater to IMigrationRoutine --- .../{IUpdater.cs => IMigrationRoutine.cs} | 8 ++++---- Jellyfin.Server/Migrations/MigrationRunner.cs | 18 +++++++++--------- .../Routines/CreateUserLoggingConfigFile.cs | 2 +- .../Routines/DisableTranscodingThrottling.cs | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) rename Jellyfin.Server/Migrations/{IUpdater.cs => IMigrationRoutine.cs} (69%) diff --git a/Jellyfin.Server/Migrations/IUpdater.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs similarity index 69% rename from Jellyfin.Server/Migrations/IUpdater.cs rename to Jellyfin.Server/Migrations/IMigrationRoutine.cs index 9b749841cf..20a3aa3d6a 100644 --- a/Jellyfin.Server/Migrations/IUpdater.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -4,20 +4,20 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { /// - /// Interface that descibes a migration routine. + /// Interface that describes a migration routine. /// - internal interface IUpdater + internal interface IMigrationRoutine { /// /// Gets the name of the migration, must be unique. /// - public abstract string Name { get; } + public string Name { get; } /// /// Execute the migration routine. /// /// Host that hosts current version. /// Host logger. - public abstract void Perform(CoreAppHost host, ILogger logger); + public void Perform(CoreAppHost host, ILogger logger); } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index ac7f3d77ae..8e786f34ea 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Migrations /// /// The list of known migrations, in order of applicability. /// - internal static readonly IUpdater[] Migrations = + internal static readonly IMigrationRoutine[] Migrations = { new Routines.DisableTranscodingThrottling(), new Routines.CreateUserLoggingConfigFile() @@ -43,26 +43,26 @@ namespace Jellyfin.Server.Migrations for (var i = 0; i < Migrations.Length; i++) { - var updater = Migrations[i]; - if (applied.Contains(updater.Name)) + var migrationRoutine = Migrations[i]; + if (applied.Contains(migrationRoutine.Name)) { - logger.LogDebug("Skipping migration {Name} as it is already applied", updater.Name); + logger.LogDebug("Skipping migration {Name} as it is already applied", migrationRoutine.Name); continue; } - logger.LogInformation("Applying migration {Name}", updater.Name); + logger.LogInformation("Applying migration {Name}", migrationRoutine.Name); try { - updater.Perform(host, logger); + migrationRoutine.Perform(host, logger); } catch (Exception ex) { - logger.LogError(ex, "Cannot apply migration {Name}", updater.Name); + logger.LogError(ex, "Cannot apply migration {Name}", migrationRoutine.Name); continue; } - logger.LogInformation("Migration {Name} applied successfully", updater.Name); - applied.Add(updater.Name); + logger.LogInformation("Migration {Name} applied successfully", migrationRoutine.Name); + applied.Add(migrationRoutine.Name); } if (applied.Count > migrationOptions.Applied.Length) diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 7a089680e4..6dbeb27761 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Migrations.Routines /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json, /// otherwise a blank file will be created. /// - internal class CreateUserLoggingConfigFile : IUpdater + internal class CreateUserLoggingConfigFile : IMigrationRoutine { /// /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file. diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index eff6469e20..db0bef8851 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Server.Migrations.Routines /// /// Updater that takes care of bringing configuration up to 10.5.0 standards. /// - internal class DisableTranscodingThrottling : IUpdater + internal class DisableTranscodingThrottling : IMigrationRoutine { /// public string Name => "DisableTranscodingThrottling"; From a0fdceb4bcf672bb87bed9f89921f60e8213080b Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 15:02:42 +0100 Subject: [PATCH 5/9] Throw exception on migration failure to halt application Also save migration configuration after each migration instead of at the end in case an exception is thrown part way through the list --- Jellyfin.Server/Migrations/MigrationRunner.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 8bc29d8ac8..0f9c2a3914 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -59,16 +59,12 @@ namespace Jellyfin.Server.Migrations catch (Exception ex) { logger.LogError(ex, "Could not apply migration {Name}", migrationRoutine.Name); - continue; + throw; } + // Mark the migration as completed logger.LogInformation("Migration {Name} applied successfully", migrationRoutine.Name); applied.Add(migrationRoutine.Name); - } - - if (applied.Count > migrationOptions.Applied.Length) - { - logger.LogInformation("Some migrations were run, saving the state"); migrationOptions.Applied = applied.ToArray(); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); } From 2f0b4cc24c84d98820c8a6755191d4fc1dab74e7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 15:02:59 +0100 Subject: [PATCH 6/9] Clean up migration logging messages --- Jellyfin.Server/Migrations/MigrationRunner.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 0f9c2a3914..821f51c02f 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -46,11 +46,11 @@ namespace Jellyfin.Server.Migrations var migrationRoutine = Migrations[i]; if (applied.Contains(migrationRoutine.Name)) { - logger.LogDebug("Skipping migration {Name} as it is already applied", migrationRoutine.Name); + logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); continue; } - logger.LogInformation("Applying migration {Name}", migrationRoutine.Name); + logger.LogInformation("Applying migration '{Name}'", migrationRoutine.Name); try { @@ -58,15 +58,16 @@ namespace Jellyfin.Server.Migrations } catch (Exception ex) { - logger.LogError(ex, "Could not apply migration {Name}", migrationRoutine.Name); + logger.LogError(ex, "Could not apply migration '{Name}'", migrationRoutine.Name); throw; } // Mark the migration as completed - logger.LogInformation("Migration {Name} applied successfully", migrationRoutine.Name); + logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); applied.Add(migrationRoutine.Name); migrationOptions.Applied = applied.ToArray(); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } } } From 8dbb1c92573ba5cf3e4f8d953ffd6083a8ccbde4 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 15:46:13 +0100 Subject: [PATCH 7/9] Use logging.json instead of logging.user.json for override settings --- .../Routines/CreateUserLoggingConfigFile.cs | 45 +++++-------------- Jellyfin.Server/Program.cs | 6 +-- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 6dbeb27761..834099ea05 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -15,11 +15,6 @@ namespace Jellyfin.Server.Migrations.Routines /// internal class CreateUserLoggingConfigFile : IMigrationRoutine { - /// - /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file. - /// - private const string EmptyLoggingConfig = @"{ ""Serilog"": { } }"; - /// /// File history for logging.json as existed during this migration creation. The contents for each has been minified. /// @@ -48,46 +43,28 @@ namespace Jellyfin.Server.Migrations.Routines public void Perform(CoreAppHost host, ILogger logger) { var logDirectory = host.Resolve().ConfigurationDirectoryPath; - var oldConfigPath = Path.Combine(logDirectory, "logging.json"); - var userConfigPath = Path.Combine(logDirectory, Program.LoggingConfigFileUser); + var existingConfigPath = Path.Combine(logDirectory, "logging.json"); - // Check if there are existing settings in the old "logging.json" file that should be migrated - bool shouldMigrateOldFile = ShouldKeepOldConfig(oldConfigPath); - - // Create the user settings file "logging.user.json" - if (shouldMigrateOldFile) - { - // Use the existing logging.json file - File.Copy(oldConfigPath, userConfigPath); - } - else + // If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json' + // NOTE: This config file has 'reloadOnChange: true', so this change will take effect immediately even though it has already been loaded + if (File.Exists(existingConfigPath) && ExistingConfigUnmodified(existingConfigPath)) { - // Write an empty JSON file - File.WriteAllText(userConfigPath, EmptyLoggingConfig); + File.Move(existingConfigPath, Path.Combine(logDirectory, "logging.old.json")); } } /// - /// Check if the existing logging.json file should be migrated to logging.user.json. + /// Check if the existing logging.json file has not been modified by the user by comparing it to all the + /// versions in our git history. Until now, the file has never been migrated after first creation so users + /// could have any version from the git history. /// - private bool ShouldKeepOldConfig(string oldConfigPath) + /// does not exist or could not be read. + private bool ExistingConfigUnmodified(string oldConfigPath) { - // Cannot keep the old logging file if it doesn't exist - if (!File.Exists(oldConfigPath)) - { - return false; - } - - // Check if the existing logging.json file has been modified by the user by comparing it to all the - // versions in our git history. Until now, the file has never been migrated after first creation so users - // could have any version from the git history. var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); - var existingConfigIsUnmodified = _defaultConfigHistory + return _defaultConfigHistory .Select(historicalConfigText => JToken.Parse(historicalConfigText)) .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); - - // The existing config file should be kept and used only if it has been modified by the user - return !existingConfigIsUnmodified; } } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 2590fdb215..7c3d0f2771 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -44,9 +44,9 @@ namespace Jellyfin.Server public static readonly string LoggingConfigFileDefault = "logging.default.json"; /// - /// The name of the logging configuration file containing user override settings. + /// The name of the logging configuration file containing the system-specific override settings. /// - public static readonly string LoggingConfigFileUser = "logging.user.json"; + public static readonly string LoggingConfigFileSystem = "logging.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); @@ -471,7 +471,7 @@ namespace Jellyfin.Server .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.Configuration) .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) - .AddJsonFile(LoggingConfigFileUser, optional: true, reloadOnChange: true) + .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true) .AddEnvironmentVariables("JELLYFIN_") .Build(); } From 72bf920291d1c486aaf66544ccd6eb64c13e254b Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 16:05:31 +0100 Subject: [PATCH 8/9] Use a Guid to uniquely identify migrations instead of a string name Also use a list instead of an array to store executed migrations in the configuration class --- Jellyfin.Server/Migrations/IMigrationRoutine.cs | 7 ++++++- Jellyfin.Server/Migrations/MigrationOptions.cs | 11 ++++++----- Jellyfin.Server/Migrations/MigrationRunner.cs | 11 ++++------- .../Routines/CreateUserLoggingConfigFile.cs | 3 +++ .../Routines/DisableTranscodingThrottling.cs | 3 +++ 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs index 20a3aa3d6a..eab995d67e 100644 --- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -9,7 +9,12 @@ namespace Jellyfin.Server.Migrations internal interface IMigrationRoutine { /// - /// Gets the name of the migration, must be unique. + /// Gets the unique id for this migration. This should never be modified after the migration has been created. + /// + public Guid Id { get; } + + /// + /// Gets the display name of the migration. /// public string Name { get; } diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs index 6b7831158f..d95354145e 100644 --- a/Jellyfin.Server/Migrations/MigrationOptions.cs +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace Jellyfin.Server.Migrations { /// @@ -10,14 +13,12 @@ namespace Jellyfin.Server.Migrations /// public MigrationOptions() { - Applied = System.Array.Empty(); + Applied = new List(); } -#pragma warning disable CA1819 // Properties should not return arrays /// - /// Gets or sets the list of applied migration routine names. + /// Gets the list of applied migration routine names. /// - public string[] Applied { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays + public List Applied { get; } } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 821f51c02f..2081143d20 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -29,22 +29,20 @@ namespace Jellyfin.Server.Migrations var logger = loggerFactory.CreateLogger(); var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); - if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Length == 0) + if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) { // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. logger.LogInformation("Marking all known migrations as applied because this is fresh install"); - migrationOptions.Applied = Migrations.Select(m => m.Name).ToArray(); + migrationOptions.Applied.AddRange(Migrations.Select(m => m.Id)); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); return; } - var applied = migrationOptions.Applied.ToList(); - for (var i = 0; i < Migrations.Length; i++) { var migrationRoutine = Migrations[i]; - if (applied.Contains(migrationRoutine.Name)) + if (migrationOptions.Applied.Contains(migrationRoutine.Id)) { logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); continue; @@ -64,8 +62,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); - applied.Add(migrationRoutine.Name); - migrationOptions.Applied = applied.ToArray(); + migrationOptions.Applied.Add(migrationRoutine.Id); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 834099ea05..3bc32c0478 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -36,6 +36,9 @@ namespace Jellyfin.Server.Migrations.Routines @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", }; + /// + public Guid Id => Guid.Parse("{EF103419-8451-40D8-9F34-D1A8E93A1679}"); + /// public string Name => "CreateLoggingConfigHeirarchy"; diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index 279e7bbea5..673f0e4155 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -12,6 +12,9 @@ namespace Jellyfin.Server.Migrations.Routines /// internal class DisableTranscodingThrottling : IMigrationRoutine { + /// + public Guid Id => Guid.Parse("{4124C2CD-E939-4FFB-9BE9-9B311C413638}"); + /// public string Name => "DisableTranscodingThrottling"; From 9e89cbbc3ad451b510a00fd7e214f6b942176f47 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 8 Mar 2020 17:40:30 +0100 Subject: [PATCH 9/9] Store migration names alongside Ids in configuration in order to assist with development/debugging --- Jellyfin.Server/Migrations/MigrationOptions.cs | 4 ++-- Jellyfin.Server/Migrations/MigrationRunner.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs index d95354145e..816dd9ee74 100644 --- a/Jellyfin.Server/Migrations/MigrationOptions.cs +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -13,12 +13,12 @@ namespace Jellyfin.Server.Migrations /// public MigrationOptions() { - Applied = new List(); + Applied = new List<(Guid Id, string Name)>(); } /// /// Gets the list of applied migration routine names. /// - public List Applied { get; } + public List<(Guid Id, string Name)> Applied { get; } } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 2081143d20..b5ea04dcac 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -33,16 +33,18 @@ namespace Jellyfin.Server.Migrations { // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. - logger.LogInformation("Marking all known migrations as applied because this is fresh install"); - migrationOptions.Applied.AddRange(Migrations.Select(m => m.Id)); + logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); + migrationOptions.Applied.AddRange(Migrations.Select(m => (m.Id, m.Name))); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); return; } + var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); + for (var i = 0; i < Migrations.Length; i++) { var migrationRoutine = Migrations[i]; - if (migrationOptions.Applied.Contains(migrationRoutine.Id)) + if (appliedMigrationIds.Contains(migrationRoutine.Id)) { logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); continue; @@ -62,7 +64,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); - migrationOptions.Applied.Add(migrationRoutine.Id); + migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name)); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); }