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; /// /// Общая настройка метрик Deal-сервисов (этап 12, пакет A): OpenTelemetry → экспортёр Prometheus, /// эндпоинт /metrics в отдельном HTTP/1.1 Kestrel-эндпоинте (порт 9464 по умолчанию). /// /// /// /// gRPC-сервисы слушают HTTP/2 (см. ), а /// Prometheus scrape'ит обычным HTTP/1.1-запросом GET — поэтому метрики вынесены на отдельный /// Kestrel-эндпоинт с : тот же процесс, тот же DI, но отдельный порт. /// Порт не публикуется наружу — scrape идёт внутри compose-сети от сервиса prometheus. /// /// /// Сервисы вызывают ровно две строки: — на этапе сборки хоста (до /// builder.Build(), обычно из configureBuilder-хука Program.cs), и /// — после сборки. Инструментация (входящие ASP.NET Core/gRPC, /// исходящие HTTP/gRPC) даёт метрики RPS/латентности/ошибок без ручного кода; прикладные метрики /// (токены, аудит, очереди) добавляет ядро своим meter'ом . /// /// public static class DealMetricsHosting { /// /// Имя meter'а прикладных метрик Deal (общий префикс с ядром: deal.*). /// public const string MeterName = "Deal"; /// /// Порт эндпоинта /metrics по умолчанию (конвенция OpenTelemetry Prometheus). /// public const int DefaultMetricsPort = 9464; // Env-ключ порта метрик (переопределяет DefaultMetricsPort). private const string MetricsPortEnvKey = "METRICS_PORT"; /// /// Порт эндпоинта метрик: env METRICS_PORT (заданное нечисловое значение игнорируется), /// иначе . Локальный запуск нескольких процессов на хосте без /// compose требует разных значений (в compose порты контейнеров изолированы). /// /// Дефолтный порт (обычно ). /// Порт HTTP/1.1-эндпоинта метрик. public static int ResolveMetricsPort(int defaultPort) => int.TryParse(Environment.GetEnvironmentVariable(MetricsPortEnvKey), out int port) && port > 0 ? port : defaultPort; /// /// Регистрирует OTel-метрики и Kestrel-эндпоинт метрик (HTTP/1.1, 0.0.0.0:). /// Вызывать до builder.Build(). /// /// Билдер хоста сервиса. /// Порт HTTP/1.1-эндпоинта метрик. 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()); } /// /// Мапит эндпоинт /metrics (формат Prometheus). Вызывать после builder.Build(). /// /// Собранное приложение сервиса. public static void MapDealMetrics(WebApplication app) { ArgumentNullException.ThrowIfNull(app); app.MapPrometheusScrapingEndpoint(); } }