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();
}
}