| File: Model\SpanType.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 Aspire.Dashboard.Model.Otlp; using Aspire.Dashboard.Otlp.Model; using Aspire.Dashboard.Resources; using Microsoft.Extensions.Localization; namespace Aspire.Dashboard.Model; public sealed class SpanType { public string Name { get; } public TelemetryFilter Filter { get; } private SpanType(string name, TelemetryFilter filter) { Name = name; Filter = filter; } // https://opentelemetry.io/docs/specs/semconv/http/http-spans/ public static readonly SpanType Http = new SpanType( "http", new SpanHasAttributeTelemetryFilter(["http.request.method"])); // https://opentelemetry.io/docs/specs/semconv/database/database-spans/ public static readonly SpanType Database = new SpanType( "database", new SpanHasAttributeTelemetryFilter(["db.system.name", "db.system"])); // https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/ public static readonly SpanType Messaging = new SpanType( "messaging", new SpanHasAttributeTelemetryFilter(["messaging.system"])); // https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ public static readonly SpanType Rpc = new SpanType( "rpc", new SpanHasAttributeTelemetryFilter(["rpc.system"])); // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/ public static readonly SpanType GenAI = new SpanType( "genai", new SpanHasAttributeTelemetryFilter(["gen_ai.system", "gen_ai.provider.name", "gen_ai.operation.name"])); // AWSSDK: https://github.com/aws/aws-sdk-net/blob/3acb8af23e9df891b4e37cf4cda9c5131be0389c/sdk/src/Core/Amazon.Runtime/Telemetry/TelemetryConstants.cs#L26 // Azure: https://github.com/Azure/azure-sdk-for-net/blob/1af7c19fe8dcb7f7f843730117accbd6927ad2a4/sdk/ai/Azure.AI.Inference/src/Telemetry/OpenTelemetryConstants.cs#L46 public static readonly SpanType Cloud = new SpanType( "cloud", new SpanScopePrefixTelemetryFilter(["Azure", "AWSSDK"])); public static readonly SpanType Other = new SpanType( "other", new SpanNoMatchTelemetryFilter([ Http.Filter, Database.Filter, Messaging.Filter, Rpc.Filter, GenAI.Filter, Cloud.Filter ])); public static List<SelectViewModel<SpanType>> CreateKnownSpanTypes(IStringLocalizer<ControlsStrings> loc) { return new List<SelectViewModel<SpanType>> { new() { Id = null, Name = loc[nameof(ControlsStrings.LabelAll)] }, new() { Id = Http, Name = loc[nameof(ControlsStrings.SpanTypeHttp)] }, new() { Id = Database, Name = loc[nameof(ControlsStrings.SpanTypeDatabase)] }, new() { Id = Messaging, Name = loc[nameof(ControlsStrings.SpanTypeMessaging)] }, new() { Id = Rpc, Name = loc[nameof(ControlsStrings.SpanTypeRpc)] }, new() { Id = GenAI, Name = loc[nameof(ControlsStrings.SpanTypeGenAI)] }, new() { Id = Cloud, Name = loc[nameof(ControlsStrings.SpanTypeCloud)] }, new() { Id = Other, Name = loc[nameof(ControlsStrings.LabelOther)] }, }; } } public sealed class SpanHasAttributeTelemetryFilter : TelemetryFilter { private readonly string[] _attributeNames; public SpanHasAttributeTelemetryFilter(string[] attributeNames) { _attributeNames = attributeNames; } public override IEnumerable<OtlpLogEntry> Apply(IEnumerable<OtlpLogEntry> input) { throw new NotSupportedException(); } public override bool Apply(OtlpSpan span) { foreach (var attributeName in _attributeNames) { if (!string.IsNullOrEmpty(span.Attributes.GetValue(attributeName))) { return true; } } return false; } public override bool Equals(TelemetryFilter? other) { return false; } } public sealed class SpanNoMatchTelemetryFilter : TelemetryFilter { private readonly TelemetryFilter[] _filters; public SpanNoMatchTelemetryFilter(TelemetryFilter[] filters) { _filters = filters; } public override IEnumerable<OtlpLogEntry> Apply(IEnumerable<OtlpLogEntry> input) { throw new NotSupportedException(); } public override bool Apply(OtlpSpan span) { foreach (var filter in _filters) { if (filter.Apply(span)) { return false; } } return true; } public override bool Equals(TelemetryFilter? other) { return false; } } public sealed class SpanScopePrefixTelemetryFilter : TelemetryFilter { private readonly string[] _scopePrefixes; public SpanScopePrefixTelemetryFilter(string[] scopePrefixes) { _scopePrefixes = scopePrefixes; } public override IEnumerable<OtlpLogEntry> Apply(IEnumerable<OtlpLogEntry> input) { throw new NotSupportedException(); } public override bool Apply(OtlpSpan span) { foreach (var scopePrefix in _scopePrefixes) { if (span.Scope.Name.StartsWith(scopePrefix, StringComparison.OrdinalIgnoreCase)) { // Exact match. if (span.Scope.Name.Length == scopePrefix.Length) { return true; } // Starts with prefix followed by delimiter. if (span.Scope.Name[scopePrefix.Length] == '.') { return true; } return false; } } return false; } public override bool Equals(TelemetryFilter? other) { return false; } }