CreateUserLoggingConfigFile.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using MediaBrowser.Common.Configuration;
  6. using Microsoft.Extensions.Logging;
  7. using Newtonsoft.Json.Linq;
  8. namespace Jellyfin.Server.Migrations.Routines
  9. {
  10. /// <summary>
  11. /// Migration to initialize the user logging configuration file "logging.user.json".
  12. /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json,
  13. /// otherwise a blank file will be created.
  14. /// </summary>
  15. internal class CreateUserLoggingConfigFile : IMigrationRoutine
  16. {
  17. /// <summary>
  18. /// An empty logging JSON configuration, which will be used as the default contents for the user settings config file.
  19. /// </summary>
  20. private const string EmptyLoggingConfig = @"{ ""Serilog"": { } }";
  21. /// <summary>
  22. /// File history for logging.json as existed during this migration creation. The contents for each has been minified.
  23. /// </summary>
  24. private readonly List<string> _defaultConfigHistory = new List<string>
  25. {
  26. // 9a6c27947353585391e211aa88b925f81e8cd7b9
  27. @"{""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""]}}",
  28. // 71bdcd730705a714ee208eaad7290b7c68df3885
  29. @"{""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""]}}",
  30. // a44936f97f8afc2817d3491615a7cfe1e31c251c
  31. @"{""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}""}}]}}",
  32. // 7af3754a11ad5a4284f107997fb5419a010ce6f3
  33. @"{""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}""}}]}}]}}",
  34. // 60691349a11f541958e0b2247c9abc13cb40c9fb
  35. @"{""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}""}}]}}]}}",
  36. // 65fe243afbcc4b596cf8726708c1965cd34b5f68
  37. @"{""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""]}}",
  38. // 96c9af590494aa8137d5a061aaf1e68feee60b67
  39. @"{""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""]}}",
  40. };
  41. /// <inheritdoc/>
  42. public string Name => "CreateLoggingConfigHeirarchy";
  43. /// <inheritdoc/>
  44. public void Perform(CoreAppHost host, ILogger logger)
  45. {
  46. var logDirectory = host.Resolve<IApplicationPaths>().ConfigurationDirectoryPath;
  47. var oldConfigPath = Path.Combine(logDirectory, "logging.json");
  48. var userConfigPath = Path.Combine(logDirectory, Program.LoggingConfigFileUser);
  49. // Check if there are existing settings in the old "logging.json" file that should be migrated
  50. bool shouldMigrateOldFile = ShouldKeepOldConfig(oldConfigPath);
  51. // Create the user settings file "logging.user.json"
  52. if (shouldMigrateOldFile)
  53. {
  54. // Use the existing logging.json file
  55. File.Copy(oldConfigPath, userConfigPath);
  56. }
  57. else
  58. {
  59. // Write an empty JSON file
  60. File.WriteAllText(userConfigPath, EmptyLoggingConfig);
  61. }
  62. }
  63. /// <summary>
  64. /// Check if the existing logging.json file should be migrated to logging.user.json.
  65. /// </summary>
  66. private bool ShouldKeepOldConfig(string oldConfigPath)
  67. {
  68. // Cannot keep the old logging file if it doesn't exist
  69. if (!File.Exists(oldConfigPath))
  70. {
  71. return false;
  72. }
  73. // Check if the existing logging.json file has been modified by the user by comparing it to all the
  74. // versions in our git history. Until now, the file has never been migrated after first creation so users
  75. // could have any version from the git history.
  76. var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath));
  77. var existingConfigIsUnmodified = _defaultConfigHistory
  78. .Select(historicalConfigText => JToken.Parse(historicalConfigText))
  79. .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson));
  80. // The existing config file should be kept and used only if it has been modified by the user
  81. return !existingConfigIsUnmodified;
  82. }
  83. }
  84. }