Seguimiento de las operaciones personalizadas con el SDK de .NET para Application Insights

Los SDK de Application Insights realizan el seguimiento automático de las solicitudes HTTP de entrada y de las llamadas a servicios dependientes, como solicitudes HTTP y consultas SQL. El seguimiento y la correlación de las solicitudes y dependencias le ofrece visibilidad en la capacidad de respuesta de toda la aplicación y la confiabilidad en todos los microservicios que combinan esta aplicación.

Hay una clase de patrones de aplicación que no se admiten de forma genérica. La supervisión correcta de estos patrones requiere la instrumentación manual de código. En este artículo se abordan algunos patrones que pueden requerir la instrumentación manual, como el procesamiento de colas personalizadas y la ejecución de tareas de larga duración en segundo plano.

En este documento se proporcionan una guía para realizar el seguimiento de operaciones personalizadas con el SDK de Application Insights. Esta documentación es relevante para:

  • Application Insights para .NET (también conocido como Base SDK) versión 2.4+.
  • Application Insights para aplicaciones web (con ASP.NET) versión 2.4+.
  • Application Insights para ASP.NET Core versión 2.1+.

Nota:

La siguiente documentación se basa en la API clásica de Application Insights. El plan a largo plazo de Application Insights consiste en recopilar datos mediante OpenTelemetry. Para más información, consulte Habilitación de Azure Monitor OpenTelemetry para aplicaciones de .NET, Node.js, Python y Java.

Información general

Una operación es una parte de trabajo lógica ejecutada por una aplicación. Tiene un nombre, una hora de inicio, una duración, un resultado y un contexto de ejecución como el nombre de usuario, las propiedades y el resultado. Si la operación B inició la operación A, la operación B se establece como un elemento primario de A. Una operación solo puede tener un elemento primario, pero puede tener muchas operaciones secundarias. Para más información sobre las operaciones y la correlación de telemetría, consulte Correlación de telemetría de Azure Application Insights.

En el SDK de .NET para Application Insights se describe la operación mediante la clase abstracta OperationTelemetry y sus descendientes RequestTelemetry y DependencyTelemetry.

Seguimiento de operaciones de entrada

El SDK web de Application Insights recopila automáticamente las solicitudes HTTP para las aplicaciones ASP.NET que se ejecutan en una canalización IIS y todas las aplicaciones ASP.NET Core. Hay soluciones admitidas por la comunidad para otras plataformas y marcos de trabajo. Si la aplicación no es compatible con ninguna de las soluciones estándar o comunitarias, puede instrumentarla manualmente.

Otro ejemplo que requiere seguimiento personalizado es el proceso de trabajo que recibe los elementos de la cola. Para algunas colas, se realiza un seguimiento como dependencia de la llamada para agregar un mensaje a esta cola. La operación general que describe el procesamiento de mensajes no se recopila automáticamente.

Ahora vamos a ver cómo se puede realizar un seguimiento de tales operaciones.

Generalmente, la tarea consiste en crear RequestTelemetry y establecer propiedades conocidas. Una vez finalizada la operación, se realiza el seguimiento de la telemetría. En el siguiente ejemplo se muestra esta tarea.

Solicitud HTTP en una aplicación autohospedada de Owin

En este ejemplo, el contexto de seguimiento se propaga según el protocolo HTTP para la correlación. Debe esperar recibir los encabezados que se describen ahí.

public class ApplicationInsightsMiddleware : OwinMiddleware
{
    // You may create a new TelemetryConfiguration instance, reuse one you already have,
    // or fetch the instance created by Application Insights SDK.
    private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
    private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
    
    public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}

    public override async Task Invoke(IOwinContext context)
    {
        // Let's create and start RequestTelemetry.
        var requestTelemetry = new RequestTelemetry
        {
            Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
        };

        // If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
        if (context.Request.Headers.ContainsKey("Request-Id"))
        {
            var requestId = context.Request.Headers.Get("Request-Id");
            // Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
            requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
            requestTelemetry.Context.Operation.ParentId = requestId;
        }

        // StartOperation is a helper method that allows correlation of 
        // current operations with nested operations/telemetry
        // and initializes start time and duration on telemetry items.
        var operation = telemetryClient.StartOperation(requestTelemetry);

        // Process the request.
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception e)
        {
            requestTelemetry.Success = false;
            requestTelemetry.ResponseCode;
            telemetryClient.TrackException(e);
            throw;
        }
        finally
        {
            // Update status code and success as appropriate.
            if (context.Response != null)
            {
                requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
                requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
            }
            else
            {
                requestTelemetry.Success = false;
            }

            // Now it's time to stop the operation (and track telemetry).
            telemetryClient.StopOperation(operation);
        }
    }
    
    public static string GetOperationId(string id)
    {
        // Returns the root ID from the '|' to the first '.' if any.
        int rootEnd = id.IndexOf('.');
        if (rootEnd < 0)
            rootEnd = id.Length;

        int rootStart = id[0] == '|' ? 1 : 0;
        return id.Substring(rootStart, rootEnd - rootStart);
    }
}

El Protocolo HTTP para la correlación también declara el encabezado Correlation-Context. Pero aquí se omite por cuestiones de simplicidad.

Instrumentación de colas

El contexto de seguimiento de W3C y el protocolo HTTP para la correlación pasan los detalles de la correlación con las solicitudes HTTP, pero cada protocolo de cola tiene que definir cómo se pasan los mismos detalles junto con el mensaje de cola. Algunos protocolos de cola, como AMQP, permiten pasar más metadatos. Otros protocolos, como Azure Storage Queue, requieren que el contexto se codifique en la carga del mensaje.

Nota

El seguimiento entre componentes aún no se admite con las colas.

Con HTTP, si el productor y el consumidor envían telemetría a distintos recursos de Application Insights, la experiencia de diagnósticos de transacción y el mapa de aplicación muestran transacciones y asignaciones de un extremo a otro. En el caso de las colas, esta funcionalidad aún no se admite.

Cola de Service Bus

Para información sobre el seguimiento, consulte Seguimiento distribuido y correlación mediante la mensajería de Azure Service Bus.

Cola de Azure Storage

En el ejemplo siguiente se muestra cómo realizar el seguimiento de operaciones de cola de Azure Storage y poner en correlación la telemetría entre el productor, el consumidor y Azure Storage.

La cola de Storage tiene una API HTTP. El recolector de dependencias de Application Insights realiza el seguimiento de todas las llamadas a la cola para las solicitudes HTTP. De forma predeterminada está configurado en aplicaciones ASP.NET y ASP.NET Core. Con otros tipos de aplicaciones, consulte la documentación de aplicaciones de consola.

Es posible que también quiera poner en correlación el identificador de operación de Application Insights con el identificador de solicitud de Storage. Para obtener información sobre cómo establecer y obtener un cliente de solicitud de Storage y un identificador de solicitud de servidor, vea Supervisión, diagnóstico y solución de problemas de Azure Storage.

Poner en cola

Como las colas de Storage admiten la API de HTTP, Application Insights realiza el seguimiento automático de todas las operaciones en la cola. En muchos casos, esta instrumentación debería ser suficiente. Pero correlacionar los seguimientos en el lado del consumidor con los seguimientos del productor, tiene que pasar algún contexto de correlación de forma similar a como se hace en el protocolo HTTP para la correlación.

En este ejemplo se muestra cómo realizar un seguimiento de la operación Enqueue. Puede:

  • Poner en correlación los reintentos (si existen) : todos tienen un elemento primario común que es la operación Enqueue. En caso contrario, se realiza su seguimiento como elementos secundarios de la solicitud de entrada. Si hay varias solicitudes lógicas a la cola, podría ser difícil buscar qué llamada generó los reintentos.
  • Poner en correlación los registros de almacenamiento (si es necesario y cuando sea necesario) : se correlacionan con la telemetría de Application Insights.

La operación Enqueue es el elemento secundario de una operación principal. Un ejemplo es una solicitud HTTP entrante. La llamada de dependencia HTTP es el elemento secundario de la operación Enqueue y el descendiente de la solicitud de entrada.

public async Task Enqueue(CloudQueue queue, string message)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("enqueue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Enqueue " + queue.Name;

    // MessagePayload represents your custom message and also serializes correlation identifiers into payload.
    // For example, if you choose to pass payload serialized to JSON, it might look like
    // {'RootId' : 'some-id', 'ParentId' : '|some-id.1.2.3.', 'message' : 'your message to process'}
    var jsonPayload = JsonConvert.SerializeObject(new MessagePayload
    {
        RootId = operation.Telemetry.Context.Operation.Id,
        ParentId = operation.Telemetry.Id,
        Payload = message
    });
    
    CloudQueueMessage queueMessage = new CloudQueueMessage(jsonPayload);

    // Add operation.Telemetry.Id to the OperationContext to correlate Storage logs and Application Insights telemetry.
    OperationContext context = new OperationContext { ClientRequestID = operation.Telemetry.Id};

    try
    {
        await queue.AddMessageAsync(queueMessage, null, null, new QueueRequestOptions(), context);
    }
    catch (StorageException e)
    {
        operation.Telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.Telemetry.Success = false;
        operation.Telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}  

Para reducir la cantidad de telemetría que notifica la aplicación o si no quiere realizar el seguimiento de la operación Enqueue por otras razones, use directamente la API Activity:

  • Cree (e inicie) una nueva Activity en lugar de iniciar la operación de Application Insights. No es necesario asignar ninguna propiedad en este elemento, excepto el nombre de la operación.
  • Serialice yourActivity.Id en la carga del mensaje en lugar de operation.Telemetry.Id. También se puede usar Activity.Current.Id.

Quitar de la cola

Al igual que Enqueue, Application Insights realiza el seguimiento automático de la solicitud HTTP a la cola de Storage. La operación Enqueue se produce supuestamente en el contexto principal, por ejemplo, en un contexto de solicitud de entrada. Los SDK de Application Insights correlacionan automáticamente una operación de este tipo, y su parte HTTP, con la solicitud principal y otra telemetría comunicada en el mismo ámbito.

La operación Dequeue es complicada. El SDK de Application Insights realiza automáticamente el seguimiento de las solicitudes HTTP. Sin embargo, se desconoce el contexto de correlación hasta que se analiza el mensaje. No es posible correlacionar la solicitud HTTP para obtener el mensaje con el resto de la telemetría, en especial cuando se recibe más de un mensaje.

public async Task<MessagePayload> Dequeue(CloudQueue queue)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("dequeue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Dequeue " + queue.Name;
    
    try
    {
        var message = await queue.GetMessageAsync();
    }
    catch (StorageException e)
    {
        operation.telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.telemetry.Success = false;
        operation.telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }

    return null;
}

Proceso

En el ejemplo siguiente, se realiza el seguimiento de un mensaje de entrada de forma similar a cómo se hace en una solicitud HTTP de entrada:

public async Task Process(MessagePayload message)
{
    // After the message is dequeued from the queue, create RequestTelemetry to track its processing.
    RequestTelemetry requestTelemetry = new RequestTelemetry { Name = "process " + queueName };
    
    // It might also make sense to get the name from the message.
    requestTelemetry.Context.Operation.Id = message.RootId;
    requestTelemetry.Context.Operation.ParentId = message.ParentId;

    var operation = telemetryClient.StartOperation(requestTelemetry);

    try
    {
        await ProcessMessage();
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        throw;
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}

Del mismo modo, se pueden instrumentar otras operaciones de cola. Una operación de lectura se debe instrumentalizar de una manera similar a la de una operación de quitar de la cola. La instrumentación de las operaciones de administración de cola no es necesaria. Application Insights realiza el seguimiento de las operaciones como HTTP y en la mayoría de los casos es suficiente.

Al instrumentar la eliminación de mensajes, asegúrese de establecer los identificadores de la operación (correlación). Como alternativa, puede usar la API de Activity. No es necesario establecer identificadores de operaciones en los elementos de telemetría, porque el SDK de Application Insights lo hace automáticamente:

  • Cree una nueva Activity después de que tenga un elemento de la cola.
  • Use Activity.SetParentId(message.ParentId) para poner en correlación los registros de consumidor y productor.
  • Inicie la Activity.
  • Realice el seguimiento de las operaciones de quitar de la cola, proceso y eliminación mediante los asistentes Start/StopOperation. Hágalo desde el mismo flujo de control asincrónico (contexto de ejecución). De esta forma, se correlacionan correctamente.
  • Pare la Activity.
  • Use Start/StopOperation o llame a la telemetría de Track manualmente.

Tipos de dependencia

Application Insights usa el tipo de dependencia para personalizar las experiencias de la interfaz de usuario. En el caso de las colas, se reconocen los siguientes tipos de DependencyTelemetry que mejoran la experiencia de diagnósticos de transacción:

  • Azure queue para colas de Azure Storage
  • Azure Event Hubs para Azure Event Hubs
  • Azure Service Bus para Azure Service Bus

Procesamiento por lotes

En algunas colas, se pueden quitar de la cola varios mensajes con una solicitud. El procesamiento de este tipo de mensajes es supuestamente independiente y pertenece a las distintas operaciones lógicas. No es posible poner en correlación la operación Dequeue con el mensaje determinado que va a procesarse.

Cada mensaje debe procesarse en su propio flujo de control asincrónico. Para más información, vea la sección Seguimiento de dependencias de salida.

Tareas en segundo plano de ejecución prolongada

Algunas aplicaciones inician operaciones de larga ejecución que es posible que se deban a solicitudes del usuario. Desde la perspectiva del seguimiento o la instrumentación, no es diferente de la instrumentación de solicitudes o dependencias:

async Task BackgroundTask()
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
    operation.Telemetry.Type = "Background";
    try
    {
        int progress = 0;
        while (progress < 100)
        {
            // Process the task.
            telemetryClient.TrackTrace($"done {progress++}%");
        }
        // Update status code and success as appropriate.
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        // Update status code and success as appropriate.
        throw;
    }
    finally
    {
        telemetryClient.StopOperation(operation);
    }
}

En este ejemplo, telemetryClient.StartOperation crea DependencyTelemetry y rellena el contexto de correlación. Supongamos que tiene una operación principal creada por las solicitudes de entrada que programaron la operación. Siempre que BackgroundTask se inicie en el mismo flujo de control asincrónico que una solicitud de entrada, se correlaciona con esa operación principal. BackgroundTask y todos los elementos de telemetría anidados se correlacionan automáticamente con la solicitud que la causó incluso después de que finalice la solicitud.

Cuando la tarea se inicia desde el subproceso en segundo plano que no tiene ninguna operación (Activity) asociada a él, BackgroundTask no tiene ningún elemento primario. Pero puede tener operaciones anidadas. Todos los elementos de telemetría notificados desde la tarea se ponen en correlación con la DependencyTelemetry creada en BackgroundTask.

Seguimiento de dependencias de salida

Puede realizar el seguimiento de su propio tipo de dependencia o de una operación que no es compatible con Application Insights.

El método Enqueue de la cola de Service Bus o la cola de Storage puede servir como ejemplo de este seguimiento personalizado.

El enfoque general para el seguimiento de dependencias personalizadas es este:

  • Llame al método TelemetryClient.StartOperation (extensión) que rellena las propiedades de DependencyTelemetry que se necesitan para la correlación y algunas otras, como inicio, marca de tiempo y duración.
  • Establezca otras propiedades personalizadas en DependencyTelemetry, como el nombre y cualquier otro contexto que se necesite.
  • Realice una llamada de dependencia y espere.
  • Detenga la operación con StopOperation cuando finalice.
  • Controle las excepciones.
public async Task RunMyTaskAsync()
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("task 1"))
    {
        try 
        {
            var myTask = await StartMyTaskAsync();
            // Update status code and success as appropriate.
        }
        catch(...) 
        {
            // Update status code and success as appropriate.
        }
    }
}

Desechar una operación hace que la operación se detenga, por lo que puede hacerlo en lugar de llamar a StopOperation.

Advertencia

En algunos casos, una excepción no controlada podría impedir que se llame a finally, por lo que es posible que no se realice un seguimiento de las operaciones.

Seguimiento y procesamiento de operaciones paralelas

La llamada a StopOperation solo detiene la operación que se inició. Si la operación de ejecución actual no coincide con la que quiere detener, StopOperation no hace nada. Esta situación puede suceder si se inician varias operaciones en paralelo en el mismo contexto de ejecución.

var firstOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 1");
var firstTask = RunMyTaskAsync();

var secondOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 2");
var secondTask = RunMyTaskAsync();

await firstTask;

// FAILURE!!! This will do nothing and will not report telemetry for the first operation
// as currently secondOperation is active.
telemetryClient.StopOperation(firstOperation); 

await secondTask;

Asegúrese de llamar siempre a StartOperation y de procesar la operación en el mismo método async para aislar las operaciones que se ejecutan en paralelo. Si la operación es sincrónica o no asincrónica, encapsule el proceso y realice un seguimiento con Task.Run.

public void RunMyTask(string name)
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>(name))
    {
        Process();
        // Update status code and success as appropriate.
    }
}

public async Task RunAllTasks()
{
    var task1 = Task.Run(() => RunMyTask("task 1"));
    var task2 = Task.Run(() => RunMyTask("task 2"));
    
    await Task.WhenAll(task1, task2);
}

Operaciones de ApplicationInsights frente a System.Diagnostics.Activity

System.Diagnostics.Activity representa el contexto de seguimiento distribuido y lo utilizan las plataformas y las bibliotecas para crear y propagar el contexto dentro y fuera del proceso, así como correlacionar los elementos de telemetría. Activity funciona junto con System.Diagnostics.DiagnosticSource como mecanismo de notificación entre la plataforma o la biblioteca para notificar eventos interesantes, como solicitudes entrantes o salientes, excepciones, etc.

Las actividades son ciudadanos de primera clase en Application Insights. La recopilación automática de dependencias y solicitudes depende en gran medida de ellas junto con los eventos DiagnosticSource. Si creara Activity en su aplicación, no se crearía la telemetría de Application Insights. Application Insights debe recibir eventos DiagnosticSource y conocer los nombres y las cargas de los eventos para convertir Activity en telemetría.

Cada operación de Application Insights (solicitud o dependencia) conlleva un elemento Activity. Cuando se llama a StartOperation, se crea Activity debajo. StartOperation es el método recomendado para realizar el seguimiento de las telemetrías de solicitudes o de dependencias manualmente y asegurarse de que todo está correlacionado.

Pasos siguientes