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