feat(core): dynamic settings service, IFinlyticLogger, log broadcaster, and persistent Yahoo auth

This commit is contained in:
2026-08-15 21:29:38 +02:00
parent 34fa774cbf
commit 3dbee36ca0
15 changed files with 920 additions and 392 deletions
@@ -1,17 +1,42 @@
using System;
using System.Threading.Tasks;
using FinlyticCore.Dtos.Logging;
using FinlyticCore.Models.Settings;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace FinlyticCore.Services;
/// <summary>
/// Bietet kanalbasierte, dynamisch steuerbare Logging-Funktionalitäten über den <see cref="ISettingsService{TContext}"/>.
/// Globaler Broadcaster für strukturierte Logs in Echtzeit.
/// </summary>
public static class FinlyticLogBroadcaster
{
public static Func<LogMessageDto, Task>? OnLogPublished { get; set; }
public static void Broadcast(LogMessageDto dto)
{
if (OnLogPublished != null)
{
_ = Task.Run(async () =>
{
try
{
await OnLogPublished(dto);
}
catch
{
// Ignore broadcast errors to never disrupt execution
}
});
}
}
}
/// <summary>
/// Bietet kanalbasierte, dynamisch steuerbare Logging-Funktionalitäten über den <see cref="ISettingsService"/>.
/// </summary>
/// <typeparam name="TContextClass">Die aufrufende Klasse (für Log-Kategorien).</typeparam>
/// <typeparam name="TDbContext">Der DbContext des Services für den Zugriff auf die Settings.</typeparam>
public interface IFinlyticLogger<TContextClass, TDbContext> where TDbContext : DbContext
public interface IFinlyticLogger<TContextClass>
{
// --- Debug ---
Task LogDebugAsync(SettingKey<bool> channelKey, string message, params object[] args);
@@ -36,22 +61,49 @@ public interface IFinlyticLogger<TContextClass, TDbContext> where TDbContext : D
/// <summary>
/// Kanalbasierte Logger-Implementierung, die Einstellungen und Stummschaltungen
/// in Echtzeit aus dem <see cref="ISettingsService{TContext}"/> bezieht.
/// in Echtzeit aus dem <see cref="ISettingsService"/> bezieht.
/// </summary>
public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContextClass, TDbContext>
where TDbContext : DbContext
public class FinlyticLogger<TContextClass> : IFinlyticLogger<TContextClass>
{
private static readonly string ServiceName = typeof(TContextClass).Assembly.GetName().Name ?? "Finlytic";
private readonly ILogger<TContextClass> _logger;
private readonly ISettingsService<TDbContext> _settingsService;
private readonly ISettingsService _settingsService;
public FinlyticLogger(
ILogger<TContextClass> logger,
ISettingsService<TDbContext> settingsService)
ISettingsService settingsService)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
}
private void DispatchBroadcast(SettingKey<bool> channelKey, LogLevel level, string message, Exception? exception, params object[] args)
{
try
{
string formattedMsg = args != null && args.Length > 0 ? string.Format(message, args) : message;
FinlyticLogBroadcaster.Broadcast(new LogMessageDto(
Timestamp: DateTime.UtcNow,
ServiceName: ServiceName,
Channel: channelKey.Name,
Level: level.ToString(),
Message: formattedMsg,
Exception: exception?.ToString()
));
}
catch
{
FinlyticLogBroadcaster.Broadcast(new LogMessageDto(
Timestamp: DateTime.UtcNow,
ServiceName: ServiceName,
Channel: channelKey.Name,
Level: level.ToString(),
Message: message,
Exception: exception?.ToString()
));
}
}
#region Debug
public async Task LogDebugAsync(SettingKey<bool> channelKey, string message, params object[] args)
@@ -59,6 +111,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
if (await ShouldLogAsync(channelKey, LogLevel.Debug))
{
_logger.LogDebug(message, args);
DispatchBroadcast(channelKey, LogLevel.Debug, message, null, args);
}
}
@@ -70,6 +123,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
_logger.LogDebug(exception, message, args);
else
_logger.LogDebug(message, args);
DispatchBroadcast(channelKey, LogLevel.Debug, message, exception, args);
}
}
@@ -82,6 +136,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
if (await ShouldLogAsync(channelKey, LogLevel.Information))
{
_logger.LogInformation(message, args);
DispatchBroadcast(channelKey, LogLevel.Information, message, null, args);
}
}
@@ -93,6 +148,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
_logger.LogInformation(exception, message, args);
else
_logger.LogInformation(message, args);
DispatchBroadcast(channelKey, LogLevel.Information, message, exception, args);
}
}
@@ -105,6 +161,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
if (await ShouldLogAsync(channelKey, LogLevel.Warning))
{
_logger.LogWarning(message, args);
DispatchBroadcast(channelKey, LogLevel.Warning, message, null, args);
}
}
@@ -116,6 +173,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
_logger.LogWarning(exception, message, args);
else
_logger.LogWarning(message, args);
DispatchBroadcast(channelKey, LogLevel.Warning, message, exception, args);
}
}
@@ -128,6 +186,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
if (await ShouldLogAsync(channelKey, LogLevel.Error))
{
_logger.LogError(message, args);
DispatchBroadcast(channelKey, LogLevel.Error, message, null, args);
}
}
@@ -139,6 +198,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
_logger.LogError(exception, message, args);
else
_logger.LogError(message, args);
DispatchBroadcast(channelKey, LogLevel.Error, message, exception, args);
}
}
@@ -151,6 +211,7 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
if (await ShouldLogAsync(channelKey, LogLevel.Trace))
{
_logger.LogTrace(message, args);
DispatchBroadcast(channelKey, LogLevel.Trace, message, null, args);
}
}
@@ -162,14 +223,12 @@ public class FinlyticLogger<TContextClass, TDbContext> : IFinlyticLogger<TContex
_logger.LogCritical(exception, message, args);
else
_logger.LogCritical(message, args);
DispatchBroadcast(channelKey, LogLevel.Critical, message, exception, args);
}
}
#endregion
/// <summary>
/// Prüft, ob ein spezifischer Kanal und die aufrufende Klasse aktives Logging erlauben.
/// </summary>
private async Task<bool> ShouldLogAsync(SettingKey<bool> channelKey, LogLevel level)
{
ArgumentNullException.ThrowIfNull(channelKey);