220 lines
7.0 KiB
C#
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);
|
|
}
|
|
}
|