104 lines
3.5 KiB
C#
104 lines
3.5 KiB
C#
using EonaCat.LogStack.Core;
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
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 decorator flow that forwards events to an inner flow only when a user-supplied
|
|
/// predicate returns <c>true</c>.
|
|
///
|
|
/// Common use-cases:
|
|
/// • Category-based routing - route only "Database" category events to file
|
|
/// • Level-range filtering - forward Warning≤level<Error to one flow
|
|
/// • Property filtering - only forward events that carry a specific property
|
|
/// • Exception routing - send events with SqlException to a special alert flow
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Route only Database category errors to a dedicated file
|
|
/// new LogBuilder()
|
|
/// .WriteToConditional(
|
|
/// predicate: e => e.Category == "Database" && e.Level >= LogLevel.Error,
|
|
/// inner: new FileFlow("logs/db-errors"))
|
|
/// </code>
|
|
/// </example>
|
|
/// </summary>
|
|
public sealed class ConditionalFlow : FlowBase
|
|
{
|
|
private readonly IFlow _inner;
|
|
private readonly Func<LogEvent, bool> _predicate;
|
|
|
|
public ConditionalFlow(
|
|
IFlow inner,
|
|
Func<LogEvent, bool> predicate,
|
|
LogLevel minimumLevel = LogLevel.Trace)
|
|
: base($"Conditional({inner?.Name ?? "?"})", minimumLevel)
|
|
{
|
|
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
|
_predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!IsEnabled || !IsLogLevelEnabled(logEvent))
|
|
{
|
|
return WriteResult.LevelFiltered;
|
|
}
|
|
|
|
bool matches;
|
|
try { matches = _predicate(logEvent); }
|
|
catch { matches = false; }
|
|
|
|
if (!matches)
|
|
{
|
|
Interlocked.Increment(ref DroppedCount);
|
|
return WriteResult.Dropped;
|
|
}
|
|
|
|
var result = await _inner.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
|
|
Interlocked.Increment(ref BlastedCount);
|
|
return result;
|
|
}
|
|
|
|
public override async Task<WriteResult> BlastBatchAsync(
|
|
ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!IsEnabled)
|
|
{
|
|
return WriteResult.FlowDisabled;
|
|
}
|
|
|
|
var result = WriteResult.Success;
|
|
var eventsArray = logEvents.ToArray();
|
|
foreach (var ev in eventsArray)
|
|
{
|
|
bool matches;
|
|
try { matches = ev.Level >= MinimumLevel && _predicate(ev); }
|
|
catch { matches = false; }
|
|
|
|
if (!matches) { Interlocked.Increment(ref DroppedCount); continue; }
|
|
|
|
var r = await _inner.BlastAsync(ev, cancellationToken).ConfigureAwait(false);
|
|
Interlocked.Increment(ref BlastedCount);
|
|
if (r != WriteResult.Success)
|
|
{
|
|
result = r;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public override Task FlushAsync(CancellationToken cancellationToken = default)
|
|
=> _inner.FlushAsync(cancellationToken);
|
|
|
|
public override ValueTask DisposeAsync() => _inner.DisposeAsync();
|
|
}
|