Files
Deal/src/grpc-hosting/Deal.Grpc.Hosting/DealMetricsHosting.cs
T
Rustam Khalimov e8b9fab860 Убрать неиспользуемые using по код-стайлу
Прогон dotnet format (IDE0005) по 4 решениям: удалены лишние using,
оставшиеся после миграции namespace (676 файлов).
2026-09-11 13:30:57 +03:00

95 lines
5.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Metrics;
namespace Deal.Grpc.Hosting;
/// <summary>
/// Общая настройка метрик Deal-сервисов (этап 12, пакет A): OpenTelemetry → экспортёр Prometheus,
/// эндпоинт <c>/metrics</c> в отдельном HTTP/1.1 Kestrel-эндпоинте (порт 9464 по умолчанию).
/// </summary>
/// <remarks>
/// <para>
/// gRPC-сервисы слушают HTTP/2 (см. <see cref="GrpcServer.ConfigureKestrelHttp2Endpoint"/>), а
/// Prometheus scrape'ит обычным HTTP/1.1-запросом GET — поэтому метрики вынесены на отдельный
/// Kestrel-эндпоинт с <see cref="HttpProtocols.Http1"/>: тот же процесс, тот же DI, но отдельный порт.
/// Порт не публикуется наружу — scrape идёт внутри compose-сети от сервиса <c>prometheus</c>.
/// </para>
/// <para>
/// Сервисы вызывают ровно две строки: <see cref="AddDealMetrics"/> — на этапе сборки хоста (до
/// <c>builder.Build()</c>, обычно из <c>configureBuilder</c>-хука Program.cs), и
/// <see cref="MapDealMetrics"/> — после сборки. Инструментация (входящие ASP.NET Core/gRPC,
/// исходящие HTTP/gRPC) даёт метрики RPS/латентности/ошибок без ручного кода; прикладные метрики
/// (токены, аудит, очереди) добавляет ядро своим meter'ом <see cref="MeterName"/>.
/// </para>
/// </remarks>
public static class DealMetricsHosting
{
/// <summary>
/// Имя meter'а прикладных метрик Deal (общий префикс с ядром: <c>deal.*</c>).
/// </summary>
public const string MeterName = "Deal";
/// <summary>
/// Порт эндпоинта <c>/metrics</c> по умолчанию (конвенция OpenTelemetry Prometheus).
/// </summary>
public const int DefaultMetricsPort = 9464;
// Env-ключ порта метрик (переопределяет DefaultMetricsPort).
private const string MetricsPortEnvKey = "METRICS_PORT";
/// <summary>
/// Порт эндпоинта метрик: env <c>METRICS_PORT</c> (заданное нечисловое значение игнорируется),
/// иначе <see cref="DefaultMetricsPort"/>. Локальный запуск нескольких процессов на хосте без
/// compose требует разных значений (в compose порты контейнеров изолированы).
/// </summary>
/// <param name="defaultPort">Дефолтный порт (обычно <see cref="DefaultMetricsPort"/>).</param>
/// <returns>Порт HTTP/1.1-эндпоинта метрик.</returns>
public static int ResolveMetricsPort(int defaultPort)
=> int.TryParse(Environment.GetEnvironmentVariable(MetricsPortEnvKey), out int port) && port > 0
? port
: defaultPort;
/// <summary>
/// Регистрирует OTel-метрики и Kestrel-эндпоинт метрик (HTTP/1.1, 0.0.0.0:<paramref name="metricsPort"/>).
/// Вызывать до <c>builder.Build()</c>.
/// </summary>
/// <param name="builder">Билдер хоста сервиса.</param>
/// <param name="metricsPort">Порт HTTP/1.1-эндпоинта метрик.</param>
public static void AddDealMetrics(WebApplicationBuilder builder, int metricsPort)
{
ArgumentNullException.ThrowIfNull(builder);
// Отдельный HTTP/1.1-эндпоинт для scrape: gRPC-порт остаётся строго HTTP/2 (см. remarks класса).
builder.WebHost.ConfigureKestrel(kestrel =>
{
kestrel.ListenAnyIP(metricsPort, listen => listen.Protocols = HttpProtocols.Http1);
});
// Инструментация входящих запросов (http.server.*: RPS/латентность/ошибки по route) и исходящих
// HTTP-клиентов (http.client.*) + экспортёр Prometheus. AddMeter — прикладные метрики.
// Исходящие gRPC-вызовы (пакет Instrumentation.GrpcNetClient) дают трейс-инструментацию, а не
// метрики — в MeterProviderBuilder не добавляются (для клиентских метрик gRPC-хопа хватает
// серверной стороны соответствующего сервиса).
builder.Services
.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddMeter(MeterName)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddPrometheusExporter());
}
/// <summary>
/// Мапит эндпоинт <c>/metrics</c> (формат Prometheus). Вызывать после <c>builder.Build()</c>.
/// </summary>
/// <param name="app">Собранное приложение сервиса.</param>
public static void MapDealMetrics(WebApplication app)
{
ArgumentNullException.ThrowIfNull(app);
app.MapPrometheusScrapingEndpoint();
}
}