From 6dfb69c26c025df6204314a137a9ba572d21112a Mon Sep 17 00:00:00 2001 From: cesnimda Date: Thu, 2 Jul 2026 17:12:22 +0200 Subject: [PATCH] feat(ops): OpenTelemetry traces + metrics (RECOMMENDATIONS #5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ASP.NET Core, outbound HttpClient (Gmail/Ollama), and Npgsql instrumentation with an OTLP exporter that activates ONLY when Otel:Endpoint / OTEL_EXPORTER_OTLP_ENDPOINT is configured — zero overhead otherwise. Logs remain on Serilog. Adds a compose 'observability' profile running grafana/otel-lgtm (Grafana+Tempo+Prometheus+Loki in one container, loopback :3000) with a documented one-line enablement. 55/55 tests; vuln + format gates clean. Co-Authored-By: Claude Opus 4.8 --- docker-compose.yml | 12 +++++++++++ src/InboxIntel.Api/InboxIntel.Api.csproj | 5 +++++ src/InboxIntel.Api/Program.cs | 26 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index d311691..d1e7e0e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,6 +67,8 @@ services: Ai__Mode: ${AI_MODE:-Disabled} # Points at the compose 'ollama' service when the ai profile is up; harmless otherwise. Ai__OllamaBaseUrl: ${OLLAMA_BASE_URL:-http://ollama:11434} + # OTLP export activates only when set (e.g. http://lgtm:4317 with the observability profile). + OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_ENDPOINT:-} # Dev mode shows the dev banner and caps the initial sync. Set DEV_MODE=true # and MAX_MESSAGES=1000 in deploy/.env to exercise it in this Docker setup. App__DevMode: ${DEV_MODE:-false} @@ -109,6 +111,16 @@ services: # count: all # capabilities: [gpu] + # Observability (RECOMMENDATIONS #5): all-in-one Grafana+Tempo+Prometheus+Loki. + # Enable with: docker compose --profile observability up -d + # then set OTEL_ENDPOINT=http://lgtm:4317 in deploy/.env and restart the api. + # Grafana UI: http://localhost:3000 (admin/admin on first run). + lgtm: + image: grafana/otel-lgtm + profiles: ["observability"] + ports: + - "127.0.0.1:3000:3000" + # Optional reverse proxy. Enable with: docker compose --profile proxy up nginx: image: nginx:alpine diff --git a/src/InboxIntel.Api/InboxIntel.Api.csproj b/src/InboxIntel.Api/InboxIntel.Api.csproj index 8b126b4..42a0fe2 100644 --- a/src/InboxIntel.Api/InboxIntel.Api.csproj +++ b/src/InboxIntel.Api/InboxIntel.Api.csproj @@ -15,6 +15,11 @@ + + + + + diff --git a/src/InboxIntel.Api/Program.cs b/src/InboxIntel.Api/Program.cs index d1d58ce..38a1592 100644 --- a/src/InboxIntel.Api/Program.cs +++ b/src/InboxIntel.Api/Program.cs @@ -16,6 +16,10 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; +using Npgsql; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; using Serilog; var builder = WebApplication.CreateBuilder(args); @@ -122,6 +126,28 @@ builder.Services.AddAuthentication(options => builder.Services.AddAuthorization(); +// RECOMMENDATIONS #5: OpenTelemetry traces + metrics (ASP.NET, outbound HTTP, Npgsql). +// The OTLP exporter only activates when Otel:Endpoint (or the standard +// OTEL_EXPORTER_OTLP_ENDPOINT env var) is configured — zero overhead otherwise. +// Logs stay on Serilog. Pair with the compose "observability" profile (grafana/otel-lgtm). +var otlpEndpoint = builder.Configuration["Otel:Endpoint"] + ?? Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT"); +if (!string.IsNullOrWhiteSpace(otlpEndpoint)) +{ + builder.Services.AddOpenTelemetry() + .ConfigureResource(r => r.AddService("inboxintel-api")) + .WithTracing(t => t + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddNpgsql() + .AddOtlpExporter(o => o.Endpoint = new Uri(otlpEndpoint))) + .WithMetrics(m => m + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddNpgsqlInstrumentation() + .AddOtlpExporter(o => o.Endpoint = new Uri(otlpEndpoint))); +} + builder.Services.AddApiVersioning(o => { o.DefaultApiVersion = new ApiVersion(1, 0); -- 2.52.0