Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Este artículo se aplica a: ✔️ .NET Core 2.1 y versiones ✔️ posteriores de .NET Framework 4.5 y versiones posteriores
Las aplicaciones .NET se pueden instrumentar mediante la System.Diagnostics.Activity API para generar telemetría de seguimiento distribuido. Algunas instrumentaciones están integradas en bibliotecas estándar de .NET, pero es posible que quiera agregar más para que el código sea más fácil de diagnosticar. En este tutorial, agregará una nueva instrumentación de seguimiento distribuido personalizada. Consulte el tutorial de recopilación para obtener más información sobre cómo registrar la telemetría generada por esta instrumentación.
Prerrequisitos
- SDK de .NET Core 2.1 o una versión posterior
Creación de una aplicación inicial
En primer lugar, creará una aplicación de ejemplo que recopila datos de telemetría mediante OpenTelemetry, pero aún no tiene ninguna instrumentación.
dotnet new console
Las aplicaciones que tienen como destino .NET 5 y versiones posteriores ya tienen incluidas las API de seguimiento distribuido necesarias. En el caso de las aplicaciones destinadas a versiones anteriores de .NET, agregue la versión 5 o posterior del paquete NuGet System.Diagnostics.DiagnosticSource . En el caso de las bibliotecas destinadas a netstandard, se recomienda hacer referencia a la versión más antigua del paquete que todavía se admite y contiene las API que necesita la biblioteca.
dotnet add package System.Diagnostics.DiagnosticSource
Agregue los paquetes NuGet OpenTelemetry y OpenTelemetry.Exporter.Console , que se usarán para recopilar la telemetría.
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
Reemplace el contenido del Program.cs generado por este origen de ejemplo:
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Threading.Tasks;
namespace Sample.DistributedTracing
{
class Program
{
static async Task Main(string[] args)
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
.AddSource("Sample.DistributedTracing")
.AddConsoleExporter()
.Build();
await DoSomeWork("banana", 8);
Console.WriteLine("Example work done");
}
// All the functions below simulate doing some arbitrary work
static async Task DoSomeWork(string foo, int bar)
{
await StepOne();
await StepTwo();
}
static async Task StepOne()
{
await Task.Delay(500);
}
static async Task StepTwo()
{
await Task.Delay(1000);
}
}
}
La aplicación aún no tiene instrumentación, por lo que no hay información de seguimiento para mostrar:
> dotnet run
Example work done
procedimientos recomendados
Solo los desarrolladores de aplicaciones necesitan hacer referencia a una biblioteca de terceros opcional para recopilar la telemetría de seguimiento distribuido, como OpenTelemetry en este ejemplo. Los autores de bibliotecas de .NET pueden confiar exclusivamente en las API de System.Diagnostics.DiagnosticSource, que forma parte del entorno de ejecución de .NET. Esto garantiza que las bibliotecas se ejecuten en una amplia gama de aplicaciones .NET, independientemente de las preferencias del desarrollador de la aplicación sobre qué biblioteca o proveedor usar para recopilar telemetría.
Adición de instrumentación básica
Las aplicaciones y las bibliotecas agregan instrumentación de seguimiento distribuido mediante las clases System.Diagnostics.ActivitySource y System.Diagnostics.Activity.
FuenteDeActividad
En primer lugar, cree una instancia de ActivitySource. ActivitySource proporciona API para crear e iniciar objetos Activity. Agregue la variable estática ActivitySource por encima de Main()
y using System.Diagnostics;
a las directivas using
.
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Sample.DistributedTracing
{
class Program
{
private static ActivitySource source = new ActivitySource("Sample.DistributedTracing", "1.0.0");
static async Task Main(string[] args)
{
// ...
procedimientos recomendados
Cree activitySource una vez, almacénelo en una variable estática y use esa instancia siempre que sea necesario. Cada biblioteca o subcomponente de biblioteca puede (y a menudo debería) crear su propio origen. Considere la posibilidad de crear un nuevo origen en lugar de reutilizar uno existente si prevé que los desarrolladores de aplicaciones apreciarían poder habilitar y deshabilitar la telemetría de actividad en los orígenes de forma independiente.
El nombre de origen pasado al constructor debe ser único para evitar los conflictos con cualquier otro origen. Si hay varios orígenes dentro del mismo ensamblado, use un nombre jerárquico que contenga el nombre del ensamblado y, opcionalmente, un nombre de componente, por ejemplo,
Microsoft.AspNetCore.Hosting
. Si un ensamblado agrega instrumentación para el código en un segundo ensamblado independiente, el nombre debe basarse en el ensamblado que define ActivitySource, no en el ensamblado cuyo código se está instrumentando.El parámetro version es opcional. Se recomienda proporcionar la versión en caso de que publique varias versiones de la biblioteca y realice cambios en la telemetría instrumentada.
Nota:
OpenTelemetry usa términos alternativos "Tracer" y "Span". En .NET 'ActivitySource' es la implementación de Tracer y Activity es la implementación de 'Span'. El tipo de actividad de .NET es muy anterior a la especificación de OpenTelemetry, y la nomenclatura original de .NET se ha conservado para mantener la coherencia dentro del ecosistema de .NET y la compatibilidad de las aplicaciones de .NET.
Actividad
Utilice el objeto ActivitySource para iniciar y detener objetos Activity en torno a unidades de trabajo significativas. Actualice DoSomeWork() con el código que se muestra aquí:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
await StepOne();
await StepTwo();
}
}
La ejecución de la aplicación muestra ahora la nueva actividad que se registra:
> dotnet run
Activity.Id: 00-f443e487a4998c41a6fd6fe88bae644e-5b7253de08ed474f-01
Activity.DisplayName: SomeWork
Activity.Kind: Internal
Activity.StartTime: 2021-03-18T10:36:51.4720202Z
Activity.Duration: 00:00:01.5025842
Resource associated with Activity:
service.name: MySample
service.instance.id: 067f4bb5-a5a8-4898-a288-dec569d6dbef
Notas
ActivitySource.StartActivity crea e inicia la actividad al mismo tiempo. El patrón de código enumerado usa el
using
bloque , que elimina automáticamente el objeto Activity creado después de ejecutar el bloque. La eliminación del objeto Activity lo detendrá, por lo que el código no necesitará llamar explícitamente a Activity.Stop(). Esto simplifica el patrón de codificación.ActivitySource.StartActivity determina internamente si hay oyentes que registran la actividad. Si no hay agentes de escucha registrados o hay agentes de escucha que no están interesados,
StartActivity()
devolveránull
y evitará crear el objeto Activity. Se trata de una optimización del rendimiento para que el patrón de código todavía se pueda usar en funciones a las que se llama con frecuencia.
Opcional: Rellenar etiquetas
Las actividades admiten datos de clave-valor denominados etiquetas, que se suelen usar para almacenar los parámetros del trabajo que pueden ser útiles para el diagnóstico. Actualice DoSomeWork()
para incluirlos:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
activity?.SetTag("foo", foo);
activity?.SetTag("bar", bar);
await StepOne();
await StepTwo();
}
}
> dotnet run
Activity.Id: 00-2b56072db8cb5a4496a4bfb69f46aa06-7bc4acda3b9cce4d-01
Activity.DisplayName: SomeWork
Activity.Kind: Internal
Activity.StartTime: 2021-03-18T10:37:31.4949570Z
Activity.Duration: 00:00:01.5417719
Activity.TagObjects:
foo: banana
bar: 8
Resource associated with Activity:
service.name: MySample
service.instance.id: 25bbc1c3-2de5-48d9-9333-062377fea49c
Example work done
procedimientos recomendados
- Como se mencionó anteriormente,
activity
el valor devuelto por ActivitySource.StartActivity puede ser NULL. El operador de fusión nula?.
en C# es una práctica abreviatura para invocar Activity.SetTag solo siactivity
no es nulo. El comportamiento es idéntico a la escritura:
if(activity != null)
{
activity.SetTag("foo", foo);
}
OpenTelemetry proporciona un conjunto de convenciones recomendadas para establecer etiquetas en actividades que representan tipos comunes de trabajo de aplicación.
Si va a instrumentar funciones con requisitos de alto rendimiento, Activity.IsAllDataRequested es una sugerencia que indica si alguno de los códigos que escuchan las actividades pretende leer información auxiliar, como etiquetas. Si nadie va a leerlo, entonces no es necesario que el código instrumentado consuma ciclos de CPU para rellenarlo. Por motivos de simplicidad, este ejemplo no aplica esa optimización.
Opcional: Agregar eventos
Los eventos son mensajes con marcas de tiempo que pueden adjuntar un flujo arbitrario de datos de diagnóstico adicionales a actividades. Agregue algunos eventos a la actividad:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
activity?.SetTag("foo", foo);
activity?.SetTag("bar", bar);
await StepOne();
activity?.AddEvent(new ActivityEvent("Part way there"));
await StepTwo();
activity?.AddEvent(new ActivityEvent("Done now"));
}
}
> dotnet run
Activity.Id: 00-82cf6ea92661b84d9fd881731741d04e-33fff2835a03c041-01
Activity.DisplayName: SomeWork
Activity.Kind: Internal
Activity.StartTime: 2021-03-18T10:39:10.6902609Z
Activity.Duration: 00:00:01.5147582
Activity.TagObjects:
foo: banana
bar: 8
Activity.Events:
Part way there [3/18/2021 10:39:11 AM +00:00]
Done now [3/18/2021 10:39:12 AM +00:00]
Resource associated with Activity:
service.name: MySample
service.instance.id: ea7f0fcb-3673-48e0-b6ce-e4af5a86ce4f
Example work done
procedimientos recomendados
- Los eventos se almacenan en una lista en memoria hasta que se puedan transmitir, lo que hace que este mecanismo solo sea adecuado para registrar un número modesto de eventos. Para un gran volumen de eventos o un volumen ilimitado, es mejor usar una API de registro de eventos centrada en esta tarea, como ILogger. ILogger también garantiza que la información de registro estará disponible independientemente de si el desarrollador de la aplicación opta por usar el seguimiento distribuido. ILogger admite la captura automática de los identificadores de actividad activos, por lo que los mensajes registrados a través de esa API todavía se pueden correlacionar con el seguimiento distribuido.
Opcional: Agregar estado
OpenTelemetry permite que cada actividad notifique un estado que represente el resultado de paso o error del trabajo. .NET tiene una API fuertemente tipada para este propósito:
Los ActivityStatusCode valores se representan como , Unset
, Ok
y Error
.
Actualice DoSomeWork() para establecer el estado:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
activity?.SetTag("foo", foo);
activity?.SetTag("bar", bar);
await StepOne();
activity?.AddEvent(new ActivityEvent("Part way there"));
await StepTwo();
activity?.AddEvent(new ActivityEvent("Done now"));
// Pretend something went wrong
activity?.SetStatus(ActivityStatusCode.Error, "Use this text give more information about the error");
}
}
Opcional: Agregar actividades adicionales
Las actividades se pueden anidar para describir partes de una unidad de trabajo mayor. Esto puede ser útil en torno a partes de código que podrían no ejecutarse rápidamente o para localizar mejor los errores que proceden de dependencias externas específicas. Aunque en este ejemplo se usa una actividad en todos los métodos, esto se debe únicamente a que se ha minimizado el código adicional. En un proyecto más grande y realista, el uso de una actividad en cada método generaría seguimientos muy detallados, por lo que no se recomienda.
Actualice StepOne y StepTwo para agregar más seguimiento en torno a estos pasos independientes:
static async Task StepOne()
{
using (Activity activity = source.StartActivity("StepOne"))
{
await Task.Delay(500);
}
}
static async Task StepTwo()
{
using (Activity activity = source.StartActivity("StepTwo"))
{
await Task.Delay(1000);
}
}
> dotnet run
Activity.Id: 00-9d5aa439e0df7e49b4abff8d2d5329a9-39cac574e8fda44b-01
Activity.ParentId: 00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepOne
Activity.Kind: Internal
Activity.StartTime: 2021-03-18T10:40:51.4278822Z
Activity.Duration: 00:00:00.5051364
Resource associated with Activity:
service.name: MySample
service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0
Activity.Id: 00-9d5aa439e0df7e49b4abff8d2d5329a9-4ccccb6efdc59546-01
Activity.ParentId: 00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepTwo
Activity.Kind: Internal
Activity.StartTime: 2021-03-18T10:40:51.9441095Z
Activity.Duration: 00:00:01.0052729
Resource associated with Activity:
service.name: MySample
service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0
Activity.Id: 00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: SomeWork
Activity.Kind: Internal
Activity.StartTime: 2021-03-18T10:40:51.4256627Z
Activity.Duration: 00:00:01.5286408
Activity.TagObjects:
foo: banana
bar: 8
otel.status_code: ERROR
otel.status_description: Use this text give more information about the error
Activity.Events:
Part way there [3/18/2021 10:40:51 AM +00:00]
Done now [3/18/2021 10:40:52 AM +00:00]
Resource associated with Activity:
service.name: MySample
service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0
Example work done
Observe que StepOne y StepTwo incluyen un ParentId que hace referencia a SomeWork. La consola no es una gran visualización de árboles anidados de trabajo, pero muchos visores de GUI como Zipkin pueden mostrar esto como un diagrama de Gantt:
Opcional: ActivityKind
Las actividades tienen una propiedad Activity.Kind, que describe la relación entre la actividad, su elemento primario y sus elementos secundarios. De forma predeterminada, todas las actividades nuevas se establecen en Internal, lo que es adecuado para las que son una operación interna dentro de una aplicación sin elementos primarios o secundarios remotos. Otros tipos se pueden establecer mediante el parámetro kind en ActivitySource.StartActivity. Para ver otras opciones, consulte System.Diagnostics.ActivityKind.
Opcional: Vínculos
Cuando se produce el trabajo en sistemas de procesamiento por lotes, una sola actividad puede representar el trabajo en nombre de muchas solicitudes diferentes simultáneamente, cada una de las cuales tiene su propio identificador de seguimiento. Aunque la actividad está restringida para tener un único elemento primario, puede vincularse a identificadores de seguimiento adicionales mediante System.Diagnostics.ActivityLink. Cada elemento ActivityLink se rellena con un objeto ActivityContext en el que se almacena la información de identificador sobre la actividad a la que se vincula. ActivityContext se puede recuperar de los objetos Activity en proceso mediante Activity.Context, o bien se puede analizar a partir de la información de identificador serializada mediante ActivityContext.Parse(String, String).
void DoBatchWork(ActivityContext[] requestContexts)
{
// Assume each context in requestContexts encodes the trace-id that was sent with a request
using(Activity activity = s_source.StartActivity(name: "BigBatchOfWork",
kind: ActivityKind.Internal,
parentContext: default,
links: requestContexts.Select(ctx => new ActivityLink(ctx))
{
// do the batch of work here
}
}
A diferencia de los eventos y las etiquetas que se pueden agregar a petición, los vínculos se deben agregar durante StartActivity()
y, después, son inmutables.
Importante
Según la especificación OpenTelemetry, el límite sugerido para el número de vínculos es 128. Sin embargo, es importante tener en cuenta que este límite no se aplica.