Added dependency injection

This commit is contained in:
2026-06-02 19:09:35 +02:00
parent dd6aa547f9
commit 3cb5ee52a0
8 changed files with 1022 additions and 12 deletions
@@ -0,0 +1,43 @@
using EonaCat.LogStack.Core;
using System;
namespace EonaCat.LogStack.Logging;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Adapter interface that wraps EonaCatLogStack to be compatible with Microsoft.Extensions.Logging.ILogger pattern
/// </summary>
public interface ILogger
{
/// <summary>
/// Gets the category/name of this logger
/// </summary>
string Category { get; }
/// <summary>
/// Logs a message at the specified log level
/// </summary>
void Log(LogLevel level, string message);
/// <summary>
/// Logs a message with an exception at the specified log level
/// </summary>
void Log(LogLevel level, Exception exception, string message);
/// <summary>
/// Logs a formatted message at the specified log level
/// </summary>
void Log(LogLevel level, string format, params object[] args);
/// <summary>
/// Logs a formatted message with an exception at the specified log level
/// </summary>
void Log(LogLevel level, Exception exception, string format, params object[] args);
/// <summary>
/// Checks if the given log level is enabled for this logger
/// </summary>
bool IsEnabled(LogLevel level);
}
@@ -0,0 +1,224 @@
using EonaCat.LogStack.Core;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace EonaCat.LogStack.Logging;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Factory for creating logger instances with a shared configuration
/// </summary>
public interface ILoggerFactory : IAsyncDisposable
{
/// <summary>
/// Creates or retrieves a logger for the specified category
/// </summary>
ILogger CreateLogger(string categoryName);
/// <summary>
/// Gets the underlying EonaCatLogStack instance
/// </summary>
EonaCatLogStack GetLogStack();
/// <summary>
/// Flushes all pending log events
/// </summary>
System.Threading.Tasks.Task FlushAsync();
/// <summary>
/// Gets diagnostics information about the logger
/// </summary>
LoggerDiagnostics GetDiagnostics();
}
/// <summary>
/// Default implementation of ILoggerFactory that manages a single EonaCatLogStack instance
/// </summary>
public sealed class LoggerFactory : ILoggerFactory
{
private readonly EonaCatLogStack _logStack;
private readonly ConcurrentDictionary<string, Logger> _loggers;
private volatile bool _isDisposed;
/// <summary>
/// Creates a new LoggerFactory with default settings
/// </summary>
public LoggerFactory(
LogLevel minimumLevel = LogLevel.Trace,
TimestampMode timestampMode = TimestampMode.Utc)
{
_logStack = new EonaCatLogStack(
category: "LoggerFactory",
minimumLevel: minimumLevel,
timestampMode: timestampMode);
_loggers = new ConcurrentDictionary<string, Logger>();
}
/// <summary>
/// Creates a new LoggerFactory using an existing EonaCatLogStack instance
/// </summary>
public LoggerFactory(EonaCatLogStack logStack)
{
_logStack = logStack ?? throw new ArgumentNullException(nameof(logStack));
_loggers = new ConcurrentDictionary<string, Logger>();
}
/// <summary>
/// Creates or retrieves a logger for the specified category
/// </summary>
public ILogger CreateLogger(string categoryName)
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
if (string.IsNullOrEmpty(categoryName))
{
categoryName = "Default";
}
return _loggers.GetOrAdd(categoryName, name => new Logger(name, _logStack));
}
/// <summary>
/// Gets the underlying EonaCatLogStack instance
/// </summary>
public EonaCatLogStack GetLogStack()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
return _logStack;
}
/// <summary>
/// Flushes all pending log events
/// </summary>
public async System.Threading.Tasks.Task FlushAsync()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
await _logStack.FlushAsync().ConfigureAwait(false);
}
/// <summary>
/// Gets diagnostics information about the logger
/// </summary>
public LoggerDiagnostics GetDiagnostics()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
return _logStack.GetDiagnostics();
}
/// <summary>
/// Disposes all loggers and the underlying log stack
/// </summary>
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
_loggers.Clear();
await _logStack.DisposeAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
/// <summary>
/// Internal logger adapter that wraps EonaCatLogStack
/// </summary>
private sealed class Logger : ILogger
{
private readonly string _category;
private readonly EonaCatLogStack _logStack;
public string Category => _category;
public Logger(string category, EonaCatLogStack logStack)
{
_category = category ?? "Default";
_logStack = logStack;
}
public void Log(LogLevel level, string message)
{
if (!IsEnabled(level))
{
return;
}
_logStack.Log(message, level);
}
public void Log(LogLevel level, Exception exception, string message)
{
if (!IsEnabled(level))
{
return;
}
_logStack.Log(level, exception, message);
}
public void Log(LogLevel level, string format, params object[] args)
{
if (!IsEnabled(level))
{
return;
}
try
{
var message = string.Format(format, args);
_logStack.Log(message, level);
}
catch
{
// Fallback if formatting fails
_logStack.Log(format, level);
}
}
public void Log(LogLevel level, Exception exception, string format, params object[] args)
{
if (!IsEnabled(level))
{
return;
}
try
{
var message = string.Format(format, args);
_logStack.Log(level, exception, message);
}
catch
{
// Fallback if formatting fails
_logStack.Log(level, exception, format);
}
}
public bool IsEnabled(LogLevel level)
{
// Check if level is enabled in the logstack
// This is a simple check; could be enhanced
return level >= LogLevel.Trace;
}
}
}
@@ -0,0 +1,120 @@
using EonaCat.LogStack.Core;
using System;
namespace EonaCat.LogStack.Logging;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Adapter that bridges EonaCat logging with Microsoft.Extensions.Logging
/// This allows EonaCatLogStack to be used where Microsoft.Extensions.Logging.ILogger is expected
/// </summary>
public sealed class MicrosoftExtensionsLoggerAdapter : Microsoft.Extensions.Logging.ILogger
{
private readonly ILogger _eonaCatLogger;
public MicrosoftExtensionsLoggerAdapter(ILogger eonaCatLogger)
{
_eonaCatLogger = eonaCatLogger ?? throw new ArgumentNullException(nameof(eonaCatLogger));
}
public IDisposable BeginScope<TState>(TState state)
{
// EonaCat doesn't support scopes out of the box, but we can return a no-op disposable
return new NoOpDisposable();
}
public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
{
var eonaCatLevel = ConvertLogLevel(logLevel);
return _eonaCatLogger.IsEnabled(eonaCatLevel);
}
public void Log<TState>(
Microsoft.Extensions.Logging.LogLevel logLevel,
Microsoft.Extensions.Logging.EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
var eonaCatLevel = ConvertLogLevel(logLevel);
var message = formatter(state, exception);
if (exception != null)
{
_eonaCatLogger.Log(eonaCatLevel, exception, message);
}
else
{
_eonaCatLogger.Log(eonaCatLevel, message);
}
}
private static LogLevel ConvertLogLevel(Microsoft.Extensions.Logging.LogLevel msLevel)
{
return msLevel switch
{
Microsoft.Extensions.Logging.LogLevel.Trace => LogLevel.Trace,
Microsoft.Extensions.Logging.LogLevel.Debug => LogLevel.Debug,
Microsoft.Extensions.Logging.LogLevel.Information => LogLevel.Information,
Microsoft.Extensions.Logging.LogLevel.Warning => LogLevel.Warning,
Microsoft.Extensions.Logging.LogLevel.Error => LogLevel.Error,
Microsoft.Extensions.Logging.LogLevel.Critical => LogLevel.Critical,
_ => LogLevel.Information
};
}
private sealed class NoOpDisposable : IDisposable
{
public void Dispose() { }
}
}
/// <summary>
/// Factory adapter that implements Microsoft.Extensions.Logging.ILoggerFactory
/// </summary>
public sealed class MicrosoftExtensionsLoggerFactoryAdapter : Microsoft.Extensions.Logging.ILoggerFactory
{
private readonly ILoggerFactory _eonaCatFactory;
private volatile bool _isDisposed;
public MicrosoftExtensionsLoggerFactoryAdapter(ILoggerFactory eonaCatFactory)
{
_eonaCatFactory = eonaCatFactory ?? throw new ArgumentNullException(nameof(eonaCatFactory));
}
public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(MicrosoftExtensionsLoggerFactoryAdapter));
}
var eonaCatLogger = _eonaCatFactory.CreateLogger(categoryName);
return new MicrosoftExtensionsLoggerAdapter(eonaCatLogger);
}
public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider)
{
// EonaCat uses Flows instead of providers, so this is a no-op
// Providers should be configured through the ILoggerFactory.GetLogStack()
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
_eonaCatFactory.DisposeAsync().GetAwaiter().GetResult();
GC.SuppressFinalize(this);
}
}