diff --git a/src/ai-service/Deal.Ai/Program.cs b/src/ai-service/Deal.Ai/Program.cs index 08b6a63..95a6ffd 100644 --- a/src/ai-service/Deal.Ai/Program.cs +++ b/src/ai-service/Deal.Ai/Program.cs @@ -18,6 +18,7 @@ WebApplication app = AiServiceHost.Create( { DealLogging.Configure(builder, aiProcessName); DealMetricsHosting.AddDealMetrics(builder, metricsPort); + DealTracingHosting.AddDealTracing(builder, aiProcessName); }); DealMetricsHosting.MapDealMetrics(app); diff --git a/src/core/Deal.Api/Deal.Api.csproj b/src/core/Deal.Api/Deal.Api.csproj index 7383b7e..8a91930 100644 --- a/src/core/Deal.Api/Deal.Api.csproj +++ b/src/core/Deal.Api/Deal.Api.csproj @@ -42,6 +42,7 @@ Версии — 1.17.x (Prometheus-экспортёр и gRPC-клиент только pre-release-линией; остальные — 1.17.0 stable). Настройка — Deal.Api/Observability/DealMetricsHosting.cs. --> + diff --git a/src/core/Deal.Api/Logging/DealLogging.cs b/src/core/Deal.Api/Logging/DealLogging.cs index 7bd21e6..c998210 100644 --- a/src/core/Deal.Api/Logging/DealLogging.cs +++ b/src/core/Deal.Api/Logging/DealLogging.cs @@ -62,7 +62,8 @@ internal static class DealLogging .MinimumLevel.Is(ParseMinimumLevel(configuration[MinimumLevelEnvKey])) .MinimumLevel.Override(EntityFrameworkCoreCategory, LogEventLevel.Warning) .MinimumLevel.Override(GrpcCategory, LogEventLevel.Information) - .Enrich.FromLogContext(); + .Enrich.FromLogContext() + .Enrich.With(); string logsDirectory = ResolveLogsDirectory(environment.ContentRootPath, configuration[LogsDirectoryEnvKey]); Directory.CreateDirectory(logsDirectory); diff --git a/src/core/Deal.Api/Logging/TraceContextEnricher.cs b/src/core/Deal.Api/Logging/TraceContextEnricher.cs new file mode 100644 index 0000000..d8c0ee4 --- /dev/null +++ b/src/core/Deal.Api/Logging/TraceContextEnricher.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using Serilog.Core; +using Serilog.Events; + +namespace Deal.Api.Logging; + +internal sealed class TraceContextEnricher : ILogEventEnricher +{ + void ILogEventEnricher.Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + Activity? activity = Activity.Current; + if (activity is null) + { + return; + } + + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TraceId", activity.TraceId.ToString())); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("SpanId", activity.SpanId.ToString())); + } +} diff --git a/src/core/Deal.Api/Observability/DealTracingHosting.cs b/src/core/Deal.Api/Observability/DealTracingHosting.cs new file mode 100644 index 0000000..a030b09 --- /dev/null +++ b/src/core/Deal.Api/Observability/DealTracingHosting.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + +namespace Deal.Api.Observability; + +/// +/// Настройка трейсинга ядра Deal.Api +/// +public static class DealTracingHosting +{ + /// + /// Env-ключ OTLP-endpoint коллектора + /// + public const string OtlpEndpointEnvKey = "OTEL_EXPORTER_OTLP_ENDPOINT"; + + /// + /// Env-ключ имени сервиса в трейсах + /// + public const string ServiceNameEnvKey = "OTEL_SERVICE_NAME"; + + /// + /// Регистрирует трейсинг OpenTelemetry с экспортом OTLP + /// + /// Билдер ядра. + /// Имя сервиса, если env OTEL_SERVICE_NAME не задан. + public static void AddDealTracing(WebApplicationBuilder builder, string defaultServiceName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(defaultServiceName); + + // Трейсинг выключен без OTLP-endpoint (dev без профиля observability): иначе экспортёр + // вхолостую спамит ошибками соединения. + string? endpoint = Environment.GetEnvironmentVariable(OtlpEndpointEnvKey); + if (string.IsNullOrWhiteSpace(endpoint)) + { + return; + } + + string serviceName = Environment.GetEnvironmentVariable(ServiceNameEnvKey) is { Length: > 0 } configured + ? configured + : defaultServiceName; + + builder.Services + .AddOpenTelemetry() + .WithTracing(tracing => tracing + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName)) + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddGrpcClientInstrumentation() + .AddOtlpExporter(options => options.Endpoint = new Uri(endpoint))); + } +} diff --git a/src/core/Deal.Api/Program.cs b/src/core/Deal.Api/Program.cs index 897ec6f..38dbbf3 100644 --- a/src/core/Deal.Api/Program.cs +++ b/src/core/Deal.Api/Program.cs @@ -66,6 +66,7 @@ DealLogging.Configure(builder, coreProcessName); int metricsPort = DealMetricsHosting.ResolveMetricsPort(DealMetricsHosting.DefaultMetricsPort); DealMetricsHosting.AddDealMetrics(builder, metricsPort); +DealTracingHosting.AddDealTracing(builder, coreProcessName); // Строка подключения Postgres — ТОЛЬКО из конфигурации (env/appsettings): dev-пароль в коде отсутствует // (Security review). Отсутствие строки = fail-fast на старте, а не тихий уход на несуществующую dev-БД. diff --git a/src/grpc-hosting/Deal.Grpc.Hosting/Deal.Grpc.Hosting.csproj b/src/grpc-hosting/Deal.Grpc.Hosting/Deal.Grpc.Hosting.csproj index b539560..2bf63ff 100644 --- a/src/grpc-hosting/Deal.Grpc.Hosting/Deal.Grpc.Hosting.csproj +++ b/src/grpc-hosting/Deal.Grpc.Hosting/Deal.Grpc.Hosting.csproj @@ -45,6 +45,7 @@ ней нет; держим для будущего трейсинга). Версии — 1.17.x (Prometheus-экспортёр и gRPC-клиент выпускаются только pre-release-линией; остальные пакеты 1.17.0 stable). --> + diff --git a/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealLogging.cs b/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealLogging.cs index 52eb428..5664f75 100644 --- a/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealLogging.cs +++ b/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealLogging.cs @@ -67,7 +67,8 @@ public static class DealLogging loggerConfiguration .MinimumLevel.Is(ParseMinimumLevel(configuration[MinimumLevelEnvKey])) .MinimumLevel.Override(GrpcCategory, LogEventLevel.Information) - .Enrich.FromLogContext(); + .Enrich.FromLogContext() + .Enrich.With(); string logsDirectory = ResolveLogsDirectory(environment.ContentRootPath, configuration[LogsDirectoryEnvKey]); Directory.CreateDirectory(logsDirectory); diff --git a/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealTracingHosting.cs b/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealTracingHosting.cs new file mode 100644 index 0000000..5dbbad9 --- /dev/null +++ b/src/grpc-hosting/Deal.Grpc.Hosting/Services/DealTracingHosting.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + +namespace Deal.Grpc.Hosting.Services; + +/// +/// Общая настройка трейсинга Deal-сервисов +/// +public static class DealTracingHosting +{ + /// + /// Env-ключ OTLP-endpoint коллектора + /// + public const string OtlpEndpointEnvKey = "OTEL_EXPORTER_OTLP_ENDPOINT"; + + /// + /// Env-ключ имени сервиса в трейсах + /// + public const string ServiceNameEnvKey = "OTEL_SERVICE_NAME"; + + /// + /// Регистрирует трейсинг OpenTelemetry с экспортом OTLP + /// + /// Билдер хоста сервиса. + /// Имя сервиса, если env OTEL_SERVICE_NAME не задан. + public static void AddDealTracing(WebApplicationBuilder builder, string defaultServiceName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(defaultServiceName); + + // Трейсинг выключен без OTLP-endpoint (dev без профиля observability): иначе экспортёр + // вхолостую спамит ошибками соединения. + string? endpoint = Environment.GetEnvironmentVariable(OtlpEndpointEnvKey); + if (string.IsNullOrWhiteSpace(endpoint)) + { + return; + } + + string serviceName = Environment.GetEnvironmentVariable(ServiceNameEnvKey) is { Length: > 0 } configured + ? configured + : defaultServiceName; + + builder.Services + .AddOpenTelemetry() + .WithTracing(tracing => tracing + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName)) + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddGrpcClientInstrumentation() + .AddOtlpExporter(options => options.Endpoint = new Uri(endpoint))); + } +} diff --git a/src/grpc-hosting/Deal.Grpc.Hosting/Services/TraceContextEnricher.cs b/src/grpc-hosting/Deal.Grpc.Hosting/Services/TraceContextEnricher.cs new file mode 100644 index 0000000..8ff9d82 --- /dev/null +++ b/src/grpc-hosting/Deal.Grpc.Hosting/Services/TraceContextEnricher.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using Serilog.Core; +using Serilog.Events; + +namespace Deal.Grpc.Hosting.Services; + +internal sealed class TraceContextEnricher : ILogEventEnricher +{ + void ILogEventEnricher.Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + Activity? activity = Activity.Current; + if (activity is null) + { + return; + } + + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TraceId", activity.TraceId.ToString())); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("SpanId", activity.SpanId.ToString())); + } +} diff --git a/src/ml-service/Deal.Ml/Program.cs b/src/ml-service/Deal.Ml/Program.cs index 4f6fcf9..aa8c09b 100644 --- a/src/ml-service/Deal.Ml/Program.cs +++ b/src/ml-service/Deal.Ml/Program.cs @@ -18,6 +18,7 @@ WebApplication app = MlServiceHost.Create( { DealLogging.Configure(builder, mlProcessName); DealMetricsHosting.AddDealMetrics(builder, metricsPort); + DealTracingHosting.AddDealTracing(builder, mlProcessName); }); DealMetricsHosting.MapDealMetrics(app); diff --git a/src/storage-service/Deal.Storage/Program.cs b/src/storage-service/Deal.Storage/Program.cs index dd40dc3..8aa8892 100644 --- a/src/storage-service/Deal.Storage/Program.cs +++ b/src/storage-service/Deal.Storage/Program.cs @@ -16,6 +16,7 @@ WebApplication app = StorageServiceHost.Create( { DealLogging.Configure(builder, storageProcessName); DealMetricsHosting.AddDealMetrics(builder, metricsPort); + DealTracingHosting.AddDealTracing(builder, storageProcessName); }); DealMetricsHosting.MapDealMetrics(app); diff --git a/src/telegram-service/Deal.Telegram/Program.cs b/src/telegram-service/Deal.Telegram/Program.cs index ec4dcdd..9a4335f 100644 --- a/src/telegram-service/Deal.Telegram/Program.cs +++ b/src/telegram-service/Deal.Telegram/Program.cs @@ -18,6 +18,7 @@ WebApplication app = TelegramServiceHost.Create( { DealLogging.Configure(builder, telegramProcessName); DealMetricsHosting.AddDealMetrics(builder, metricsPort); + DealTracingHosting.AddDealTracing(builder, telegramProcessName); }); DealMetricsHosting.MapDealMetrics(app);