using System;
using System.Globalization;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Jellyfin.Server.ServerSetupApp;
///
public class StartupLogger : IStartupLogger
{
private static readonly AsyncLocal _ambientTopic = new();
private readonly StartupLogTopic? _topic;
///
/// Initializes a new instance of the class.
///
/// The underlying base logger.
public StartupLogger(ILogger logger)
{
BaseLogger = logger;
_topic = _ambientTopic.Value;
}
///
/// Initializes a new instance of the class.
///
/// The underlying base logger.
/// The group for this logger.
internal StartupLogger(ILogger logger, StartupLogTopic? topic) : this(logger)
{
_topic = topic;
}
internal static IStartupLogger Logger { get; set; } = new StartupLogger(NullLogger.Instance);
///
public StartupLogTopic? Topic => _topic;
///
/// Gets or Sets the underlying base logger.
///
protected ILogger BaseLogger { get; set; }
///
/// Makes the topic that loggers created on this execution context attach to.
///
/// The topic to nest newly created loggers under.
/// A scope that restores the previously ambient topic when disposed.
internal static IDisposable BeginAmbientTopic(StartupLogTopic? topic)
{
var scope = new AmbientTopicScope(_ambientTopic.Value);
_ambientTopic.Value = topic;
return scope;
}
///
public IStartupLogger BeginGroup(FormattableString logEntry)
{
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 (Topic is null)
{
SetupServer.LogQueue?.Enqueue(startupEntry);
}
else
{
Topic.Children.Add(startupEntry);
}
return startupEntry;
}
///
public IDisposable? BeginScope(TState state)
where TState : notnull
{
return null;
}
///
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
///
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
{
if (BaseLogger.IsEnabled(logLevel))
{
// if enabled allow the base logger also to receive the message
BaseLogger.Log(logLevel, eventId, state, exception, formatter);
}
var startupEntry = new StartupLogTopic()
{
LogLevel = logLevel,
Content = formatter(state, exception),
DateOfCreation = DateTimeOffset.Now
};
if (Topic is null)
{
SetupServer.LogQueue?.Enqueue(startupEntry);
}
else
{
Topic.Children.Add(startupEntry);
}
}
private sealed class AmbientTopicScope : IDisposable
{
private readonly StartupLogTopic? _previous;
public AmbientTopicScope(StartupLogTopic? previous)
{
_previous = previous;
}
public void Dispose()
{
_ambientTopic.Value = _previous;
}
}
}