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;
 
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() => Trace.Spans.Where(s => s.ParentSpanId == SpanId);
    public OtlpSpan? GetParentSpan() => string.IsNullOrEmpty(ParentSpanId) ? null : Trace.Spans.Where(s => s.SpanId == ParentSpanId).FirstOrDefault();
 
    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 Dictionary<string, string> AllProperties()
    {
        var props = new Dictionary<string, string>
        {
            { "SpanId", SpanId },
            { "Name", Name },
            { "Kind", Kind.ToString() },
        };
 
        if (Status != OtlpSpanStatusCode.Unset)
        {
            props.Add("Status", Status.ToString());
        }
 
        if (!string.IsNullOrEmpty(StatusMessage))
        {
            props.Add("StatusMessage", StatusMessage);
        }
 
        foreach (var kv in Attributes.OrderBy(a => a.Key))
        {
            props.TryAdd(kv.Key, kv.Value);
        }
 
        return props;
    }
 
    private string DebuggerToString()
    {
        return $@"SpanId = {SpanId}, StartTime = {StartTime.ToLocalTime():h:mm:ss.fff tt}, ParentSpanId = {ParentSpanId}, TraceId = {Trace.TraceId}";
    }
}