Dela via


Källgenererade mått med starkt skrivna taggar

Moderna .NET-program kan samla in mått med hjälp av api:et System.Diagnostics.Metrics. Dessa mått innehåller ofta ytterligare kontext i form av nyckel/värde-par som kallas taggar (kallas ibland dimensioner i telemetrisystem). Den här artikeln visar hur du använder en källkodsgenerator vid kompileringsstid för att definiera typsäkra måtttaggar (TagNames) samt typer och metoder för måttinspelning. Genom att använda starkt skrivna taggar eliminerar du upprepad exempelkod och ser till att relaterade mått delar samma uppsättning taggnamn med kompileringstidssäkerhet. Den främsta fördelen med den här metoden är förbättrad produktivitet och säkerhet för utvecklare.

Anteckning

När det gäller mått kallas en tagg ibland även för en "dimension". Den här artikeln använder "tagg" för tydlighet och konsekvens med .NET-måttterminologi.

Kom igång

Kom igång genom att 📦 installera NuGet-paketet Microsoft.Extensions.Telemetry.Abstractions :

dotnet add package Microsoft.Extensions.Telemetry.Abstractions

Mer information finns i dotnet add package eller på Hantera paketsberoenden i .NET-applikationer.

Standardvärden för taggnamn och anpassning

Som standard härleder källgeneratorn måtttaggnamn från fält- och egenskapsnamnen för taggklassen. Med andra ord blir varje offentligt fält eller egenskap i det starkt skrivna taggobjektet ett taggnamn som standard. Du kan åsidosätta detta med hjälp av TagNameAttribute i ett fält eller en egenskap för att ange ett anpassat taggnamn. I exemplen nedan visas båda metoderna i praktiken.

Exempel 1: Grundläggande mått med en enda tagg

I följande exempel visas en enkel räknare med en tagg. I det här scenariot vill vi räkna antalet bearbetade begäranden och kategorisera dem efter en Region tagg:

public struct RequestTags
{
    public string Region { get; set; }
}

public static partial class MyMetrics
{
    [Counter<int>(typeof(RequestTags))]
    public static partial RequestCount CreateRequestCount(Meter meter);
}

I föregående kod är RequestTags en starkt typad taggstruktur med en enda egenskap Region. Metoden CreateRequestCount är markerad med CounterAttribute<T> var T är en int, som anger att den genererar ett Counter instrument som spårar int värden. Attributet refererar till typeof(RequestTags), vilket innebär att räknaren använder taggarna som definierats i RequestTags vid registrering av mått. Källgeneratorn skapar en starkt typad instrumentklass (med namnet RequestCount) med en Add metod som accepterar heltalsvärde och RequestTags -objekt.

Om du vill använda det genererade måttet skapar du en Meter och registrerar mått enligt nedan:

Meter meter = new("MyCompany.MyApp", "1.0");
RequestCount requestCountMetric = MyMetrics.CreateRequestCount(meter);

// Create a tag object with the relevant tag value
var tags = new RequestTags { Region = "NorthAmerica" };

// Record a metric value with the associated tag
requestCountMetric.Add(1, tags);

I det här användningsexemplet skapar anropande MyMetrics.CreateRequestCount(meter) ett motinstrument (via Meter) och returnerar ett RequestCount måttobjekt. När du anropar requestCountMetric.Add(1, tags)registrerar måttsystemet antalet 1 som är associerade med taggen Region="NorthAmerica". Du kan återanvända RequestTags-objektet eller skapa nya för att registrera antal för olika regioner, och taggnamnet Region tillämpas konsekvent på varje mätning.

Exempel 2: Metrik med kapslade taggobjekt

För mer komplexa scenarier kan du definiera taggklasser som innehåller flera taggar, kapslade objekt eller till och med ärvda egenskaper. På så sätt kan en grupp relaterade mått effektivt dela en gemensam uppsättning taggar. I nästa exempel definierar du en uppsättning taggklasser och använder dem för tre olika mått:

using Microsoft.Extensions.Diagnostics.Metrics;

namespace MetricsGen;

public class MetricTags : MetricParentTags
{
    [TagName("Dim1DimensionName")]
    public string? Dim1;                      // custom tag name via attribute
    public Operations Operation { get; set; } // tag name defaults to "Operation"
    public MetricChildTags? ChildTagsObject { get; set; }
}

public enum Operations
{
    Unknown = 0,
    Operation1 = 1,
}

public class MetricParentTags
{
    [TagName("DimensionNameOfParentOperation")]
    public string? ParentOperationName { get; set; }  // custom tag name via attribute
    public MetricTagsStruct ChildTagsStruct { get; set; }
}

public class MetricChildTags
{
    public string? Dim2 { get; set; }  // tag name defaults to "Dim2"
}

public struct MetricTagsStruct
{
    public string Dim3 { get; set; }   // tag name defaults to "Dim3"
}

Föregående kod definierar måttets arvs- och objektformer. Följande kod visar hur du använder dessa former med generatorn, som du ser i Metric klassen:

using System.Diagnostics.Metrics;
using Microsoft.Extensions.Diagnostics.Metrics;

public static partial class Metric
{
    [Histogram<long>(typeof(MetricTags))]
    public static partial Latency CreateLatency(Meter meter);

    [Counter<long>(typeof(MetricTags))]
    public static partial TotalCount CreateTotalCount(Meter meter);

    [Counter<int>(typeof(MetricTags))]
    public static partial TotalFailures CreateTotalFailures(Meter meter);
}

I det här exemplet är MetricTags en taggklass som ärver från MetricParentTags och även innehåller ett kapslat taggobjekt (MetricChildTags) och en kapslad struct (MetricTagsStruct). Taggegenskaperna visar både standard- och anpassade taggnamn:

  • Fältet Dim1 i MetricTags har ett [TagName("Dim1DimensionName")]-attribut, så taggnamnet blir "Dim1DimensionName".
  • Egenskapen Operation har inget attribut, så dess taggnamn är som standard "Operation".
  • I MetricParentTagsåsidosättas egenskapen ParentOperationName med ett anpassat taggnamn "DimensionNameOfParentOperation".
  • Den kapslade MetricChildTags-klassen definierar en Dim2-egenskap (inget attribut, taggnamn "Dim2").
  • Structen MetricTagsStruct definierar ett Dim3 fält (taggnamn "Dim3").

Alla tre måttdefinitionerna CreateLatency, CreateTotalCountoch CreateTotalFailures använda MetricTags som taggobjekttyp. Det innebär att de genererade måtttyperna (Latency, TotalCountoch TotalFailures) alla förväntar sig en MetricTags instans när data registreras. Vart och ett av dessa mått har samma uppsättning taggnamn:Dim1DimensionName, Operation, Dim2, Dim3och DimensionNameOfParentOperation.

Följande kod visar hur du skapar och använder dessa mått i en klass:

internal class MyClass
{
    private readonly Latency _latencyMetric;
    private readonly TotalCount _totalCountMetric;
    private readonly TotalFailures _totalFailuresMetric;

    public MyClass(Meter meter)
    {
        // Create metric instances using the source-generated factory methods
        _latencyMetric = Metric.CreateLatency(meter);
        _totalCountMetric = Metric.CreateTotalCount(meter);
        _totalFailuresMetric = Metric.CreateTotalFailures(meter);
    }

    public void DoWork()
    {
        var startingTimestamp = Stopwatch.GetTimestamp();
        bool requestSuccessful = true;
        // Perform some operation to measure
        var elapsedTime = Stopwatch.GetElapsedTime(startingTimestamp);

        // Create a tag object with values for all tags
        var tags = new MetricTags
        {
            Dim1 = "Dim1Value",
            Operation = Operations.Operation1,
            ParentOperationName = "ParentOpValue",
            ChildTagsObject = new MetricChildTags
            {
                Dim2 = "Dim2Value",
            },
            ChildTagsStruct = new MetricTagsStruct
            {
                Dim3 = "Dim3Value"
            }
        };

        // Record the metric values with the associated tags
        _latencyMetric.Record(elapsedTime.ElapsedMilliseconds, tags);
        _totalCountMetric.Add(1, tags);
        if (!requestSuccessful)
        {
            _totalFailuresMetric.Add(1, tags);
        }
    }
}

I föregående MyClass.DoWork-metod fylls ett MetricTags-objekt med värden för varje tagg. Det här enda tags objektet skickas sedan till alla tre instrumenten när data registreras. Det Latency måttet (ett histogram) registrerar den förflutna tiden och båda räknarna (TotalCount och TotalFailures) registrerar antalet förekomster. Eftersom alla mått har samma taggobjekttyp finns taggarna (Dim1DimensionName, Operation, Dim2, Dim3, DimensionNameOfParentOperation) på varje mätning.

Prestandaöverväganden

Att använda starkt skrivna taggar via källgenerering medför inga omkostnader jämfört med att använda mått direkt. Om du behöver minimera allokeringen ytterligare för mycket högfrekventa mått kan du överväga att definiera taggobjektet som en struct (värdetyp) i stället för en class. Om du använder en struct för taggobjektet kan du undvika heap-allokeringar när du registrerar mått, eftersom taggarna kommer att skickas som värde.

Krav för genererade måttmetoder

När du definierar måttfabriksmetoder (de partiella metoderna som är dekorerade med [Counter], [Histogram]osv.) ställer källgeneratorn några krav:

  • Varje metod måste vara public static partial (för att källgeneratorn ska kunna tillhandahålla implementeringen).
  • Returtypen för varje partiell metod måste vara unik (så att generatorn kan skapa en unikt namngiven typ för måttet).
  • Metodnamnet bör inte börja med ett understreck (_), och parameternamn bör inte börja med ett understreck.
  • Den första parametern måste vara en Meter (det här är mätarinstansen som används för att skapa det underliggande instrumentet).
  • Metoderna kan inte vara generiska och kan inte ha generiska parametrar.
  • Taggegenskaperna i taggklassen kan bara vara av typen string eller enum. För andra typer (till exempel bool eller numeriska typer) konverterar du värdet till en sträng innan du tilldelar det till taggobjektet.

Om du följer dessa krav ser du till att källgeneratorn kan producera måtttyperna och metoderna.

Se även