File: Otlp\Model\OtlpSpan.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.Model.Otlp;
 
namespace Aspire.Dashboard.Otlp.Model;
 
/// <summary>
/// Represents a Span within an Operation (Trace)
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
public class OtlpSpan
{
    public const string PeerServiceAttributeKey = "peer.service";
    public const string UrlFullAttributeKey = "url.full";
    public const string ServerAddressAttributeKey = "server.address";
    public const string ServerPortAttributeKey = "server.port";
    public const string NetPeerNameAttributeKey = "net.peer.name";
    public const string NetPeerPortAttributeKey = "net.peer.port";
    public const string SpanKindAttributeKey = "span.kind";
 
    public string TraceId => Trace.TraceId;
    public OtlpTrace Trace { get; }
    public OtlpApplicationView Source { get; }
 
    public required string SpanId { get; init; }
    public required string? ParentSpanId { get; init; }
    public required string Name { get; init; }
    public required OtlpSpanKind Kind { get; init; }
    public required DateTime StartTime { get; init; }
    public required DateTime EndTime { get; init; }
    public required OtlpSpanStatusCode Status { get; init; }
    public required string? StatusMessage { get; init; }
    public required string? State { get; init; }
    public required KeyValuePair<string, string>[] Attributes { get; init; }
    public required List<OtlpSpanEvent> Events { get; init; }
    public required List<OtlpSpanLink> Links { get; init; }
    public required List<OtlpSpanLink> BackLinks { get; init; }
 
    public OtlpScope Scope { get; }
    public TimeSpan Duration => EndTime - StartTime;
 
    public IEnumerable<OtlpSpan> GetChildSpans() => GetChildSpans(this, Trace.Spans);
    public static IEnumerable<OtlpSpan> GetChildSpans(OtlpSpan parentSpan, OtlpSpanCollection spans) => spans.Where(s => s.ParentSpanId == parentSpan.SpanId);
    public OtlpSpan? GetParentSpan()
    {
        if (string.IsNullOrEmpty(ParentSpanId))
        {
            return null;
        }
 
        if (Trace.Spans.TryGetValue(ParentSpanId, out var span))
        {
            return span;
        }
 
        return null;
    }
 
    public OtlpSpan(OtlpApplicationView applicationView, OtlpTrace trace, OtlpScope scope)
    {
        Source = applicationView;
        Trace = trace;
        Scope = scope;
    }
 
    public static OtlpSpan Clone(OtlpSpan item, OtlpTrace trace)
    {
        return new OtlpSpan(item.Source, trace, item.Scope)
        {
            SpanId = item.SpanId,
            ParentSpanId = item.ParentSpanId,
            Name = item.Name,
            Kind = item.Kind,
            StartTime = item.StartTime,
            EndTime = item.EndTime,
            Status = item.Status,
            StatusMessage = item.StatusMessage,
            State = item.State,
            Attributes = item.Attributes,
            Events = item.Events,
            Links = item.Links,
            BackLinks = item.BackLinks,
        };
    }
 
    public List<OtlpDisplayField> AllProperties()
    {
        var props = new List<OtlpDisplayField>
        {
            new OtlpDisplayField { DisplayName = "SpanId", Key = KnownTraceFields.SpanIdField, Value = SpanId },
            new OtlpDisplayField { DisplayName = "Name", Key = KnownTraceFields.NameField, Value = Name },
            new OtlpDisplayField { DisplayName = "Kind", Key = KnownTraceFields.KindField, Value = Kind.ToString() },
        };
 
        if (Status != OtlpSpanStatusCode.Unset)
        {
            props.Add(new OtlpDisplayField { DisplayName = "Status", Key = KnownTraceFields.StatusField, Value = Status.ToString() });
        }
 
        if (!string.IsNullOrEmpty(StatusMessage))
        {
            props.Add(new OtlpDisplayField { DisplayName = "StatusMessage", Key = KnownTraceFields.StatusField, Value = Status.ToString() });
        }
 
        foreach (var kv in Attributes.OrderBy(a => a.Key))
        {
            props.Add(new OtlpDisplayField { DisplayName = kv.Key, Key = $"unknown-{kv.Key}", Value = kv.Value });
        }
 
        return props;
    }
 
    private string DebuggerToString()
    {
        return $@"SpanId = {SpanId}, StartTime = {StartTime.ToLocalTime():h:mm:ss.fff tt}, ParentSpanId = {ParentSpanId}, TraceId = {Trace.TraceId}";
    }
 
    public static string? GetFieldValue(OtlpSpan span, string field)
    {
        return field switch
        {
            KnownResourceFields.ServiceNameField => span.Source.Application.ApplicationName,
            KnownTraceFields.TraceIdField => span.TraceId,
            KnownTraceFields.SpanIdField => span.SpanId,
            KnownTraceFields.KindField => span.Kind.ToString(),
            KnownTraceFields.StatusField => span.Status.ToString(),
            KnownSourceFields.NameField => span.Scope.ScopeName,
            KnownTraceFields.NameField => span.Name,
            _ => span.Attributes.GetValue(field)
        };
    }
}