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
+2 -2
View File
@@ -14,7 +14,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
<Copyright>EonaCat (Jeroen Saey)</Copyright>
<PackageTags>EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey</PackageTags>
<PackageIconUrl />
<FileVersion>0.0.6</FileVersion>
<FileVersion>0.0.7</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
@@ -25,7 +25,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
</PropertyGroup>
<PropertyGroup>
<EVRevisionFormat>0.0.6+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVRevisionFormat>0.0.7+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVDefault>true</EVDefault>
<EVInfo>true</EVInfo>
<EVTagMatch>v[0-9]*</EVTagMatch>
+45 -9
View File
@@ -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)
@@ -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);
}
}
@@ -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.
/// <summary>
/// Extension methods for integrating EonaCat LogStack with Microsoft Dependency Injection
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers EonaCat LogStack as the logging provider in the dependency injection container
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="minimumLevel">The minimum log level to process</param>
/// <param name="timestampMode">The timestamp mode to use</param>
/// <returns>The service collection for chaining</returns>
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<ILoggerFactory>(loggerFactory);
services.AddSingleton<Microsoft.Extensions.Logging.ILoggerFactory>(
new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory));
// Also register as ILogger for constructor injection
services.AddSingleton(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger("Default"));
services.AddSingleton(sp =>
new MicrosoftExtensionsLoggerAdapter(
sp.GetRequiredService<ILoggerFactory>().CreateLogger("Default")));
return services;
}
/// <summary>
/// Registers EonaCat LogStack using an existing EonaCatLogStack instance
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="logStack">The pre-configured EonaCatLogStack instance</param>
/// <returns>The service collection for chaining</returns>
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<ILoggerFactory>(loggerFactory);
services.AddSingleton<Microsoft.Extensions.Logging.ILoggerFactory>(
new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory));
// Also register as ILogger for constructor injection
services.AddSingleton(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger("Default"));
services.AddSingleton(sp =>
new MicrosoftExtensionsLoggerAdapter(
sp.GetRequiredService<ILoggerFactory>().CreateLogger("Default")));
return services;
}
/// <summary>
/// Registers EonaCat LogStack with a configuration callback
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="configure">Callback to configure the EonaCatLogStack instance</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddEonaCatLogging(
this IServiceCollection services,
Action<EonaCatLogStack> 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<ILoggerFactory>(loggerFactory);
services.AddSingleton<Microsoft.Extensions.Logging.ILoggerFactory>(
new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory));
// Also register as ILogger for constructor injection
services.AddSingleton(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger("Default"));
services.AddSingleton(sp =>
new MicrosoftExtensionsLoggerAdapter(
sp.GetRequiredService<ILoggerFactory>().CreateLogger("Default")));
return services;
}
/// <summary>
/// Registers EonaCat LogStack using a fluent LogBuilder configuration.
/// This is the recommended approach for DI integration with fluent configuration.
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="category">The category/name of the logger</param>
/// <param name="configure">Callback to configure the LogBuilder instance</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddEonaCatLogging(
this IServiceCollection services,
string category,
Action<LogBuilder> 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<ILoggerFactory>(loggerFactory);
services.AddSingleton<Microsoft.Extensions.Logging.ILoggerFactory>(
new MicrosoftExtensionsLoggerFactoryAdapter(loggerFactory));
// Also register as ILogger for constructor injection
services.AddSingleton(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger(category));
services.AddSingleton(sp =>
new MicrosoftExtensionsLoggerAdapter(
sp.GetRequiredService<ILoggerFactory>().CreateLogger(category)));
return services;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="configure">Callback to configure the LogBuilder instance</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddEonaCatLogging(
this IServiceCollection services,
Action<LogBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
return services.AddEonaCatLogging("Application", configure);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="category">The category/name of the logger</param>
/// <param name="configure">Callback to configure the LogBuilder instance</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddEonaCatLoggingFactory(
this IServiceCollection services,
string category,
Action<LogBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
services.AddSingleton<ILoggerFactory>(sp =>
{
var builder = new LogBuilder(category);
configure(builder);
var logStack = builder.Build();
return new LoggerFactory(logStack);
});
services.AddSingleton<Microsoft.Extensions.Logging.ILoggerFactory>(sp =>
new MicrosoftExtensionsLoggerFactoryAdapter(
sp.GetRequiredService<ILoggerFactory>()));
// Also register as ILogger for constructor injection
services.AddSingleton(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger(category));
services.AddSingleton(sp =>
new MicrosoftExtensionsLoggerAdapter(
sp.GetRequiredService<ILoggerFactory>().CreateLogger(category)));
return services;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="services">The service collection to register with</param>
/// <param name="configure">Callback to configure the LogBuilder instance</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddEonaCatLoggingFactory(
this IServiceCollection services,
Action<LogBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
return services.AddEonaCatLoggingFactory("Application", configure);
}
}
+335 -1
View File
@@ -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.
`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<EonaCatLogStack>`:
```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<ILoggerFactory>();
await loggerFactory.DisposeAsync();
```
@@ -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(() =>
//{