Инициализировать репозиторий «Дейл»

Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы
ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ,
инструкция пользователя, техдокументация, код-стайл), бэклог,
скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
Rustam Khalimov
2026-09-11 02:50:17 +03:00
commit 9e07568ddd
1402 changed files with 177470 additions and 0 deletions
@@ -0,0 +1,95 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
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();
}
}