File: Sampling\LogSamplingRuleSelector.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Telemetry\Microsoft.Extensions.Telemetry.csproj (Microsoft.Extensions.Telemetry)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable CA1307 // Specify StringComparison for clarity
#pragma warning disable S1659 // Multiple variables should not be declared on the same line
#pragma warning disable S2302 // "nameof" should be used
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.Extensions.Diagnostics.Sampling;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.Extensions.Diagnostics.Sampling;
 
internal sealed class LogSamplingRuleSelector<T>
    where T : class, ILogSamplingFilterRule
{
    private readonly ConcurrentDictionary<(string, LogLevel, EventId), T?> _ruleCache = new();
 
    public void InvalidateCache()
    {
        _ruleCache.Clear();
    }
 
    public T? Select(IList<T> rules, string category, LogLevel logLevel, EventId eventId)
    {
        return _ruleCache.GetOrAdd((category, logLevel, eventId), _ =>
        {
            // Filter rule selection:
            // 0. Ignore rules whose LogLevel is defined but lower than the requested logLevel
            // 1. Ignore rules whose EventId is defined but different from the requested eventId
            // 2. For category filtering, handle optional wildcards (only one '*' allowed) and match the prefix/suffix ignoring case
            // 3. Out of the matched set, pick the rule with the longest matching category
            // 4. If no rules match by category, accept rules without a category
            // 5. If exactly one rule remains, use it; if multiple remain, select the last in the list
            T? current = null;
            foreach (T rule in rules)
            {
                if (IsBetter(rule, current, category, logLevel, eventId))
                {
                    current = rule;
                }
            }
 
            return current;
        });
    }
 
    private static bool IsBetter(T rule, T? current, string category, LogLevel logLevel, EventId eventId)
    {
        // Skip rules with inapplicable log level
        if (rule.LogLevel is not null && rule.LogLevel < logLevel)
        {
            return false;
        }
 
        // Skip rules with inapplicable event id
        if (rule.EventId is not null && rule.EventId != eventId)
        {
            return false;
        }
 
        // Skip rules with inapplicable category
        string? categoryName = rule.CategoryName;
        if (categoryName is not null)
        {
            const char WildcardChar = '*';
 
            int wildcardIndex = categoryName.IndexOf(WildcardChar);
            if (wildcardIndex >= 0 &&
                categoryName.IndexOf(WildcardChar, wildcardIndex + 1) >= 0)
            {
                throw new InvalidOperationException("Only one wildcard character is allowed in category name.");
            }
 
            ReadOnlySpan<char> prefix, suffix;
            if (wildcardIndex == -1)
            {
                prefix = categoryName.AsSpan();
                suffix = default;
            }
            else
            {
                prefix = categoryName.AsSpan(0, wildcardIndex);
                suffix = categoryName.AsSpan(wildcardIndex + 1);
            }
 
            if (!category.AsSpan().StartsWith(prefix, StringComparison.OrdinalIgnoreCase) ||
                !category.AsSpan().EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
        }
 
        // Decide whose category is better - rule vs current
        if (current?.CategoryName is not null)
        {
            if (rule.CategoryName is null)
            {
                return false;
            }
 
            if (current.CategoryName.Length > rule.CategoryName.Length)
            {
                return false;
            }
        }
 
        // Decide whose log level is better - rule vs current
        if (current?.LogLevel is not null)
        {
            if (rule.LogLevel is null)
            {
                return false;
            }
 
            if (current.LogLevel < rule.LogLevel)
            {
                return false;
            }
        }
 
        // Decide whose event id is better - rule vs current
        if (rule.EventId is null)
        {
            if (current?.EventId is not null)
            {
                return false;
            }
        }
 
        return true;
    }
}