Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Aplicativos .NET modernos podem capturar métricas usando a System.Diagnostics.Metrics API. Essas métricas geralmente incluem contexto adicional na forma de pares chave-valor, denominados marcadores (às vezes chamados de dimensões em sistemas de telemetria). Este artigo mostra como usar um gerador de código em tempo de compilação para definir tags de métrica fortemente tipadas (TagNames) e tipos e métodos de registro de métricas. Usando tags fortemente tipadas, você elimina o código repetitivo e padrão e garante que as métricas relacionadas compartilhem o mesmo conjunto de nomes de tags com segurança em tempo de compilação. O principal benefício dessa abordagem é a melhoria da produtividade do desenvolvedor e da segurança de tipos.
Observação
No contexto das métricas, uma "etiqueta" às vezes também é referida como "dimensão". Este artigo usa "etiqueta" para clareza e consistência com a terminologia das métricas do .NET.
Introdução
Para começar, instale o 📦 pacote NuGet Microsoft.Extensions.Telemetry.Abstractions :
dotnet add package Microsoft.Extensions.Telemetry.Abstractions
Para obter mais informações, consulte dotnet add package ou Gerenciar dependências de pacotes em aplicativos .NET.
Padrões de nome de tag e personalização
Por padrão, o gerador de código deriva os nomes das tags de métrica dos nomes de campo e propriedade da sua classe de tags. Em outras palavras, cada campo público ou propriedade no objeto de tag fortemente tipado se transforma em um nome de tag por padrão. Você pode substituir isso usando a tag TagNameAttribute em um campo ou propriedade para especificar um nome de tag personalizado. Nos exemplos abaixo, você verá as duas abordagens em ação.
Exemplo 1: Métrica básica com uma única tag
O exemplo a seguir demonstra uma métrica de contador simples com uma etiqueta. Nesse cenário, queremos contar o número de solicitações processadas e categorizá-las por uma Region marca:
public struct RequestTags
{
public string Region { get; set; }
}
public static partial class MyMetrics
{
[Counter<int>(typeof(RequestTags))]
public static partial RequestCount CreateRequestCount(Meter meter);
}
No código anterior, RequestTags é um struct de tag fortemente tipado com uma única propriedade Region. O método CreateRequestCount é marcado com CounterAttribute<T>, onde T é um int, indicando que gera um instrumento Counter que rastreia valores int. O atributo refere-se a typeof(RequestTags), o que significa que o contador usa as marcas definidas em RequestTags ao gravar métricas. O gerador de origem produz uma classe de instrumento fortemente tipada (denominada RequestCount) com um método Add que aceita um valor inteiro e um objeto RequestTags.
Para usar a métrica gerada, crie um Meter e registre as medições, conforme mostrado abaixo:
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);
Neste exemplo de uso, a chamada MyMetrics.CreateRequestCount(meter) cria um instrumento de contador (por meio do Meter) e retorna um RequestCount objeto de métrica. Quando você chama requestCountMetric.Add(1, tags), o sistema de métricas registra uma contagem de 1 associada à etiqueta Region="NorthAmerica". Você pode reutilizar o objeto RequestTags ou criar novos para registrar contagens de diferentes regiões, e o nome da tag Region será aplicado consistentemente a cada medida.
Exemplo 2: Métrica com objetos de tag aninhados
Para cenários mais complexos, você pode definir classes de marca que incluem várias marcas, objetos aninhados ou até mesmo propriedades herdadas. Isso permite que um grupo de métricas relacionadas compartilhe de forma eficaz um conjunto comum de etiquetas. No próximo exemplo, você define um conjunto de classes de marca e as usa para três métricas diferentes:
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"
}
O código anterior define a herança de métricas e as formas dos objetos. O código a seguir demonstra como usar essas formas com o gerador, conforme mostrado na Metric classe:
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);
}
Neste exemplo, MetricTags é uma classe de tag que herda de MetricParentTags e também contém um objeto de tag aninhado (MetricChildTags) e uma estrutura aninhada (MetricTagsStruct). As propriedades da tag demonstram tanto nomes de tag padrão quanto personalizados.
- O
Dim1campo emMetricTagstem um[TagName("Dim1DimensionName")]atributo, portanto, seu nome de marca será"Dim1DimensionName". - A propriedade
Operationnão tem atributo, então o nome da tag padrão é"Operation". - Em
MetricParentTags, a propriedadeParentOperationNameé substituída por um nome de tag personalizado"DimensionNameOfParentOperation". - A classe aninhada
MetricChildTagsdefine uma propriedadeDim2(sem atributo, com nome de tag"Dim2"). - O
MetricTagsStructestrutura define umDim3campo (nome da tag"Dim3").
Todas as três definições de métrica CreateLatency, CreateTotalCount e CreateTotalFailures usam MetricTags como tipo de objeto de marca. Isso significa que os tipos de métrica gerados (Latency, TotalCount e TotalFailures) esperarão uma instância MetricTags ao gravar dados.
Cada uma dessas métricas terá o mesmo conjunto de nomes de marca:Dim1DimensionName, Operation, Dim2, e Dim3DimensionNameOfParentOperation.
O código a seguir mostra como criar e usar essas métricas em uma classe:
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);
}
}
}
No método anterior MyClass.DoWork, um objeto MetricTags é preenchido com valores para cada tag. Em seguida, esse único tags objeto é passado para todos os três instrumentos ao gravar dados. A Latency métrica (um histograma) registra o tempo decorrido e os contadores (TotalCount e TotalFailures) registram contagens de ocorrências. Como todas as métricas compartilham o mesmo tipo de objeto de marca, as marcas (Dim1DimensionName, , Operation, Dim2, Dim3, DimensionNameOfParentOperation) estão presentes em todas as medidas.
Especificando unidades
A partir do .NET 10.2, opcionalmente, você pode especificar uma unidade de medida para suas métricas usando o Unit parâmetro. Isso ajuda a fornecer contexto sobre o que a métrica mede (por exemplo, "segundos", "bytes" e "solicitações"). A unidade é passada para o elemento subjacente Meter ao criar o instrumento.
O código a seguir demonstra como usar o gerador com tipos primitivos com unidades especificadas:
public static partial class Metric
{
[Histogram<long>(typeof(MetricTags), Unit = "ms")]
public static partial Latency CreateLatency(Meter meter);
[Counter<long>(typeof(MetricTags), Unit = "requests")]
public static partial TotalCount CreateTotalCount(Meter meter);
[Counter<int>(typeof(MetricTags), Unit = "failures")]
public static partial TotalFailures CreateTotalFailures(Meter meter);
}
Considerações sobre desempenho
O uso de tags fortemente tipadas por meio da geração de fonte não adiciona sobrecarga em relação ao uso direto de métricas. Se você precisar minimizar ainda mais as alocações para métricas de alta frequência, considere definir o objeto de marca como um struct (tipo de valor) em vez de um class. Usar um struct para o objeto de tag pode evitar alocações de heap ao gravar métricas, já que as tags seriam passadas por valor.
Requisitos do método de métricas geradas
Ao definir métodos de fábrica de métricas (os métodos parciais decorados com [Counter], [Histogram] etc.), o gerador de origem impõe alguns requisitos:
- Cada método deve ser
public static partial(para que o gerador de origem forneça a implementação). - O tipo de retorno de cada método parcial deve ser exclusivo (para que o gerador possa criar um tipo nomeado exclusivamente para a métrica).
- O nome do método não deve começar com um sublinhado (
_) e os nomes de parâmetro não devem começar com um sublinhado. - O primeiro parâmetro deve ser um Meter (esta é a instância de medidor usada para criar o instrumento subjacente).
- Os métodos não podem ser genéricos e não podem ter parâmetros genéricos.
- As propriedades de marca na classe de marca só podem ser do tipo
stringouenum. Para outros tipos (por exemplo,boolou tipos numéricos), converta o valor em uma cadeia de caracteres antes de atribuí-lo ao objeto de marca.
Aderir a esses requisitos garante que o gerador de origem possa produzir com êxito os tipos e métodos de métrica.
Consulte também
- Métricas geradas pela origem no .NET
- Criando métricas no .NET (tutorial de instrumentação)
- Coletando métricas no .NET (usando MeterListener e exportadores)
- Geração de fonte para registro em log no .NET (para uma abordagem semelhante de geração de fonte aplicada ao registro em log)