Files
EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/DelegatingLoggerFlow.cs
T
2026-08-04 21:47:43 +02:00

220 lines
7.0 KiB
C#

using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace EonaCat.LogStack.Flows;
// 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>
/// A flow that delegates log events to another logger instance.
/// This enables chaining multiple logger instances together, allowing logs to flow
/// through different logging backends or implementations.
/// </summary>
public sealed class DelegatingLoggerFlow : FlowBase
{
private readonly EonaCatLogStack _targetLogger;
private readonly Queue<LogEvent> _delegationQueue = new Queue<LogEvent>();
private readonly object _queueLock = new object();
private int _disposed;
/// <summary>
/// Creates a new delegating logger flow
/// </summary>
/// <param name="targetLogger">The logger to delegate log events to</param>
/// <param name="minimumLevel">The minimum log level to process</param>
public DelegatingLoggerFlow(
EonaCatLogStack targetLogger,
LogLevel minimumLevel = LogLevel.Trace)
: base($"DelegatingFlow({targetLogger?.GetType().Name ?? "UnknownLogger"})", minimumLevel)
{
_targetLogger = targetLogger ?? throw new ArgumentNullException(nameof(targetLogger));
}
/// <summary>
/// The target logger this flow delegates to
/// </summary>
public EonaCatLogStack TargetLogger => _targetLogger;
/// <summary>
/// Blasts (sends) a single log event to the target logger
/// </summary>
public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
{
if (!IsEnabled || !IsLogLevelEnabled(logEvent))
{
Interlocked.Increment(ref DroppedCount);
return WriteResult.LevelFiltered;
}
try
{
// Queue the event for delegation with cancellation support
lock (_queueLock)
{
if (_disposed != 0)
{
Interlocked.Increment(ref DroppedCount);
return WriteResult.Dropped;
}
_delegationQueue.Enqueue(logEvent);
}
// Delegate to the target logger's logging mechanism
// This is done asynchronously to prevent blocking
await Task.Run(() => DelegateLogEvent(logEvent), cancellationToken).ConfigureAwait(false);
Interlocked.Increment(ref BlastedCount);
return WriteResult.Success;
}
catch (Exception ex)
{
Interlocked.Increment(ref DroppedCount);
System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow error: {ex.Message}");
return WriteResult.Failed;
}
}
/// <summary>
/// Blasts (sends) a batch of log events to the target logger
/// </summary>
public override async Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
{
if (!IsEnabled)
{
Interlocked.Add(ref DroppedCount, logEvents.Length);
return WriteResult.LevelFiltered;
}
var result = WriteResult.Success;
var eventsArray = logEvents.ToArray();
foreach (var logEvent in eventsArray)
{
try
{
if (IsLogLevelEnabled(logEvent))
{
lock (_queueLock)
{
if (_disposed == 0)
{
_delegationQueue.Enqueue(logEvent);
}
}
await Task.Run(() => DelegateLogEvent(logEvent), cancellationToken).ConfigureAwait(false);
Interlocked.Increment(ref BlastedCount);
}
else
{
Interlocked.Increment(ref DroppedCount);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow batch error: {ex.Message}");
Interlocked.Increment(ref DroppedCount);
result = WriteResult.Failed;
}
}
return result;
}
/// <summary>
/// Flushes any pending log events to the target logger
/// </summary>
public override async Task FlushAsync(CancellationToken cancellationToken = default)
{
Queue<LogEvent> pendingEvents;
lock (_queueLock)
{
if (_delegationQueue.Count == 0)
{
return;
}
pendingEvents = new Queue<LogEvent>(_delegationQueue);
_delegationQueue.Clear();
}
// Process remaining events
foreach (var logEvent in pendingEvents)
{
try
{
DelegateLogEvent(logEvent);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow flush error: {ex.Message}");
}
}
await Task.CompletedTask.ConfigureAwait(false);
}
/// <summary>
/// Gets diagnostic information about this flow
/// </summary>
public override FlowDiagnostics GetDiagnostics()
{
var diagnostics = base.GetDiagnostics();
diagnostics.Name = $"{Name} (delegates to {TargetLogger.GetType().Name})";
return diagnostics;
}
private void DelegateLogEvent(LogEvent logEvent)
{
if (_disposed != 0)
{
return;
}
try
{
// Delegate by re-logging through the target logger using its public Log methods
// We reconstruct the logging call with the information from the LogEvent
var messageStr = logEvent.Message.ToString();
if (logEvent.Exception != null)
{
_targetLogger.Log(logEvent.Level, logEvent.Exception, messageStr);
}
else
{
_targetLogger.Log(messageStr, logEvent.Level);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to delegate log to target logger: {ex.Message}");
}
}
/// <summary>
/// Disposes the flow and clears the delegation queue
/// </summary>
public override async ValueTask DisposeAsync()
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}
await FlushAsync().ConfigureAwait(false);
lock (_queueLock)
{
_delegationQueue.Clear();
}
await base.DisposeAsync().ConfigureAwait(false);
}
}