// 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);
}