Files
EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/ConditionalFlow.cs
T
EonaCat 1560e282b5 Updated README.md
Added more telemetry tooling
2026-06-22 18:58:32 +02:00

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&lt;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" &amp;&amp; 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();
}