File: Model\Otlp\TelemetryFilter.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Resources;
using Microsoft.Extensions.Localization;
 
namespace Aspire.Dashboard.Model.Otlp;
 
public abstract class TelemetryFilter : IEquatable<TelemetryFilter>
{
    public bool Enabled { get; set; } = true;
 
    public abstract bool Equals(TelemetryFilter? other);
}
 
[DebuggerDisplay("{DebuggerDisplayText,nq}")]
public class FieldTelemetryFilter : TelemetryFilter
{
    public string Field { get; set; } = default!;
    public string? FallbackField { get; set; }
    public FilterCondition Condition { get; set; }
    public string Value { get; set; } = default!;
 
    private string DebuggerDisplayText => $"{Field} {ConditionToString(Condition, null)} {Value}";
 
    public string GetDisplayText(IStringLocalizer<StructuredFiltering> loc) => $"{ResolveFieldName(Field)} {ConditionToString(Condition, loc)} {Value}";
 
    public static string ResolveFieldName(string name)
    {
        return name switch
        {
            KnownStructuredLogFields.MessageField => "Message",
            KnownStructuredLogFields.TraceIdField => "TraceId",
            KnownStructuredLogFields.SpanIdField => "SpanId",
            KnownStructuredLogFields.OriginalFormatField => "OriginalFormat",
            KnownStructuredLogFields.CategoryField => "Category",
            KnownStructuredLogFields.EventNameField => "EventName",
            KnownStructuredLogFields.TimestampField => "Timestamp",
            KnownTraceFields.NameField => "Name",
            KnownTraceFields.SpanIdField => "SpanId",
            KnownTraceFields.TraceIdField => "TraceId",
            KnownTraceFields.KindField => "Kind",
            KnownTraceFields.StatusField => "Status",
            KnownTraceFields.DurationField => "Duration (ms)",
            KnownTraceFields.TimestampField => "Timestamp",
            KnownSourceFields.NameField => "Source",
            KnownResourceFields.ServiceNameField => "Resource",
            _ => name
        };
    }
 
    public static bool IsNumericField(string name) => name is KnownTraceFields.DurationField;
 
    /// <summary>
    /// Returns true when the field represents a timestamp that should be compared as a date.
    /// Filter values for these fields are parsed as <see cref="DateTime"/> and compared using
    /// milliseconds stored in the field value from <see cref="OtlpSpan.GetFieldValue"/> / <see cref="OtlpLogEntry.GetFieldValue"/>.
    /// </summary>
    public static bool IsDateField(string name) => name is KnownTraceFields.TimestampField or KnownStructuredLogFields.TimestampField;
 
    internal static FieldType GetFieldType(string name)
    {
        if (IsNumericField(name))
        {
            return FieldType.Numeric;
        }
        if (IsDateField(name))
        {
            return FieldType.Date;
        }
        return FieldType.String;
    }
 
    public static string ConditionToString(FilterCondition c, IStringLocalizer<StructuredFiltering>? loc) =>
        c switch
        {
            FilterCondition.Equals => "==",
            FilterCondition.Contains => loc?[nameof(StructuredFiltering.ConditionContains)] ?? "contains",
            FilterCondition.GreaterThan => ">",
            FilterCondition.LessThan => "<",
            FilterCondition.GreaterThanOrEqual => ">=",
            FilterCondition.LessThanOrEqual => "<=",
            FilterCondition.NotEqual => "!=",
            FilterCondition.NotContains => loc?[nameof(StructuredFiltering.ConditionNotContains)] ?? "not contains",
            _ => throw new ArgumentOutOfRangeException(nameof(c), c, null)
        };
 
    public override bool Equals(TelemetryFilter? other)
    {
        var otherFilter = other as FieldTelemetryFilter;
        if (otherFilter == null)
        {
            return false;
        }
 
        if (Field != otherFilter.Field)
        {
            return false;
        }
 
        if (Condition != otherFilter.Condition)
        {
            return false;
        }
 
        if (!string.Equals(Value, otherFilter.Value, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }
 
        return true;
    }
}
 
internal enum FieldType
{
    String,
    Numeric,
    Date
}