From 3cb5ee52a0a6ec7a5b3d769deb886a3e0b7eeaf1 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Tue, 2 Jun 2026 19:09:35 +0200 Subject: [PATCH] Added dependency injection --- EonaCat.LogStack/EonaCat.LogStack.csproj | 4 +- EonaCat.LogStack/EonaCatLogger.cs | 54 ++- EonaCat.LogStack/EonaCatLoggerCore/ILogger.cs | 43 +++ .../EonaCatLoggerCore/LoggerFactory.cs | 224 ++++++++++++ .../MicrosoftExtensionsLoggerAdapter.cs | 120 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 251 +++++++++++++ README.md | 336 +++++++++++++++++- Testers/EonaCat.LogStack.Test.Web/Program.cs | 2 + 8 files changed, 1022 insertions(+), 12 deletions(-) create mode 100644 EonaCat.LogStack/EonaCatLoggerCore/ILogger.cs create mode 100644 EonaCat.LogStack/EonaCatLoggerCore/LoggerFactory.cs create mode 100644 EonaCat.LogStack/EonaCatLoggerCore/MicrosoftExtensionsLoggerAdapter.cs create mode 100644 EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs diff --git a/EonaCat.LogStack/EonaCat.LogStack.csproj b/EonaCat.LogStack/EonaCat.LogStack.csproj index f7328d2..f4dbdde 100644 --- a/EonaCat.LogStack/EonaCat.LogStack.csproj +++ b/EonaCat.LogStack/EonaCat.LogStack.csproj @@ -14,7 +14,7 @@ It features a rich fluent API for routing log events to dozens of destinations f EonaCat (Jeroen Saey) EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey - 0.0.6 + 0.0.7 README.md True LICENSE @@ -25,7 +25,7 @@ It features a rich fluent API for routing log events to dozens of destinations f - 0.0.6+{chash:10}.{c:ymd} + 0.0.7+{chash:10}.{c:ymd} true true v[0-9]* diff --git a/EonaCat.LogStack/EonaCatLogger.cs b/EonaCat.LogStack/EonaCatLogger.cs index f6f2065..594bdbc 100644 --- a/EonaCat.LogStack/EonaCatLogger.cs +++ b/EonaCat.LogStack/EonaCatLogger.cs @@ -178,15 +178,51 @@ namespace EonaCat.LogStack ProcessLogEvent(ref builder); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Trace(string message) => Log(LogLevel.Trace, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Debug(string message) => Log(LogLevel.Debug, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Information(string message) => Log(LogLevel.Information, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Warning(string message) => Log(LogLevel.Warning, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Warning(Exception ex, string message) => Log(LogLevel.Warning, ex, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Error(string message) => Log(LogLevel.Error, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Error(Exception ex, string message) => Log(LogLevel.Error, ex, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Critical(string message) => Log(LogLevel.Critical, message); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Critical(Exception ex, string message) => Log(LogLevel.Critical, ex, message); + private void Write(LogLevel level, string template, params object[] args) + { + Log(level, string.Format(template, args)); + } + + private void Write(LogLevel level, Exception ex, string template, params object[] args) + { + Log(level, ex, string.Format(template, args)); + } + + public void Trace(string template, params object[] args) => Write(LogLevel.Trace, template, args); + + public void Debug(string template, params object[] args) => Write(LogLevel.Debug, template, args); + + public void Information(string template, params object[] args) => Write(LogLevel.Information, template, args); + + public void Warning(string template, params object[] args) => Write(LogLevel.Warning, template, args); + + public void Warning(Exception ex, string template, params object[] args) => Write(LogLevel.Warning, ex, template, args); + + public void Error(string template, params object[] args) => Write(LogLevel.Error, template, args); + + public void Error(Exception ex, string template, params object[] args) => Write(LogLevel.Error, ex, template, args); + + public void Critical(string template, params object[] args) => Write(LogLevel.Critical, template, args); + + public void Critical(Exception ex, string template, params object[] args) => Write(LogLevel.Critical, ex, template, args); + + public void LogTrace(string template, params object[] args) => Write(LogLevel.Trace, template, args); + + public void LogDebug(string template, params object[] args) => Write(LogLevel.Debug, template, args); + + public void LogInformation(string template, params object[] args) => Write(LogLevel.Information, template, args); + + public void LogWarning(string template, params object[] args) => Write(LogLevel.Warning, template, args); + + public void LogWarning(Exception ex, string template, params object[] args) => Write(LogLevel.Warning, ex, template, args); + + public void LogError(string template, params object[] args) => Write(LogLevel.Error, template, args); + + public void LogError(Exception ex, string template, params object[] args) => Write(LogLevel.Error, ex, template, args); + + public void LogCritical(string template, params object[] args) => Write(LogLevel.Critical, template, args); + + public void LogCritical(Exception ex, string template, params object[] args) => Write(LogLevel.Critical, ex, template, args); [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ProcessLogEvent(ref LogEventBuilder builder) diff --git a/EonaCat.LogStack/EonaCatLoggerCore/ILogger.cs b/EonaCat.LogStack/EonaCatLoggerCore/ILogger.cs new file mode 100644 index 0000000..892e70e --- /dev/null +++ b/EonaCat.LogStack/EonaCatLoggerCore/ILogger.cs @@ -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. + +/// +/// Adapter interface that wraps EonaCatLogStack to be compatible with Microsoft.Extensions.Logging.ILogger pattern +/// +public interface ILogger +{ + /// + /// Gets the category/name of this logger + /// + string Category { get; } + + /// + /// Logs a message at the specified log level + /// + void Log(LogLevel level, string message); + + /// + /// Logs a message with an exception at the specified log level + /// + void Log(LogLevel level, Exception exception, string message); + + /// + /// Logs a formatted message at the specified log level + /// + void Log(LogLevel level, string format, params object[] args); + + /// + /// Logs a formatted message with an exception at the specified log level + /// + void Log(LogLevel level, Exception exception, string format, params object[] args); + + /// + /// Checks if the given log level is enabled for this logger + /// + bool IsEnabled(LogLevel level); +} diff --git a/EonaCat.LogStack/EonaCatLoggerCore/LoggerFactory.cs b/EonaCat.LogStack/EonaCatLoggerCore/LoggerFactory.cs new file mode 100644 index 0000000..403011d --- /dev/null +++ b/EonaCat.LogStack/EonaCatLoggerCore/LoggerFactory.cs @@ -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. + +/// +/// Factory for creating logger instances with a shared configuration +/// +public interface ILoggerFactory : IAsyncDisposable +{ + /// + /// Creates or retrieves a logger for the specified category + /// + ILogger CreateLogger(string categoryName); + + /// + /// Gets the underlying EonaCatLogStack instance + /// + EonaCatLogStack GetLogStack(); + + /// + /// Flushes all pending log events + /// + System.Threading.Tasks.Task FlushAsync(); + + /// + /// Gets diagnostics information about the logger + /// + LoggerDiagnostics GetDiagnostics(); +} + +/// +/// Default implementation of ILoggerFactory that manages a single EonaCatLogStack instance +/// +public sealed class LoggerFactory : ILoggerFactory +{ + private readonly EonaCatLogStack _logStack; + private readonly ConcurrentDictionary _loggers; + private volatile bool _isDisposed; + + /// + /// Creates a new LoggerFactory with default settings + /// + public LoggerFactory( + LogLevel minimumLevel = LogLevel.Trace, + TimestampMode timestampMode = TimestampMode.Utc) + { + _logStack = new EonaCatLogStack( + category: "LoggerFactory", + minimumLevel: minimumLevel, + timestampMode: timestampMode); + _loggers = new ConcurrentDictionary(); + } + + /// + /// Creates a new LoggerFactory using an existing EonaCatLogStack instance + /// + public LoggerFactory(EonaCatLogStack logStack) + { + _logStack = logStack ?? throw new ArgumentNullException(nameof(logStack)); + _loggers = new ConcurrentDictionary(); + } + + /// + /// Creates or retrieves a logger for the specified category + /// + 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)); + } + + /// + /// Gets the underlying EonaCatLogStack instance + /// + public EonaCatLogStack GetLogStack() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(LoggerFactory)); + } + + return _logStack; + } + + /// + /// Flushes all pending log events + /// + public async System.Threading.Tasks.Task FlushAsync() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(LoggerFactory)); + } + + await _logStack.FlushAsync().ConfigureAwait(false); + } + + /// + /// Gets diagnostics information about the logger + /// + public LoggerDiagnostics GetDiagnostics() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(LoggerFactory)); + } + + return _logStack.GetDiagnostics(); + } + + /// + /// Disposes all loggers and the underlying log stack + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + _loggers.Clear(); + await _logStack.DisposeAsync().ConfigureAwait(false); + GC.SuppressFinalize(this); + } + + /// + /// Internal logger adapter that wraps EonaCatLogStack + /// + 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; + } + } +} diff --git a/EonaCat.LogStack/EonaCatLoggerCore/MicrosoftExtensionsLoggerAdapter.cs b/EonaCat.LogStack/EonaCatLoggerCore/MicrosoftExtensionsLoggerAdapter.cs new file mode 100644 index 0000000..49eb4ee --- /dev/null +++ b/EonaCat.LogStack/EonaCatLoggerCore/MicrosoftExtensionsLoggerAdapter.cs @@ -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. + +/// +/// Adapter that bridges EonaCat logging with Microsoft.Extensions.Logging +/// This allows EonaCatLogStack to be used where Microsoft.Extensions.Logging.ILogger is expected +/// +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 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( + Microsoft.Extensions.Logging.LogLevel logLevel, + Microsoft.Extensions.Logging.EventId eventId, + TState state, + Exception exception, + Func 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() { } + } +} + +/// +/// Factory adapter that implements Microsoft.Extensions.Logging.ILoggerFactory +/// +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); + } +} diff --git a/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs b/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..5211773 --- /dev/null +++ b/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,251 @@ +using EonaCat.LogStack.Configuration; +using EonaCat.LogStack.Core; +using EonaCat.LogStack.Logging; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace EonaCat.LogStack.Extensions; + +// 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. + +/// +/// Extension methods for integrating EonaCat LogStack with Microsoft Dependency Injection +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers EonaCat LogStack as the logging provider in the dependency injection container + /// + /// The service collection to register with + /// The minimum log level to process + /// The timestamp mode to use + /// The service collection for chaining + public static IServiceCollection AddEonaCatLogging( + this IServiceCollection services, + LogLevel minimumLevel = LogLevel.Trace, + TimestampMode timestampMode = TimestampMode.Utc) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + var loggerFactory = new LoggerFactory(minimumLevel, timestampMode); + services.AddSingleton(loggerFactory); + services.AddSingleton( + new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory)); + + // Also register as ILogger for constructor injection + services.AddSingleton(sp => sp.GetRequiredService().CreateLogger("Default")); + services.AddSingleton(sp => + new MicrosoftExtensionsLoggerAdapter( + sp.GetRequiredService().CreateLogger("Default"))); + + return services; + } + + /// + /// Registers EonaCat LogStack using an existing EonaCatLogStack instance + /// + /// The service collection to register with + /// The pre-configured EonaCatLogStack instance + /// The service collection for chaining + public static IServiceCollection AddEonaCatLogging( + this IServiceCollection services, + EonaCatLogStack logStack) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (logStack == null) + { + throw new ArgumentNullException(nameof(logStack)); + } + + var loggerFactory = new LoggerFactory(logStack); + services.AddSingleton(loggerFactory); + services.AddSingleton( + new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory)); + + // Also register as ILogger for constructor injection + services.AddSingleton(sp => sp.GetRequiredService().CreateLogger("Default")); + services.AddSingleton(sp => + new MicrosoftExtensionsLoggerAdapter( + sp.GetRequiredService().CreateLogger("Default"))); + + return services; + } + + /// + /// Registers EonaCat LogStack with a configuration callback + /// + /// The service collection to register with + /// Callback to configure the EonaCatLogStack instance + /// The service collection for chaining + public static IServiceCollection AddEonaCatLogging( + this IServiceCollection services, + Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var logStack = new EonaCatLogStack(); + configure(logStack); + + var loggerFactory = new LoggerFactory(logStack); + services.AddSingleton(loggerFactory); + services.AddSingleton( + new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory)); + + // Also register as ILogger for constructor injection + services.AddSingleton(sp => sp.GetRequiredService().CreateLogger("Default")); + services.AddSingleton(sp => + new MicrosoftExtensionsLoggerAdapter( + sp.GetRequiredService().CreateLogger("Default"))); + + return services; + } + + /// + /// Registers EonaCat LogStack using a fluent LogBuilder configuration. + /// This is the recommended approach for DI integration with fluent configuration. + /// + /// The service collection to register with + /// The category/name of the logger + /// Callback to configure the LogBuilder instance + /// The service collection for chaining + public static IServiceCollection AddEonaCatLogging( + this IServiceCollection services, + string category, + Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new LogBuilder(category); + configure(builder); + var logStack = builder.Build(); + + var loggerFactory = new LoggerFactory(logStack); + services.AddSingleton(loggerFactory); + services.AddSingleton( + new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory)); + + // Also register as ILogger for constructor injection + services.AddSingleton(sp => sp.GetRequiredService().CreateLogger(category)); + services.AddSingleton(sp => + new MicrosoftExtensionsLoggerAdapter( + sp.GetRequiredService().CreateLogger(category))); + + return services; + } + + /// + /// Registers EonaCat LogStack using a fluent LogBuilder configuration with "Application" as the default category. + /// This is the recommended approach for DI integration with fluent configuration. + /// + /// The service collection to register with + /// Callback to configure the LogBuilder instance + /// The service collection for chaining + public static IServiceCollection AddEonaCatLogging( + this IServiceCollection services, + Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + return services.AddEonaCatLogging("Application", configure); + } + + /// + /// Registers EonaCat LogStack as a factory using a LogBuilder configuration callback. + /// Allows configuring the logger factory through DI with full access to the service provider. + /// + /// The service collection to register with + /// The category/name of the logger + /// Callback to configure the LogBuilder instance + /// The service collection for chaining + public static IServiceCollection AddEonaCatLoggingFactory( + this IServiceCollection services, + string category, + Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + services.AddSingleton(sp => + { + var builder = new LogBuilder(category); + configure(builder); + var logStack = builder.Build(); + return new LoggerFactory(logStack); + }); + + services.AddSingleton(sp => + new MicrosoftExtensionsLoggerFactoryAdapter( + sp.GetRequiredService())); + + // Also register as ILogger for constructor injection + services.AddSingleton(sp => sp.GetRequiredService().CreateLogger(category)); + services.AddSingleton(sp => + new MicrosoftExtensionsLoggerAdapter( + sp.GetRequiredService().CreateLogger(category))); + + return services; + } + + /// + /// Registers EonaCat LogStack as a factory using a LogBuilder configuration callback with "Application" as default category. + /// Allows configuring the logger factory through DI with full access to the service provider. + /// + /// The service collection to register with + /// Callback to configure the LogBuilder instance + /// The service collection for chaining + public static IServiceCollection AddEonaCatLoggingFactory( + this IServiceCollection services, + Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + return services.AddEonaCatLoggingFactory("Application", configure); + } +} diff --git a/README.md b/README.md index 8b8e773..339b369 100644 --- a/README.md +++ b/README.md @@ -474,4 +474,338 @@ server.LogDropped += line => Metrics.Increment("dropped"); Console.CancelKeyPress += (_, e) => { e.Cancel = true; server.Stop(); }; ``` -`Stop()` prints a throughput summary and disposes all listeners cleanly. \ No newline at end of file +`Stop()` prints a throughput summary and disposes all listeners cleanly. + + +## Dependency Injection (DI) registration methods + +### 1. Basic Registration (Simplest) + +Register with default settings: + +```csharp +services.AddEonaCatLogging(); +``` + +This registers: +- `ILoggerFactory` - For creating category-specific loggers +- `Microsoft.Extensions.Logging.ILoggerFactory` - For Microsoft.Extensions.Logging compatibility +- `ILogger` - For injecting the default logger + +### 2. Registration with Log Level and Timestamp Mode + +```csharp +services.AddEonaCatLogging( + minimumLevel: LogLevel.Information, + timestampMode: TimestampMode.Local); +``` + +### 3. Registration with Pre-built EonaCatLogStack + +If you've already created an `EonaCatLogStack` instance: + +```csharp +var logStack = new EonaCatLogStack("MyApp"); +logStack.AddFlow(new ConsoleFlow()); + +services.AddEonaCatLogging(logStack); +``` + +### 4. Registration with Configuration Callback (Recommended) + +Configure the logger directly with an `Action`: + +```csharp +services.AddEonaCatLogging(logStack => +{ + logStack.AddFlow(new ConsoleFlow()); + logStack.AddFlow(new FileFlow("./logs")); + logStack.AddBooster(new MachineNameBooster()); +}); +``` + +### 5. Registration with LogBuilder (Most Fluent - Recommended) + +Use the fluent `LogBuilder` API for the most intuitive configuration: + +```csharp +services.AddEonaCatLogging("MyApplication", builder => +{ + builder + .WithMinimumLevel(LogLevel.Information) + .WithTimestampMode(TimestampMode.Local) + .WriteToConsole() + .WriteToFile("./logs") + .BoostWithMachineName() + .BoostWithProcessId() + .BoostWithCorrelationId(); +}); +``` + +Or with the default "Application" category: + +```csharp +services.AddEonaCatLogging(builder => +{ + builder + .WriteToConsole() + .WriteToFile("./logs"); +}); +``` + +### 6. Registration with Factory Method (Advanced) + +For advanced scenarios where you need access to the service provider: + +```csharp +services.AddEonaCatLoggingFactory("MyApplication", builder => +{ + builder + .WriteToConsole() + .WriteToFile("./logs"); +}); +``` + +## Using in Your Application + +### Injecting ILoggerFactory + +```csharp +public class MyService +{ + private readonly ILoggerFactory _loggerFactory; + + public MyService(ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + public void DoSomething() + { + var logger = _loggerFactory.CreateLogger("MyService"); + logger.Log(LogLevel.Information, "Doing something"); + } +} +``` + +### Injecting ILogger + +```csharp +public class MyService +{ + private readonly ILogger _logger; + + public MyService(ILogger logger) + { + _logger = logger; + } + + public void DoSomething() + { + _logger.Log(LogLevel.Information, "Doing something"); + } +} +``` + +### Using with Microsoft.Extensions.Logging.ILogger + +```csharp +public class MyService +{ + private readonly Microsoft.Extensions.Logging.ILogger _logger; + + public MyService(Microsoft.Extensions.Logging.ILogger logger) + { + _logger = logger; + } + + public void DoSomething() + { + _logger.LogInformation("Doing something"); + } +} +``` + +## ASP.NET Core / Razor Pages Integration + +In your `Program.cs`: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Add EonaCat LogStack to the service collection +builder.Services.AddEonaCatLogging("WebApplication", logBuilder => +{ + logBuilder + .WithMinimumLevel(LogLevel.Information) + .WriteToConsole(useColors: true) + .WriteToFile("./logs") + .BoostWithCorrelationId() + .BoostWithThreadId(); +}); + +// Rest of your configuration... +var app = builder.Build(); + +// Configure HTTP request pipeline... +app.Run(); +``` + +### In Razor Page Code-Behind + +```csharp +public class IndexModel : PageModel +{ + private readonly ILogger _logger; + + public IndexModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet() + { + _logger.Log(LogLevel.Information, "Index page loaded"); + } +} +``` + +## Features + +### Multiple Output Destinations (Flows) + +```csharp +services.AddEonaCatLogging(builder => +{ + builder + .WriteToConsole() // Console output + .WriteToFile("./logs") // File output + .WriteToSlack("https://hooks.slack.com/...") // Slack + .WriteToDiscord("https://discordapp.com/...") // Discord + .WriteToElasticSearch("http://localhost:9200") // Elasticsearch + .WriteToEmail("smtp.gmail.com", 587, ...) // Email + .WriteToMicrosoftTeams("https://..."); // Teams +}); +``` + +### Enriching Log Events (Boosters) + +```csharp +services.AddEonaCatLogging(builder => +{ + builder + .BoostWithMachineName() // Adds machine name + .BoostWithProcessId() // Adds process ID + .BoostWithThreadId() // Adds thread ID + .BoostWithCorrelationId() // Adds correlation ID (for distributed tracing) + .BoostWithMemory() // Adds memory usage + .BoostWithOS() // Adds OS info + .BoostWithUser() // Adds username + .BoostWithCustomText("Environment", "Production"); // Custom properties +}); +``` + +### Log Level Filtering + +```csharp +services.AddEonaCatLogging(builder => +{ + builder + .WithMinimumLevel(LogLevel.Warning) // Only log warnings and above + .WriteToConsole(minimumLevel: LogLevel.Information) // More verbose for console + .WriteToFile("./logs", minimumLevel: LogLevel.Error); // Only errors to file +}); +``` + +## Best Practices + +1. **Use LogBuilder for Configuration**: The fluent LogBuilder API is the most readable and maintainable approach. + +2. **Register Early**: Register logging in `Program.cs` before other services that depend on logging. + +3. **Use Appropriate Log Levels**: + - `Trace` - Very detailed diagnostic info + - `Debug` - Debug-level diagnostic info + - `Information` - General informational messages + - `Warning` - Warning messages + - `Error` - Error messages + - `Critical` - Critical failures + +4. **Inject Specific Types**: Prefer injecting `ILoggerFactory` to create category-specific loggers rather than injecting a single shared logger. + +5. **Use Categories**: Create loggers with meaningful category names: + ```csharp + var logger = loggerFactory.CreateLogger("MyApp.Services.UserService"); + ``` + +6. **Enable Correlation IDs**: For distributed tracing scenarios: + ```csharp + builder.BoostWithCorrelationId() + ``` + +## Configuration Examples + +### Minimal Setup (Console Only) + +```csharp +services.AddEonaCatLogging(b => b.WriteToConsole()); +``` + +### Development Environment + +```csharp +services.AddEonaCatLogging(builder => +{ + builder + .WithMinimumLevel(LogLevel.Debug) + .WriteToConsole(useColors: true) + .WriteToFile("./logs") + .BoostWithMachineName() + .BoostWithThreadId(); +}); +``` + +### Production Environment + +```csharp +services.AddEonaCatLogging("ProductionApp", builder => +{ + builder + .WithMinimumLevel(LogLevel.Information) + .WriteToFile("./logs", minimumLevel: LogLevel.Information) + .WriteToElasticSearch("https://elastic.company.com") + .WriteToSlack("https://hooks.slack.com/...") + .BoostWithCorrelationId() + .BoostWithMachineName() + .BoostWithUser(); +}); +``` +## Diagnostics + +```csharp +public class DiagnosticsService +{ + private readonly ILoggerFactory _loggerFactory; + + public DiagnosticsService(ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + public void PrintDiagnostics() + { + var diagnostics = _loggerFactory.GetDiagnostics(); + Console.WriteLine($"Total Logged: {diagnostics.TotalLoggedCount}"); + Console.WriteLine($"Total Dropped: {diagnostics.TotalDroppedCount}"); + } +} +``` + +## Disposing of the Logger + +The logger is registered as a Singleton in the DI container, so it will be automatically disposed when the application shuts down. You can also manually access and dispose it: + +```csharp +var loggerFactory = app.Services.GetRequiredService(); +await loggerFactory.DisposeAsync(); +``` \ No newline at end of file diff --git a/Testers/EonaCat.LogStack.Test.Web/Program.cs b/Testers/EonaCat.LogStack.Test.Web/Program.cs index 709f120..adadf8d 100644 --- a/Testers/EonaCat.LogStack.Test.Web/Program.cs +++ b/Testers/EonaCat.LogStack.Test.Web/Program.cs @@ -130,6 +130,8 @@ namespace EonaCat.LogStack.Test.Web var builder = WebApplication.CreateBuilder(args); int onLogCounter = 0; var defaultColor = Console.ForegroundColor; + builder.Services.AddEonaCatLogging(); + //_ = Task.Run(() => //{