Добавить трейсинг OpenTelemetry (OTLP) во все сервисы

Общая настройка DealTracingHosting (Deal.Grpc.Hosting + Deal.Api):
AspNetCore/Http/GrpcNetClient инструментация, OTLP-экспорт во внешний
коллектор, имя сервиса из env. Логи обогащаются TraceId/SpanId.
This commit is contained in:
2026-09-13 04:47:27 +03:00
parent ae7014ccda
commit 99828857ef
13 changed files with 159 additions and 2 deletions
+1
View File
@@ -18,6 +18,7 @@ WebApplication app = AiServiceHost.Create(
{
DealLogging.Configure(builder, aiProcessName);
DealMetricsHosting.AddDealMetrics(builder, metricsPort);
DealTracingHosting.AddDealTracing(builder, aiProcessName);
});
DealMetricsHosting.MapDealMetrics(app);
+1
View File
@@ -42,6 +42,7 @@
Версии — 1.17.x (Prometheus-экспортёр и gRPC-клиент только pre-release-линией; остальные —
1.17.0 stable). Настройка — Deal.Api/Observability/DealMetricsHosting.cs. -->
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.17.0-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.17.0" />
+2 -1
View File
@@ -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<TraceContextEnricher>();
string logsDirectory = ResolveLogsDirectory(environment.ContentRootPath, configuration[LogsDirectoryEnvKey]);
Directory.CreateDirectory(logsDirectory);
@@ -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()));
}
}
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace Deal.Api.Observability;
/// <summary>
/// Настройка трейсинга ядра Deal.Api
/// </summary>
public static class DealTracingHosting
{
/// <summary>
/// Env-ключ OTLP-endpoint коллектора
/// </summary>
public const string OtlpEndpointEnvKey = "OTEL_EXPORTER_OTLP_ENDPOINT";
/// <summary>
/// Env-ключ имени сервиса в трейсах
/// </summary>
public const string ServiceNameEnvKey = "OTEL_SERVICE_NAME";
/// <summary>
/// Регистрирует трейсинг OpenTelemetry с экспортом OTLP
/// </summary>
/// <param name="builder">Билдер ядра.</param>
/// <param name="defaultServiceName">Имя сервиса, если env OTEL_SERVICE_NAME не задан.</param>
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)));
}
}
+1
View File
@@ -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-БД.
@@ -45,6 +45,7 @@
ней нет; держим для будущего трейсинга). Версии — 1.17.x (Prometheus-экспортёр и gRPC-клиент
выпускаются только pre-release-линией; остальные пакеты 1.17.0 stable). -->
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.17.0-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.17.0" />
@@ -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<TraceContextEnricher>();
string logsDirectory = ResolveLogsDirectory(environment.ContentRootPath, configuration[LogsDirectoryEnvKey]);
Directory.CreateDirectory(logsDirectory);
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace Deal.Grpc.Hosting.Services;
/// <summary>
/// Общая настройка трейсинга Deal-сервисов
/// </summary>
public static class DealTracingHosting
{
/// <summary>
/// Env-ключ OTLP-endpoint коллектора
/// </summary>
public const string OtlpEndpointEnvKey = "OTEL_EXPORTER_OTLP_ENDPOINT";
/// <summary>
/// Env-ключ имени сервиса в трейсах
/// </summary>
public const string ServiceNameEnvKey = "OTEL_SERVICE_NAME";
/// <summary>
/// Регистрирует трейсинг OpenTelemetry с экспортом OTLP
/// </summary>
/// <param name="builder">Билдер хоста сервиса.</param>
/// <param name="defaultServiceName">Имя сервиса, если env OTEL_SERVICE_NAME не задан.</param>
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)));
}
}
@@ -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()));
}
}
+1
View File
@@ -18,6 +18,7 @@ WebApplication app = MlServiceHost.Create(
{
DealLogging.Configure(builder, mlProcessName);
DealMetricsHosting.AddDealMetrics(builder, metricsPort);
DealTracingHosting.AddDealTracing(builder, mlProcessName);
});
DealMetricsHosting.MapDealMetrics(app);
@@ -16,6 +16,7 @@ WebApplication app = StorageServiceHost.Create(
{
DealLogging.Configure(builder, storageProcessName);
DealMetricsHosting.AddDealMetrics(builder, metricsPort);
DealTracingHosting.AddDealTracing(builder, storageProcessName);
});
DealMetricsHosting.MapDealMetrics(app);
@@ -18,6 +18,7 @@ WebApplication app = TelegramServiceHost.Create(
{
DealLogging.Configure(builder, telegramProcessName);
DealMetricsHosting.AddDealMetrics(builder, metricsPort);
DealTracingHosting.AddDealTracing(builder, telegramProcessName);
});
DealMetricsHosting.MapDealMetrics(app);