Skip to main content
Filtering allows you to control which log events are written to sinks based on criteria beyond just the log level. Filters can inspect the entire log event, including properties, level, message, and exception, to decide whether an event should be written or suppressed.

Why Filter Logs?

Log filtering helps you:
  • Reduce log volume and storage costs
  • Remove noisy or redundant events
  • Route different events to different sinks
  • Exclude sensitive information
  • Focus on specific scenarios during troubleshooting

Level-Based Filtering

The simplest form of filtering is setting a minimum log level:
var log = new LoggerConfiguration()
    .MinimumLevel.Information()  // Filter out Verbose and Debug
    .WriteTo.Console()
    .CreateLogger();

Log.Debug("This won't be written");
Log.Information("This will be written");

Per-Sink Level Filtering

Apply different level filters to different sinks:
var log = new LoggerConfiguration()
    .MinimumLevel.Verbose()  // Allow all events
    .WriteTo.Console(
        restrictedToMinimumLevel: LogEventLevel.Information)  // Console: Info+
    .WriteTo.File("debug.log",
        restrictedToMinimumLevel: LogEventLevel.Debug)  // File: Debug+
    .WriteTo.Seq("http://localhost:5341",
        restrictedToMinimumLevel: LogEventLevel.Verbose)  // Seq: All events
    .CreateLogger();

Override by Namespace

Reduce noise from specific namespaces:
var log = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Warning)
    .WriteTo.Console()
    .CreateLogger();

// Microsoft.* logs only Information+
// Microsoft.EntityFrameworkCore.* logs only Warning+
// System.* logs only Warning+
// Everything else logs Debug+

The ILogEventFilter Interface

For more advanced filtering, implement the ILogEventFilter interface (Core/ILogEventFilter.cs:20-29):
public interface ILogEventFilter
{
    bool IsEnabled(LogEvent logEvent);
}
The filter returns:
  • true - Event should be written
  • false - Event should be suppressed
From the source code (Core/Sinks/FilteringSink.cs:17-50), filters are applied before sinks:
class FilteringSink : ILogEventSink
{
    readonly ILogEventSink _sink;
    readonly ILogEventFilter[] _filters;

    public void Emit(LogEvent logEvent)
    {
        foreach (var logEventFilter in _filters)
        {
            if (!logEventFilter.IsEnabled(logEvent))
                return;  // Event suppressed
        }
        
        _sink.Emit(logEvent);  // Event passed all filters
    }
}

Filter Expression Syntax

Serilog provides a rich filtering syntax through the Serilog.Filters.Expressions package:
using Serilog.Filters;

var log = new LoggerConfiguration()
    .Filter.ByExcluding("@Level = 'Debug' and SourceContext = 'MyApp.Noisy'")
    .WriteTo.Console()
    .CreateLogger();

Filter by Level

// Exclude all Debug events
.Filter.ByExcluding("@Level = 'Debug'")

// Include only Error and Fatal
.Filter.ByIncludingOnly("@Level in ['Error', 'Fatal']")

// Exclude Information from specific source
.Filter.ByExcluding("@Level = 'Information' and SourceContext = 'MyApp.Verbose'")

Filter by Property

// Exclude events with specific user
.Filter.ByExcluding("UserId = 'system'")

// Include only production events
.Filter.ByIncludingOnly("Environment = 'Production'")

// Exclude health check requests
.Filter.ByExcluding("RequestPath = '/health'")

// Include slow requests only
.Filter.ByIncludingOnly("ElapsedMilliseconds > 1000")

Filter by Message Template

// Exclude specific message template
.Filter.ByExcluding("@MessageTemplate = 'Cache hit for {Key}'")

// Include messages matching pattern
.Filter.ByIncludingOnly("@MessageTemplate like '%database%'")

Complex Expressions

// Exclude debug events from Microsoft or System namespaces
.Filter.ByExcluding(
    "@Level = 'Debug' and (StartsWith(SourceContext, 'Microsoft') or StartsWith(SourceContext, 'System'))")

// Include only errors with specific conditions
.Filter.ByIncludingOnly(
    "@Level >= 'Error' or (RequestPath like '/api/%' and ElapsedMilliseconds > 500)")

// Exclude common, low-value events
.Filter.ByExcluding(
    "(@MessageTemplate = 'Cache hit for {Key}') or " +
    "(@MessageTemplate = 'Configuration loaded') or " +
    "(RequestPath in ['/health', '/metrics', '/ready'])")

Programmatic Filtering

Use lambda expressions for custom filtering logic:

ByExcluding

Exclude events matching a predicate:
var log = new LoggerConfiguration()
    .Filter.ByExcluding(logEvent => 
        logEvent.Level == LogEventLevel.Debug &&
        logEvent.Properties.ContainsKey("SourceContext") &&
        logEvent.Properties["SourceContext"].ToString().Contains("Microsoft"))
    .WriteTo.Console()
    .CreateLogger();

ByIncludingOnly

Include only events matching a predicate:
var log = new LoggerConfiguration()
    .Filter.ByIncludingOnly(logEvent => 
        logEvent.Level >= LogEventLevel.Warning ||
        logEvent.Properties.ContainsKey("Critical"))
    .WriteTo.Console()
    .CreateLogger();

Filter by Exception Type

var log = new LoggerConfiguration()
    .Filter.ByExcluding(logEvent => 
        logEvent.Exception is TaskCanceledException)
    .WriteTo.Console()
    .CreateLogger();

Filter by Property Value

var log = new LoggerConfiguration()
    .Filter.ByExcluding(logEvent => 
    {
        if (logEvent.Properties.TryGetValue("RequestPath", out var path))
        {
            var pathStr = path.ToString().Trim('"');
            return pathStr == "/health" || pathStr == "/metrics";
        }
        return false;
    })
    .WriteTo.Console()
    .CreateLogger();

Per-Sink Filtering

Apply different filters to different sinks:
var log = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(e => e.Level >= LogEventLevel.Warning)
        .WriteTo.File("warnings.log"))  // Only warnings and above
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly("@MessageTemplate like '%slow%'")
        .WriteTo.File("performance.log"))  // Only performance-related events
    .CreateLogger();

Practical Filtering Examples

Exclude Health Checks

var log = new LoggerConfiguration()
    .Filter.ByExcluding(e => 
        e.Properties.TryGetValue("RequestPath", out var path) &&
        (path.ToString().Contains("/health") || 
         path.ToString().Contains("/ready") ||
         path.ToString().Contains("/live")))
    .WriteTo.Console()
    .CreateLogger();

Reduce Entity Framework Noise

var log = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", 
        LogEventLevel.Warning)
    .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Infrastructure",
        LogEventLevel.Warning)
    .WriteTo.Console()
    .CreateLogger();

Include Only Business Events

var log = new LoggerConfiguration()
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(e => 
            e.Properties.ContainsKey("OrderId") ||
            e.Properties.ContainsKey("CustomerId") ||
            e.Properties.ContainsKey("TransactionId"))
        .WriteTo.Seq("http://localhost:5341"))
    .WriteTo.Console()  // All events to console
    .CreateLogger();

Filter by Time-Based Conditions

var log = new LoggerConfiguration()
    .Filter.ByExcluding(e => 
    {
        // Exclude verbose events during business hours
        if (e.Level < LogEventLevel.Information)
        {
            var hour = DateTime.Now.Hour;
            return hour >= 9 && hour < 17;  // 9 AM to 5 PM
        }
        return false;
    })
    .WriteTo.Console()
    .CreateLogger();

Sample Frequent Events

public class SamplingFilter : ILogEventFilter
{
    readonly int _keepEveryNth;
    int _counter;

    public SamplingFilter(int keepEveryNth)
    {
        _keepEveryNth = keepEveryNth;
    }

    public bool IsEnabled(LogEvent logEvent)
    {
        // Keep every Nth event, suppress others
        return Interlocked.Increment(ref _counter) % _keepEveryNth == 0;
    }
}

// Use it:
var log = new LoggerConfiguration()
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly("@MessageTemplate = 'Cache access'")
        .Filter.With(new SamplingFilter(keepEveryNth: 100))  // Keep 1 in 100
        .WriteTo.File("cache-sample.log"))
    .WriteTo.Console()  // All other events
    .CreateLogger();

Dynamic Filtering

Implement filters that can be updated at runtime:
public class DynamicLevelFilter : ILogEventFilter
{
    public LogEventLevel MinimumLevel { get; set; } = LogEventLevel.Information;

    public bool IsEnabled(LogEvent logEvent)
    {
        return logEvent.Level >= MinimumLevel;
    }
}

var dynamicFilter = new DynamicLevelFilter();

var log = new LoggerConfiguration()
    .Filter.With(dynamicFilter)
    .WriteTo.Console()
    .CreateLogger();

// Later, change the filter:
dynamicFilter.MinimumLevel = LogEventLevel.Debug;  // Temporarily enable debug

// Process requests...

dynamicFilter.MinimumLevel = LogEventLevel.Information;  // Return to normal

Best Practices

1. Filter Early

Filter at the logger configuration level when possible to avoid creating events that will be suppressed:
// Events are not created if level is disabled
var log = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console()
    .CreateLogger();

2. Use Namespace Overrides for Framework Logs

var log = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .MinimumLevel.Override("System", LogEventLevel.Warning)
    .WriteTo.Console()
    .CreateLogger();

3. Combine Multiple Filters

Filters are applied in order. Use this to build complex logic:
var log = new LoggerConfiguration()
    // First, include only relevant events
    .Filter.ByIncludingOnly(e => 
        e.Level >= LogEventLevel.Information ||
        e.Properties.ContainsKey("Important"))
    // Then, exclude health checks
    .Filter.ByExcluding("RequestPath = '/health'")
    .WriteTo.Console()
    .CreateLogger();

4. Test Your Filters

Ensure filters work as expected:
[Fact]
public void HealthCheckRequestsAreFiltered()
{
    var events = new List<LogEvent>();
    var sink = new DelegatingSink(events.Add);
    
    var log = new LoggerConfiguration()
        .Filter.ByExcluding("RequestPath = '/health'")
        .WriteTo.Sink(sink)
        .CreateLogger();
    
    log.Information("Request to {RequestPath}", "/health");
    log.Information("Request to {RequestPath}", "/api/users");
    
    Assert.Single(events);  // Only /api/users logged
    Assert.Contains("/api/users", events[0].RenderMessage());
}

5. Monitor Filter Impact

Log when filters suppress significant events:
public class MonitoredFilter : ILogEventFilter
{
    readonly ILogEventFilter _innerFilter;
    int _suppressedCount;

    public MonitoredFilter(ILogEventFilter innerFilter)
    {
        _innerFilter = innerFilter;
    }

    public bool IsEnabled(LogEvent logEvent)
    {
        var result = _innerFilter.IsEnabled(logEvent);
        if (!result)
        {
            var count = Interlocked.Increment(ref _suppressedCount);
            if (count % 1000 == 0)
            {
                // Report every 1000 suppressions
                Console.WriteLine($"Filter has suppressed {count} events");
            }
        }
        return result;
    }
}
Filtering is powerful but use it judiciously. Over-filtering can hide important diagnostic information. Always ensure critical errors and business events are never filtered.

Build docs developers (and LLMs) love