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) --- 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 ++++++++++++++++ 6 files changed, 200 insertions(+), 43 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/ServerSetupApp') diff --git a/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs b/Jellyfin.Server/ServerSetupApp/IStartupLogger.cs index 2c2ef05f8a..e7c1939368 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 d88dbee577..6d58e3c4e1 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 0000000000..cd440a9b53 --- /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 2b86dc0c1a..0121854ce3 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 0000000000..ada4b56a7e --- /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 0000000000..64da0ce88a --- /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); + } +} -- cgit v1.2.3