// Copyright (c) Microsoft. All rights reserved. using static Microsoft.Extensions.AI.Agents.Runtime.ActorRuntimeOpenTelemetryConsts; namespace Microsoft.Extensions.AI.Agents.Runtime; /// /// Helper methods for setting common telemetry attributes on activities. /// internal static class ActivityExtensions { public const string ActorCreated = ActorRuntimeOpenTelemetryConsts.EventInfo.Names.ActorCreated; public const string ActorStarted = ActorRuntimeOpenTelemetryConsts.EventInfo.Names.ActorStarted; public const string MessageSent = ActorRuntimeOpenTelemetryConsts.EventInfo.Names.MessageSent; public const string MessageReceived = ActorRuntimeOpenTelemetryConsts.EventInfo.Names.MessageReceived; public const string RequestCompleted = ActorRuntimeOpenTelemetryConsts.EventInfo.Names.RequestCompleted; // Re-export common status values for convenience public const string Started = "started"; public const string Sent = "sent"; public const string Enqueued = "enqueued"; public const string Created = "created"; public const string Found = "found"; public const string HandleCreated = "handle_created"; /// /// Sets common actor attributes on an activity. /// /// The activity to set attributes on. /// The actor ID. /// Optional operation name. public static void SetActorAttributes(this System.Diagnostics.Activity? activity, ActorId actorId, string? operation = null) { if (activity == null) { return; } activity.SetTag(ActorRuntimeOpenTelemetryConsts.Actor.Id, actorId.ToString()); activity.SetTag(ActorRuntimeOpenTelemetryConsts.Actor.Type, actorId.Type.Name); activity.SetTag(ActorRuntimeOpenTelemetryConsts.Actor.RpcSystem, ActorRuntimeOpenTelemetryConsts.Actor.SystemName); if (!string.IsNullOrEmpty(operation)) { activity.SetTag(ActorRuntimeOpenTelemetryConsts.Actor.Operation, operation); } } /// /// Sets common message attributes on an activity. /// /// The activity to set attributes on. /// The message ID. /// Optional message type. /// Optional message method. public static void SetMessageAttributes(this System.Diagnostics.Activity? activity, string messageId, string? messageType = null, string? method = null) { if (activity == null) { return; } activity.SetTag(Message.Id, messageId); if (!string.IsNullOrEmpty(messageType)) { activity.SetTag(Message.Type, messageType); } if (!string.IsNullOrEmpty(method)) { activity.SetTag(Message.Method, method); } } /// /// Sets common request attributes on an activity. /// /// The activity to set attributes on. /// The request ID. /// Optional request method. /// Optional timeout value. public static void SetRequestAttributes(this System.Diagnostics.Activity? activity, string requestId, string? method = null, System.TimeSpan? timeout = null) { if (activity == null) { return; } activity.SetTag(Request.Id, requestId); if (!string.IsNullOrEmpty(method)) { activity.SetTag(Request.Method, method); } if (timeout.HasValue) { activity.SetTag(Request.Timeout, timeout.Value.TotalMilliseconds); } } /// /// Sets common state operation attributes on an activity. /// /// The activity to set attributes on. /// The type of state operation. /// Optional count of operations. /// Optional ETag value. public static void SetStateAttributes(this System.Diagnostics.Activity? activity, string operationType, int? operationCount = null, string? etag = null) { if (activity == null) { return; } activity.SetTag(State.OperationType, operationType); if (operationCount.HasValue) { activity.SetTag(State.OperationCount, operationCount.Value); } if (!string.IsNullOrEmpty(etag)) { activity.SetTag(State.ETag, etag); } } /// /// Sets success/failure status on an activity. /// /// The activity to set status on. /// Whether the operation was successful. /// Optional error message for failures. public static void SetOperationStatus(this System.Diagnostics.Activity? activity, bool success, string? errorMessage = null) { if (activity == null) { return; } if (success) { activity.SetStatus(System.Diagnostics.ActivityStatusCode.Ok); } else { activity.SetStatus(System.Diagnostics.ActivityStatusCode.Error, errorMessage); } } /// /// Sets error attributes on an activity. /// /// The activity to set error attributes on. /// The exception that occurred. /// Optional custom error type. public static void SetErrorAttributes(this System.Diagnostics.Activity? activity, System.Exception exception, string? errorType = null) { if (activity == null) { return; } activity.SetTag(ErrorInfo.Type, errorType ?? exception.GetType().Name); activity.SetTag(ErrorInfo.Message, exception.Message); activity.SetStatus(System.Diagnostics.ActivityStatusCode.Error, exception.Message); // Add exception event activity.AddEvent(new System.Diagnostics.ActivityEvent("exception", System.DateTimeOffset.UtcNow, new System.Diagnostics.ActivityTagsCollection { [ErrorInfo.Type] = errorType ?? exception.GetType().Name, [ErrorInfo.Message] = exception.Message, [ErrorInfo.StackTrace] = exception.StackTrace })); } /// /// Sets RPC-style attributes for actor operations. /// /// The activity to set attributes on. /// The RPC service name. /// The RPC method name. public static void SetRpcAttributes(this System.Diagnostics.Activity? activity, string service, string method) { if (activity == null) { return; } activity.SetTag(Actor.RpcSystem, Actor.SystemName); activity.SetTag(Actor.RpcService, service); activity.SetTag(Actor.RpcMethod, method); } /// /// Sets up complete telemetry for actor retrieval/creation operations. /// /// The activity to set attributes on. /// The actor ID. /// Whether the actor already exists. /// Whether the actor was started. public static void SetupActorOperation(this System.Diagnostics.Activity? activity, ActorId actorId, bool? exists = null, bool? started = null) { if (activity == null) { return; } SetActorAttributes(activity, actorId); SetRpcAttributes(activity, "ActorRuntime", "GetOrCreateActor"); if (exists.HasValue) { activity.SetTag(Actor.Exists, exists.Value); } if (started.HasValue) { activity.SetTag(Actor.Started, started.Value); } } /// /// Sets up complete telemetry for message operations. /// /// The activity to set attributes on. /// The actor ID. /// The message ID. /// Optional message type. /// Optional message method. /// Optional message status. public static void SetupMessageOperation(this System.Diagnostics.Activity? activity, ActorId actorId, string messageId, string? messageType = null, string? method = null, string? status = null) { if (activity == null) { return; } SetActorAttributes(activity, actorId); SetMessageAttributes(activity, messageId, messageType, method); if (!string.IsNullOrEmpty(status)) { activity.SetTag(Message.Status, status); } } /// /// Sets up complete telemetry for request operations. /// /// The activity to set attributes on. /// The actor ID. /// The request ID. /// Optional request method. /// The RPC service name. /// The RPC method name. /// Optional timeout value. public static void SetupRequestOperation(this System.Diagnostics.Activity? activity, ActorId actorId, string requestId, string? method = null, string service = "ActorClient", string rpcMethod = "SendRequest", System.TimeSpan? timeout = null) { if (activity == null) { return; } SetActorAttributes(activity, actorId); SetRequestAttributes(activity, requestId, method, timeout); SetRpcAttributes(activity, service, rpcMethod); } /// /// Sets up complete telemetry for state operations. /// /// The activity to set attributes on. /// The actor ID. /// The type of state operation. /// Optional count of operations. /// Optional ETag value. public static void SetupStateOperation(this System.Diagnostics.Activity? activity, ActorId actorId, string operationType, int? operationCount = null, string? etag = null) { if (activity == null) { return; } SetActorAttributes(activity, actorId); SetStateAttributes(activity, operationType, operationCount, etag); } /// /// Records successful completion of an operation with optional additional attributes. /// /// The activity to update. /// Optional additional tags to set. public static void RecordSuccess(this System.Diagnostics.Activity? activity, params (string key, object? value)[] additionalTags) { if (activity == null) { return; } SetOperationStatus(activity, true); foreach (var (key, value) in additionalTags) { activity.SetTag(key, value); } } /// /// Records failure of an operation with error details. /// /// The activity to update. /// The exception that occurred. /// Optional custom error type. /// Optional additional tags to set. public static void RecordFailure(this System.Diagnostics.Activity? activity, System.Exception exception, string? errorType = null, params (string key, object? value)[] additionalTags) { if (activity == null) { return; } SetErrorAttributes(activity, exception, errorType); foreach (var (key, value) in additionalTags) { activity.SetTag(key, value); } } /// /// Adds an event with common actor context. /// /// The activity to add the event to. /// The name of the event. /// The actor ID. /// Optional additional event data. public static void AddActorEvent(this System.Diagnostics.Activity? activity, string eventName, ActorId actorId, params (string key, object? value)[] additionalData) { if (activity == null) { return; } var tags = new System.Diagnostics.ActivityTagsCollection { [Actor.Id] = actorId.ToString(), [Actor.Type] = actorId.Type.Name }; foreach (var (key, value) in additionalData) { tags[key] = value; } activity.AddEvent(new System.Diagnostics.ActivityEvent(eventName, System.DateTimeOffset.UtcNow, tags)); } /// /// Records successful completion and adds an event in a single terse call. /// /// The activity to update. /// The name of the event to add. /// The actor ID for the event. /// Status tags to set on the activity. /// Additional event data. public static void CompleteWithEvent(this System.Diagnostics.Activity? activity, string eventName, ActorId actorId, (string key, object? value)[] statusTags, params (string key, object? value)[] eventData) { if (activity == null) { return; } RecordSuccess(activity, statusTags); AddActorEvent(activity, eventName, actorId, eventData); } /// /// Complete with event - ultra-terse single-line calls. /// public static void Complete(this System.Diagnostics.Activity? activity, string @event, ActorId actor, string status, params (string, object?)[] data) => CompleteWithEvent(activity, @event, actor, [(Request.Status, status)], data); /// /// Complete with multiple status tags and event. /// public static void Complete(this System.Diagnostics.Activity? activity, string @event, ActorId actor, (string, object?)[] status, params (string, object?)[] data) => CompleteWithEvent(activity, @event, actor, status, data); /// /// Record success with single status. /// public static void Success(this System.Diagnostics.Activity? activity, string status) => RecordSuccess(activity, (Request.Status, status)); /// /// Add actor event. /// public static void Event(this System.Diagnostics.Activity? activity, string @event, ActorId actor, params (string, object?)[] data) => AddActorEvent(activity, @event, actor, data); /// /// Record failure. /// public static void Fail(this System.Diagnostics.Activity? activity, System.Exception exception, string? status = null) => RecordFailure(activity, exception, null, status != null ? (Request.Status, status) : default); }