From 88332e89c458266bc073d3304eafcb23603f15fa Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 5 Jun 2025 17:59:11 +0300 Subject: Feature/version check in library migration (#14105) --- .../Routines/MigrateLibraryDbCompatibilityCheck.cs | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs (limited to 'Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs') diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs new file mode 100644 index 000000000..2d5fc2a0d --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs @@ -0,0 +1,73 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Server.ServerSetupApp; +using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// The migration routine for checking if the current instance of Jellyfin is compatiable to be upgraded. +/// +[JellyfinMigration("2025-04-20T19:30:00", nameof(MigrateLibraryDbCompatibilityCheck))] +public class MigrateLibraryDbCompatibilityCheck : IAsyncMigrationRoutine +{ + private const string DbFilename = "library.db"; + private readonly IStartupLogger _logger; + private readonly IServerApplicationPaths _paths; + + /// + /// Initializes a new instance of the class. + /// + /// The startup logger. + /// The Path service. + public MigrateLibraryDbCompatibilityCheck(IStartupLogger startupLogger, IServerApplicationPaths paths) + { + _logger = startupLogger; + _paths = paths; + } + + /// + public async Task PerformAsync(CancellationToken cancellationToken) + { + var dataPath = _paths.DataPath; + var libraryDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(libraryDbPath)) + { + _logger.LogError("Cannot migrate {LibraryDb} as it does not exist..", libraryDbPath); + return; + } + + using var connection = new SqliteConnection($"Filename={libraryDbPath};Mode=ReadOnly"); + await connection.OpenAsync(cancellationToken).ConfigureAwait(false); + CheckMigratableVersion(connection); + await connection.CloseAsync().ConfigureAwait(false); + } + + private static void CheckMigratableVersion(SqliteConnection connection) + { + CheckColumnExistance(connection, "TypedBaseItems", "lufs"); + CheckColumnExistance(connection, "TypedBaseItems", "normalizationgain"); + CheckColumnExistance(connection, "mediastreams", "dvversionmajor"); + + static void CheckColumnExistance(SqliteConnection connection, string table, string column) + { + using (var cmd = connection.CreateCommand()) + { +#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities + cmd.CommandText = $"Select COUNT(1) FROM pragma_table_xinfo('{table}') WHERE lower(name) = '{column}';"; +#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities + var result = cmd.ExecuteScalar()!; + if (!result.Equals(1L)) + { + throw new InvalidOperationException("Your database does not meet the required standard. Only upgrades from server version 10.9.11 or above are supported. Please upgrade first to server version 10.10.7 before attempting to upgrade afterwards to 10.11"); + } + } + } + } +} -- cgit v1.2.3 From 1e9e4ffda9abe30b71ceb1de2f4c3143805c66a9 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Mon, 9 Jun 2025 04:52:39 +0300 Subject: Rework startup topic handling and reenable output to logging framework (#14243) --- .../Migrations/JellyfinMigrationService.cs | 2 +- .../Migrations/Routines/MigrateKeyframeData.cs | 2 +- .../Migrations/Routines/MigrateLibraryDb.cs | 2 +- .../Routines/MigrateLibraryDbCompatibilityCheck.cs | 2 +- .../Migrations/Routines/MigrateRatingLevels.cs | 2 +- .../Migrations/Routines/MoveExtractedFiles.cs | 2 +- .../Migrations/Routines/MoveTrickplayFiles.cs | 2 +- Jellyfin.Server/Migrations/Stages/CodeMigration.cs | 18 +++-- Jellyfin.Server/Program.cs | 11 +-- Jellyfin.Server/ServerSetupApp/IStartupLogger.cs | 43 +++++++++++- Jellyfin.Server/ServerSetupApp/SetupServer.cs | 17 +---- Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs | 31 +++++++++ Jellyfin.Server/ServerSetupApp/StartupLogger.cs | 78 ++++++++++++++-------- .../ServerSetupApp/StartupLoggerExtensions.cs | 18 +++++ .../ServerSetupApp/StartupLoggerOfCategory.cs | 56 ++++++++++++++++ .../JellyfinApplicationFactory.cs | 29 +++++++- 16 files changed, 255 insertions(+), 60 deletions(-) create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs create mode 100644 Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs (limited to 'Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs') diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs index 5331b43e3..31a220118 100644 --- a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs +++ b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs @@ -47,7 +47,7 @@ internal class JellyfinMigrationService public JellyfinMigrationService( IDbContextFactory dbContextFactory, ILoggerFactory loggerFactory, - IStartupLogger startupLogger, + IStartupLogger startupLogger, IApplicationPaths applicationPaths, IBackupService? backupService = null, IJellyfinDatabaseProvider? jellyfinDatabaseProvider = null) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs index 033045e63..c199ee4d6 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs @@ -35,7 +35,7 @@ public class MigrateKeyframeData : IDatabaseMigrationRoutine /// Instance of the interface. /// The EFCore db factory. public MigrateKeyframeData( - IStartupLogger startupLogger, + IStartupLogger startupLogger, IApplicationPaths appPaths, IDbContextFactory dbProvider) { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs index 521655a4f..0953030fa 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs @@ -48,7 +48,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine /// The server application paths. /// The database provider for special access. public MigrateLibraryDb( - IStartupLogger startupLogger, + IStartupLogger startupLogger, IDbContextFactory provider, IServerApplicationPaths paths, IJellyfinDatabaseProvider jellyfinDatabaseProvider) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs index 2d5fc2a0d..d4cc9bbee 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDbCompatibilityCheck.cs @@ -26,7 +26,7 @@ public class MigrateLibraryDbCompatibilityCheck : IAsyncMigrationRoutine /// /// The startup logger. /// The Path service. - public MigrateLibraryDbCompatibilityCheck(IStartupLogger startupLogger, IServerApplicationPaths paths) + public MigrateLibraryDbCompatibilityCheck(IStartupLogger startupLogger, IServerApplicationPaths paths) { _logger = startupLogger; _paths = paths; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs index ae93557de..2a6db01cf 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs @@ -23,7 +23,7 @@ internal class MigrateRatingLevels : IDatabaseMigrationRoutine public MigrateRatingLevels( IDbContextFactory provider, - IStartupLogger logger, + IStartupLogger logger, ILocalizationManager localizationManager) { _provider = provider; diff --git a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs index 6f650f731..8b394dd7a 100644 --- a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs +++ b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs @@ -47,7 +47,7 @@ public class MoveExtractedFiles : IAsyncMigrationRoutine public MoveExtractedFiles( IApplicationPaths appPaths, ILogger logger, - IStartupLogger startupLogger, + IStartupLogger startupLogger, IPathManager pathManager, IFileSystem fileSystem, IDbContextFactory dbProvider) diff --git a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs index a674aa928..0f55465e8 100644 --- a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs +++ b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs @@ -37,7 +37,7 @@ public class MoveTrickplayFiles : IMigrationRoutine ITrickplayManager trickplayManager, IFileSystem fileSystem, ILibraryManager libraryManager, - IStartupLogger logger) + IStartupLogger logger) { _trickplayManager = trickplayManager; _fileSystem = fileSystem; diff --git a/Jellyfin.Server/Migrations/Stages/CodeMigration.cs b/Jellyfin.Server/Migrations/Stages/CodeMigration.cs index 47ed26965..c3592f62a 100644 --- a/Jellyfin.Server/Migrations/Stages/CodeMigration.cs +++ b/Jellyfin.Server/Migrations/Stages/CodeMigration.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Jellyfin.Server.ServerSetupApp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.Stages; @@ -21,11 +22,13 @@ internal class CodeMigration(Type migrationType, JellyfinMigrationAttribute meta return Metadata.Order.ToString("yyyyMMddHHmmsss", CultureInfo.InvariantCulture) + "_" + Metadata.Name!; } - private ServiceCollection MigrationServices(IServiceProvider serviceProvider, IStartupLogger logger) + private IServiceCollection MigrationServices(IServiceProvider serviceProvider, IStartupLogger logger) { - var childServiceCollection = new ServiceCollection(); - childServiceCollection.AddSingleton(serviceProvider); - childServiceCollection.AddSingleton(logger); + var childServiceCollection = new ServiceCollection() + .AddSingleton(serviceProvider) + .AddSingleton(logger) + .AddSingleton(typeof(IStartupLogger<>), typeof(NestedStartupLogger<>)) + .AddSingleton(logger.Topic!); foreach (ServiceDescriptor service in serviceProvider.GetRequiredService()) { @@ -78,4 +81,11 @@ internal class CodeMigration(Type migrationType, JellyfinMigrationAttribute meta throw new InvalidOperationException($"The type {MigrationType} does not implement either IMigrationRoutine or IAsyncMigrationRoutine and is not a valid migration type"); } } + + private class NestedStartupLogger : StartupLogger, IStartupLogger + { + public NestedStartupLogger(ILogger logger, StartupLogTopic topic) : base(logger, topic) + { + } + } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0b77d63ac..dc7fa5eb3 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -60,7 +60,7 @@ namespace Jellyfin.Server private static long _startTimestamp; private static ILogger _logger = NullLogger.Instance; private static bool _restartOnShutdown; - private static IStartupLogger? _migrationLogger; + private static IStartupLogger? _migrationLogger; private static string? _restoreFromBackup; /// @@ -103,6 +103,7 @@ namespace Jellyfin.Server _setupServer = new SetupServer(static () => _jellyfinHost?.Services?.GetService(), appPaths, static () => _appHost, _loggerFactory, startupConfig); await _setupServer.RunAsync().ConfigureAwait(false); _logger = _loggerFactory.CreateLogger("Main"); + StartupLogger.Logger = new StartupLogger(_logger); // Use the logging framework for uncaught exceptions instead of std error AppDomain.CurrentDomain.UnhandledException += (_, e) @@ -178,7 +179,9 @@ namespace Jellyfin.Server }) .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig)) .UseSerilog() - .ConfigureServices(e => e.AddTransient().AddSingleton(e)) + .ConfigureServices(e => e + .RegisterStartupLogger() + .AddSingleton(e)) .Build(); // Re-use the host service provider in the app host since ASP.NET doesn't allow a custom service collection. @@ -268,7 +271,7 @@ namespace Jellyfin.Server /// A task. public static async Task ApplyStartupMigrationAsync(ServerApplicationPaths appPaths, IConfiguration startupConfig) { - _migrationLogger = StartupLogger.Logger.BeginGroup($"Migration Service"); + _migrationLogger = StartupLogger.Logger.BeginGroup($"Migration Service"); var startupConfigurationManager = new ServerConfigurationManager(appPaths, _loggerFactory, new MyXmlSerializer()); startupConfigurationManager.AddParts([new DatabaseConfigurationFactory()]); var migrationStartupServiceProvider = new ServiceCollection() @@ -276,7 +279,7 @@ namespace Jellyfin.Server .AddJellyfinDbContext(startupConfigurationManager, startupConfig) .AddSingleton(appPaths) .AddSingleton(appPaths) - .AddSingleton(_migrationLogger); + .RegisterStartupLogger(); migrationStartupServiceProvider.AddSingleton(migrationStartupServiceProvider); var startupService = migrationStartupServiceProvider.BuildServiceProvider(); diff --git a/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs b/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs index 2c2ef05f8..e7c193936 100644 --- a/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs +++ b/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs @@ -1,5 +1,4 @@ using System; -using Morestachio.Helper.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Jellyfin.Server.ServerSetupApp; @@ -9,6 +8,11 @@ namespace Jellyfin.Server.ServerSetupApp; /// public interface IStartupLogger : ILogger { + /// + /// Gets the topic this logger is assigned to. + /// + StartupLogTopic? Topic { get; } + /// /// Adds another logger instance to this logger for combined logging. /// @@ -22,4 +26,41 @@ public interface IStartupLogger : ILogger /// Defines the log message that introduces the new group. /// A new logger that can write to the group. IStartupLogger BeginGroup(FormattableString logEntry); + + /// + /// Adds another logger instance to this logger for combined logging. + /// + /// Other logger to rely messages to. + /// A combined logger. + /// The logger cateogry. + IStartupLogger With(ILogger logger); + + /// + /// Opens a new Group logger within the parent logger. + /// + /// Defines the log message that introduces the new group. + /// A new logger that can write to the group. + /// The logger cateogry. + IStartupLogger BeginGroup(FormattableString logEntry); +} + +/// +/// Defines a logger that can be injected via DI to get a startup logger initialised with an logger framework connected . +/// +/// The logger cateogry. +public interface IStartupLogger : IStartupLogger +{ + /// + /// Adds another logger instance to this logger for combined logging. + /// + /// Other logger to rely messages to. + /// A combined logger. + new IStartupLogger With(ILogger logger); + + /// + /// Opens a new Group logger within the parent logger. + /// + /// Defines the log message that introduces the new group. + /// A new logger that can write to the group. + new IStartupLogger BeginGroup(FormattableString logEntry); } diff --git a/Jellyfin.Server/ServerSetupApp/SetupServer.cs b/Jellyfin.Server/ServerSetupApp/SetupServer.cs index d88dbee57..6d58e3c4e 100644 --- a/Jellyfin.Server/ServerSetupApp/SetupServer.cs +++ b/Jellyfin.Server/ServerSetupApp/SetupServer.cs @@ -71,7 +71,7 @@ public sealed class SetupServer : IDisposable _configurationManager.RegisterConfiguration(); } - internal static ConcurrentQueue? LogQueue { get; set; } = new(); + internal static ConcurrentQueue? LogQueue { get; set; } = new(); /// /// Gets a value indicating whether Startup server is currently running. @@ -88,12 +88,12 @@ public sealed class SetupServer : IDisposable _startupUiRenderer = (await ParserOptionsBuilder.New() .WithTemplate(fileTemplate) .WithFormatter( - (StartupLogEntry logEntry, IEnumerable children) => + (StartupLogTopic logEntry, IEnumerable children) => { if (children.Any()) { var maxLevel = logEntry.LogLevel; - var stack = new Stack(children); + var stack = new Stack(children); while (maxLevel != LogLevel.Error && stack.Count > 0 && (logEntry = stack.Pop()) != null) // error is the highest inherted error level. { @@ -362,15 +362,4 @@ public sealed class SetupServer : IDisposable }); } } - - internal class StartupLogEntry - { - public LogLevel LogLevel { get; set; } - - public string? Content { get; set; } - - public DateTimeOffset DateOfCreation { get; set; } - - public List Children { get; set; } = []; - } } diff --git a/Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs b/Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs new file mode 100644 index 000000000..cd440a9b5 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLogTopic.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.ObjectModel; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.ServerSetupApp; + +/// +/// Defines a topic for the Startup UI. +/// +public class StartupLogTopic +{ + /// + /// Gets or Sets the LogLevel. + /// + public LogLevel LogLevel { get; set; } + + /// + /// Gets or Sets the descriptor for the topic. + /// + public string? Content { get; set; } + + /// + /// Gets or sets the time the topic was created. + /// + public DateTimeOffset DateOfCreation { get; set; } + + /// + /// Gets the child items of this topic. + /// + public Collection Children { get; } = []; +} diff --git a/Jellyfin.Server/ServerSetupApp/StartupLogger.cs b/Jellyfin.Server/ServerSetupApp/StartupLogger.cs index 2b86dc0c1..0121854ce 100644 --- a/Jellyfin.Server/ServerSetupApp/StartupLogger.cs +++ b/Jellyfin.Server/ServerSetupApp/StartupLogger.cs @@ -1,56 +1,86 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using Jellyfin.Server.Migrations.Routines; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Jellyfin.Server.ServerSetupApp; /// public class StartupLogger : IStartupLogger { - private readonly SetupServer.StartupLogEntry? _groupEntry; + private readonly StartupLogTopic? _topic; /// /// Initializes a new instance of the class. /// - public StartupLogger() + /// The underlying base logger. + public StartupLogger(ILogger logger) { - Loggers = []; + BaseLogger = logger; } /// /// Initializes a new instance of the class. /// - private StartupLogger(SetupServer.StartupLogEntry? groupEntry) : this() + /// The underlying base logger. + /// The group for this logger. + internal StartupLogger(ILogger logger, StartupLogTopic? topic) : this(logger) { - _groupEntry = groupEntry; + _topic = topic; } - internal static IStartupLogger Logger { get; } = new StartupLogger(); + internal static IStartupLogger Logger { get; set; } = new StartupLogger(NullLogger.Instance); - private List Loggers { get; set; } + /// + public StartupLogTopic? Topic => _topic; + + /// + /// Gets or Sets the underlying base logger. + /// + protected ILogger BaseLogger { get; set; } /// public IStartupLogger BeginGroup(FormattableString logEntry) { - var startupEntry = new SetupServer.StartupLogEntry() + return new StartupLogger(BaseLogger, AddToTopic(logEntry)); + } + + /// + public IStartupLogger With(ILogger logger) + { + return new StartupLogger(logger, Topic); + } + + /// + public IStartupLogger With(ILogger logger) + { + return new StartupLogger(logger, Topic); + } + + /// + public IStartupLogger BeginGroup(FormattableString logEntry) + { + return new StartupLogger(BaseLogger, AddToTopic(logEntry)); + } + + private StartupLogTopic AddToTopic(FormattableString logEntry) + { + var startupEntry = new StartupLogTopic() { Content = logEntry.ToString(CultureInfo.InvariantCulture), DateOfCreation = DateTimeOffset.Now }; - if (_groupEntry is null) + if (Topic is null) { SetupServer.LogQueue?.Enqueue(startupEntry); } else { - _groupEntry.Children.Add(startupEntry); + Topic.Children.Add(startupEntry); } - return new StartupLogger(startupEntry); + return startupEntry; } /// @@ -69,34 +99,26 @@ public class StartupLogger : IStartupLogger /// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - foreach (var item in Loggers.Where(e => e.IsEnabled(logLevel))) + if (BaseLogger.IsEnabled(logLevel)) { - item.Log(logLevel, eventId, state, exception, formatter); + // if enabled allow the base logger also to receive the message + BaseLogger.Log(logLevel, eventId, state, exception, formatter); } - var startupEntry = new SetupServer.StartupLogEntry() + var startupEntry = new StartupLogTopic() { LogLevel = logLevel, Content = formatter(state, exception), DateOfCreation = DateTimeOffset.Now }; - if (_groupEntry is null) + if (Topic is null) { SetupServer.LogQueue?.Enqueue(startupEntry); } else { - _groupEntry.Children.Add(startupEntry); + Topic.Children.Add(startupEntry); } } - - /// - public IStartupLogger With(ILogger logger) - { - return new StartupLogger(_groupEntry) - { - Loggers = [.. Loggers, logger] - }; - } } diff --git a/Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs b/Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs new file mode 100644 index 000000000..ada4b56a7 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLoggerExtensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Jellyfin.Server.ServerSetupApp; + +internal static class StartupLoggerExtensions +{ + public static IServiceCollection RegisterStartupLogger(this IServiceCollection services) + { + return services + .AddTransient>() + .AddTransient(typeof(IStartupLogger<>), typeof(StartupLogger<>)); + } +} diff --git a/Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs b/Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs new file mode 100644 index 000000000..64da0ce88 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/StartupLoggerOfCategory.cs @@ -0,0 +1,56 @@ +using System; +using System.Globalization; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.ServerSetupApp; + +/// +/// Startup logger for usage with DI that utilises an underlying logger from the DI. +/// +/// The category of the underlying logger. +#pragma warning disable SA1649 // File name should match first type name +public class StartupLogger : StartupLogger, IStartupLogger +#pragma warning restore SA1649 // File name should match first type name +{ + /// + /// Initializes a new instance of the class. + /// + /// The injected base logger. + public StartupLogger(ILogger logger) : base(logger) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The underlying base logger. + /// The group for this logger. + internal StartupLogger(ILogger logger, StartupLogTopic? groupEntry) : base(logger, groupEntry) + { + } + + IStartupLogger IStartupLogger.BeginGroup(FormattableString logEntry) + { + var startupEntry = new StartupLogTopic() + { + Content = logEntry.ToString(CultureInfo.InvariantCulture), + DateOfCreation = DateTimeOffset.Now + }; + + if (Topic is null) + { + SetupServer.LogQueue?.Enqueue(startupEntry); + } + else + { + Topic.Children.Add(startupEntry); + } + + return new StartupLogger(BaseLogger, startupEntry); + } + + IStartupLogger IStartupLogger.With(ILogger logger) + { + return new StartupLogger(logger, Topic); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 725e359d7..0952fb8b6 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -98,7 +98,10 @@ namespace Jellyfin.Server.Integration.Tests .AddEnvironmentVariables("JELLYFIN_") .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); }) - .ConfigureServices(e => e.AddSingleton().AddSingleton(e)); + .ConfigureServices(e => e + .AddSingleton>() + .AddTransient(typeof(IStartupLogger<>), typeof(NullStartupLogger<>)) + .AddSingleton(e)); } /// @@ -132,13 +135,20 @@ namespace Jellyfin.Server.Integration.Tests base.Dispose(disposing); } - private sealed class NullStartupLogger : IStartupLogger + private sealed class NullStartupLogger : IStartupLogger { + public StartupLogTopic? Topic => throw new NotImplementedException(); + public IStartupLogger BeginGroup(FormattableString logEntry) { return this; } + public IStartupLogger BeginGroup(FormattableString logEntry) + { + return new NullStartupLogger(); + } + public IDisposable? BeginScope(TState state) where TState : notnull { @@ -160,10 +170,25 @@ namespace Jellyfin.Server.Integration.Tests return this; } + public IStartupLogger With(Microsoft.Extensions.Logging.ILogger logger) + { + return new NullStartupLogger(); + } + + IStartupLogger IStartupLogger.BeginGroup(FormattableString logEntry) + { + return new NullStartupLogger(); + } + IStartupLogger IStartupLogger.With(Microsoft.Extensions.Logging.ILogger logger) { return this; } + + IStartupLogger IStartupLogger.With(Microsoft.Extensions.Logging.ILogger logger) + { + return this; + } } } } -- cgit v1.2.3