Custom Activities & Context Toevoegen Aan Je .NET Traces

08.06

In onze vorige blogpost benadrukten we het belang van een combinatie van auto-instrumentatie en handmatige instrumentatie. Hoewel automatische tracing snel voor brede dekking zorgt, krijg je pas écht diepgaand inzicht door je eigen custom activities (in OpenTelemetry-termen vaak spans genoemd) toe te voegen.  

Deze post legt uit hoe .NET-ontwikkelaars dit praktisch kunnen aanpakken met OpenTelemetry en het System.Diagnostics.Activity-systeem, dat naadloos integreert met Application Insights. We laten zien hoe je custom activities maakt, ze verrijkt met zinvolle tags en baggage (context die meereist), en ervoor zorgt dat ze correct zichtbaar worden in je observability-backend, zoals Azure Monitor Application Insights. 

Stap 1: Definieer je ActivitySource 

De basis voor het maken van custom activities is de System.Diagnostics.ActivitySource. Zie het als een ‘factory’ of bron voor je activiteiten. Het is cruciaal om deze een unieke naam te geven, die je later in de OpenTelemetry-configuratie zult gebruiken.  

Het is gebruikelijk om dit als een statische variabele te definiëren binnen een relevante klasse of een toegewijde telemetry helper class, zoals je kan zien in de CatalogAPI: 

private static readonly ActivitySource ActivitySource = new(nameof(CatalogApi));

Waarom is de naam belangrijk? OpenTelemetry gebruikt deze bronnaam om activiteiten te identificeren en te filteren. Je zal die bronnaam expliciet moeten toevoegen aan je configuratie (zie stap 5). 

Stap 2: Creëer custom activities rond je logica

Nu kun je je ActivitySource gebruiken om activiteiten te starten en te stoppen rond specifieke codeblokken. De StartActivity methode creëert een nieuwe activiteit (span). Het gebruik van een using statement is het aanbevolen patroon. Hier zie je hoe we dit toepassen in de GetItems methode: 

using (var getAllItemsActivity = ActivitySource.StartActivity("GetAllItems", ActivityKind.Server))
{
    getAllItemsActivity?.SetTag("status", "success");
    var pageSize = paginationRequest.PageSize;
    var pageIndex = paginationRequest.PageIndex;    var totalItems = await services.Context.CatalogItems
        .LongCountAsync();    var itemsOnPage = await services.Context.CatalogItems
        .OrderBy(c => c.Name)
        .Skip(pageSize * pageIndex)
        .Take(pageSize)
        .ToListAsync();if (totalItems <= 0 || itemsOnPage.Count <= 0)
    getAllItemsActivity?.SetTag("status", "warning");    return TypedResults.Ok(new PaginatedItems<CatalogItem>(pageIndex, pageSize, totalItems, itemsOnPage));
}

Het resultaat van het toevoegen van deze custom activity is duidelijk zichtbaar in de end-to-end transactieweergave in Azure Monitor, waar onze GetAllItems span genest is binnen de door het framework gegenereerde span: 

A screenshot of the Microsoft Azure Monitor web interface.

A screenshot of the Microsoft Azure Monitor web interface.

Stap 3: Voeg propagerende context toe met baggage

Soms heb je contextuele informatie (zoals een CustomerID of SessionID) die je niet alleen beschikbaar wilt hebben op de huidige span, maar ook op alle onderliggende spans die later in het proces worden gemaakt. Dat kan zelfs over servicegrenzen heen als de contextpropagatie correct is ingesteld. Dit is waar baggage van pas komt.

if (totalItems <= 0 || itemsOnPage.Count <= 0)
    getAllItemsActivity?.SetBaggage("status", "warning");

Hoewel baggage wordt doorgegeven, zullen OpenTelemetry-backends en UI’s baggage-items vaak niet automatisch als tags op spans tonen. Je hebt meestal een extra stap nodig om ze zichtbaar te maken. 

Stap 4: Maak baggage zichtbaar met een custom processor

Om baggage-items automatisch als tags aan spans toe te voegen, maken we een custom processor die erft van BaseProcessor<Activity>: 

public class OpenTelemetryEnricher : BaseProcessor<Activity>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public OpenTelemetryEnricher(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public override void OnStart(Activity activity)
    {
        var customerId = _httpContextAccessor.HttpContext?.User.FindFirst("customerId")?.Value;
        if (!string.IsNullOrEmpty(customerId))
            activity.SetTag("customerId", customerId);
    }

    public override void OnEnd(Activity activity)
    {
        foreach (var baggage in activity.Baggage)
        {
            activity.SetTag(baggage.Key, baggage.Value);
        }
    }
}

Stap 5: Configureer OpenTelemetry om je bron en processor te gebruiken 

Vertel je OpenTelemetry-configuratie dat het je ActivitySource (uit stap 1) moet gebruiken en je custom processor (uit stap 4). 

  • AddSource(): Vertelt OpenTelemetry expliciet om te luisteren naar activiteiten gemaakt door je benoemde ActivitySource. Zonder dit worden je custom activities genegeerd. 
  • AddProcessor(): Registreert je custom processor in de pipeline. Deze zal nu draaien voor elke activity die door deze tracer provider wordt verwerkt.
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});

builder.Services.AddOpenTelemetry()
.WithMetrics()
.WithTracing(tracing =>
{
    tracing.AddSource(nameof(CatalogApi));
    tracing.AddProcessor<OpenTelemetryEnricher>();
}

Conclusie

Het creëren van custom activities met relevante tags en het propageren van context via baggage tilt je observability aanzienlijk boven basis auto-instrumentatie uit. Het stelt je in staat om je traces direct af te stemmen op de kernlogica en bedrijfsconcepten van je applicatie.  

Bij CloudFuel hebben we diepgaande expertise in het ontwerpen en implementeren van geavanceerde observability-strategieën zoals deze. Worstel je met het opzetten van custom tracing, het optimaliseren van je OpenTelemetry-configuratie, of wil je simpelweg de volgende stap zetten om méér waarde uit je observability-data te halen? Neem dan contact op met CloudFuel. Wij helpen je graag om deze technieken effectief toe te passen in jouw specifieke omgeving. 

Smokescreen