using System.Diagnostics; using System.Globalization; namespace BuildingBlocks.OpenTelemetryCollector; internal static class ActivityExtensions { /// /// Retrieves the tags from the parent of the current Activity, if available. /// /// The current Activity. /// A dictionary containing the parent tags, or an empty dictionary if no parent tags are available. public static Dictionary GetParentTags(this Activity activity) { ArgumentNullException.ThrowIfNull(activity); var parentTags = new Dictionary(); // Check if the current activity has a parent var parentActivity = activity.Parent; if (parentActivity != null) { foreach (var tag in parentActivity.Tags) { parentTags[tag.Key] = tag.Value; } } else { // If no parent Activity is available, check for links foreach (var link in activity.Links) { // Extract tags from the first link's context (assuming it's the parent-like context) if (link.Tags != null) { foreach (var tag in link.Tags) { parentTags[tag.Key] = tag.Value; } } // Break after processing the first link, as there should only be one parent context. break; } } return parentTags; } /// /// Extracts important information from an Activity into an ActivityInfo object. /// /// The Activity from which to extract information. /// An ActivityInfo object containing the extracted information. public static ActivityInfo ExtractImportantInformation(this Activity activity) { ArgumentNullException.ThrowIfNull(activity); var activityInfo = new ActivityInfo { Name = activity.DisplayName, StartTime = activity.StartTimeUtc, Duration = activity.Duration, Status = activity.Tags.FirstOrDefault(tag => tag.Key == TelemetryTags.Tracing.Otel.StatusCode).Value ?? "Unknown", StatusDescription = activity .Tags.FirstOrDefault(tag => tag.Key == TelemetryTags.Tracing.Otel.StatusDescription) .Value, Tags = activity.Tags.ToDictionary(tag => tag.Key, tag => tag.Value), Events = activity .Events.Select(e => new ActivityEventInfo { Name = e.Name, Timestamp = e.Timestamp, Attributes = e.Tags.ToDictionary(tag => tag.Key, tag => tag.Value), }) .ToList(), TraceId = activity.TraceId.ToString(), SpanId = activity.SpanId.ToString(), }; return activityInfo; } /// /// Sets an "OK" status on the provided Activity, indicating a successful operation. /// /// The Activity to update. /// An optional description of the successful operation. /// The updated Activity with the status and tags set. public static Activity SetOkStatus(this Activity activity, string? description = null) { ArgumentNullException.ThrowIfNull(activity); // Set the status of the activity to "OK" activity.SetStatus(ActivityStatusCode.Ok, description); // Add telemetry tags for status activity.SetTag( TelemetryTags.Tracing.Otel.StatusCode, nameof(ActivityStatusCode.Ok).ToUpper(CultureInfo.InvariantCulture) ); if (!string.IsNullOrEmpty(description)) activity.SetTag(TelemetryTags.Tracing.Otel.StatusDescription, description); return activity; } /// /// Sets an "Unset" status on the provided Activity, indicating no explicit status was applied. /// /// The Activity to update. /// An optional description of the unset status. /// The updated Activity with the status and tags set. public static Activity SetUnsetStatus(this Activity activity, string? description = null) { ArgumentNullException.ThrowIfNull(activity); // Set the status of the activity to "Unset" activity.SetStatus(ActivityStatusCode.Unset, description); // Add telemetry tags for status activity.SetTag( TelemetryTags.Tracing.Otel.StatusCode, nameof(ActivityStatusCode.Unset).ToUpper(CultureInfo.InvariantCulture) ); if (!string.IsNullOrEmpty(description)) activity.SetTag(TelemetryTags.Tracing.Otel.StatusDescription, description); return activity; } /// /// Sets an "Error" status on the provided Activity, indicating a failed operation. /// /// The Activity to update. /// The exception associated with the error, if available. /// An optional description of the error. /// The updated Activity with the status, error details, and tags set. public static Activity SetErrorStatus(this Activity activity, System.Exception? exception, string? description = null) { ArgumentNullException.ThrowIfNull(activity); // Add telemetry tags for status activity.SetTag( TelemetryTags.Tracing.Otel.StatusCode, nameof(ActivityStatusCode.Error).ToUpper(CultureInfo.InvariantCulture) ); if (!string.IsNullOrEmpty(description)) activity.SetTag(TelemetryTags.Tracing.Otel.StatusDescription, description); // Add detailed exception tags, if an exception is provided return activity.SetExceptionTags(exception); } // See https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/exceptions/ public static Activity SetExceptionTags(this Activity activity, System.Exception? ex) { if (ex is null) { return activity; } activity.SetStatus(ActivityStatusCode.Error); activity.AddException(ex); activity.AddTag(TelemetryTags.Tracing.Exception.Message, ex.Message); activity.AddTag(TelemetryTags.Tracing.Exception.Stacktrace, ex.ToString()); activity.AddTag(TelemetryTags.Tracing.Exception.Type, ex.GetType().FullName); return activity; } }