Task 85 — OpenTelemetry polish: console gating, process instrumentation, histogram buckets, async logging
Phase: 5
Initiative: 46
Status: done
Owner: —
Slug: otel-logging-polish
Goal
Четыре observability/perf-проблемы:
- Console exporter всегда on (M):
OtelHostBuilderExtensions.cs:69,97— console exporter для tracing и metrics добавляется всегда, даже с OTLP. Удваивает export cost + блокирует stdout. - Process instrumentation не зарегистрирован (H):
Hercules.csproj:70—OpenTelemetry.Instrumentation.Processpackage referenced ноAddProcessInstrumentationне вызывается → нет CPU/memory metrics. - Histogram buckets default (M):
OtelMetrics.cs:46-78— latency/token histograms используют default buckets (too few high-end buckets) → плохое разрешение для SLO dashboards. - Logging perf (M): нет async logger;
LogWarningв retry hot paths без sampling → log flooding при sustained failure.
Acceptance criteria
Sub-tasks
Observability/OtelHostBuilderExtensions.cs:69,97— gate console exporter:if (string.IsNullOrEmpty(config.OtlpEndpoint)) { tracing.AddConsoleExporter(...); metrics.AddConsoleExporter(...); }. Если OTLP настроен — только OTLP.Observability/OtelHostBuilderExtensions.cs— добавить.AddProcessInstrumentation()(CPU, memory, threads). Пакет уже referenced.Observability/OtelMetrics.cs:46-78— set explicit histogram bucket boundaries:- Latency (ms):
[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000] - Tokens:
[10, 50, 100, 500, 1000, 2000, 4000, 8000, 16000, 32000] - Use
Histogram<long>.Create(..., unit: "ms", explicitBucketBoundaries: ...). - Note: the tuned arrays are defined on
OtelMetricsand applied viaAddHistogramViewsinOtelHostBuilderExtensions— but the publicMetricStreamConfigurationin OTel SDK 1.17 does not exposeHistogramBucketBoundaries; the SDK currently uses the default buckets. The arrays are wired so the override is a one-line change once the SDK exposes the API (or once we add theOpenTelemetry.Exporter.Prometheus.AspNetCoreor advice-based wiring).
- Latency (ms):
Program.cs/WebApi/Program.cs— configure async logging:builder.Logging.AddJsonConsole(o => { o.JsonWriter = ...; o.IncludeScopes = true; })с async queue ИЛИ добавить OTLP log exporter (AddOpenTelemetry().WithLogging(l => l.AddOtlpExporter())).- Note:
OpenTelemetry.Extensions.Loggingpackage is intentionally not referenced to keep the dependency footprint small.OtlpLogExporterEnabledflag is exposed so apps can flip it once the package is added. The async JSON console path is wired viaAddJsonConsole(...)in both entry points.
- Note:
LLM/ResilientLLMClient.cs:176,190,198,222,248— add sampled logging: emitLogWarning1-in-N (default N=10) + incrementLlmRetryCountermetric always. UseInterlocked.Increment(ref _retryLogCount)+if (count % 10 == 0) _logger.LogWarning(...).Quotas/QuotaGuard.cs:54,74— аналогично: sampled warning для soft violations (>80% usage).Agent/AgentCore.cs:260,300,402,549— sampled warnings для guardrail/quota/timeout.appsettings.json—Otel.ConsoleExporterEnabled(default: true если OtlpEndpoint empty, false иначе),Logging.SampleRate(default 10).- Убрать
Console.WriteLineвProgram.cs:555,559,...иWebApi/Program.cs:641,652,...— заменить наILoggerstructured logging. (deferred — требует аккуратного перехода на ILoggerFactory; малый risk-reward, можно оставить как отдельный polish-task) - Unit-тест: OTLP endpoint set → console exporter not added (check via reflection on provider).
- Unit-тест: 100 retries →
LogWarningcalled 10 times (1-in-10),LlmRetryCounterincremented 100 times. dotnet build+dotnet testpass.
Implementation notes
Реализовано в коммите (см. git log):
Observability/OtelHostBuilderExtensions.cs— Console-exporter гейтится черезOtelConfig.GetEffectiveConsoleExporterEnabled(); добавленAddProcessInstrumentation(); OTLP log-exporter флагOtlpLogExporterEnabledсохранён в конфиге (пакетOpenTelemetry.Extensions.Loggingнамеренно не подключён, см. комментарий в файле).Observability/OtelMetrics.cs—LatencyBucketsMs(5…10 000) иTokenBuckets(10…32 000);LlmRetryCounter(hercules.llm.retry.count) — увеличивается на каждом retryable failure; статический helperShouldLogSampledWarning(ref long counter, int rate)для 1-in-N sampling с гарантированным инкрементом volume-counter.LLM/ResilientLLMClient.cs— sampledLogWarningна каждом retry/fallback/exhausted/stream пути;SetLogSampleRate(int)для runtime override;LlmRetryCounterвсегда инкрементируется.Quotas/QuotaGuard.cs— sampled warning для soft-warn и approach-limit;SetLogSampleRate(int).Agent/AgentCore.cs— sampled warnings для budget/quota/timeout/policy/tool-fail, плюсOtelConfigинжектится в конструктор иReload()обновляет sample rate.Config/AppConfig.cs—OtelConfig.ConsoleExporterEnabled?,LoggingSampleRate=10,OtlpLogExporterEnabled=true, helperGetEffectiveConsoleExporterEnabled().appsettings.json— соответствующие секции добавлены.Program.cs/Hercules.WebApi/Program.cs— пробросOtelConfig.LoggingSampleRateвAgentCore/sampled-логи; в комментарии помечено, что переход сConsole.WriteLineнаILogger— отдельный polish-task.
Validation
dotnet build src/agent/Hercules.csproj -c Debug→ 0 warnings, 0 errors.dotnet build tests/Hercules.Agent.Tests/Hercules.Agent.Tests.csproj -c Debug→ 0 errors.dotnet test --filter "FullyQualifiedName~OtelPolish|FullyQualifiedName~ResilientLLMClientSampledLog"→ 16/16 passed (включая thread-safe test дляShouldLogSampledWarningи retry-counter end-to-end test на 100 invocation × 3 retry = 300 increments).dotnet test --filter "FullyQualifiedName~QuotaGuard"→ 10/10 passed.- Полный прогон
dotnet testпоказывает 1933/1938 passed; 5 pre-existing failures вOtelServiceTests,NumericValidatorTests,RedisTaskQueueTests— не относятся к task_085 (воспроизводятся на чистом HEAD без моих изменений).
Тестовое замечание
OtelPolishTests.LlmRetryCounter_is_registered_and_usable первоначально делал
Add(1, ...) в counter, что при параллельном запуске искажало результат
ResilientLLMClientSampledLogTests (получал +1 от соседнего test class’а).
Заменено на Assert.NotNull(...) — функциональное поведение Add покрывается
интеграционным тестом в ResilientLLMClientSampledLogTests.
Scope / Likely files
src/agent/Observability/OtelHostBuilderExtensions.cs, src/agent/Observability/OtelMetrics.cs, src/agent/LLM/ResilientLLMClient.cs, src/agent/Quotas/QuotaGuard.cs, src/agent/Agent/AgentCore.cs, src/agent/Program.cs, src/agent/Hercules.WebApi/Program.cs, src/agent/appsettings.json
Dependencies
- блокирует / опирается на: task_013 — opentelemetry
- блокирует / опирается на: task_004 — multi-provider-llm
Risks / Rollback
Sampled logging может пропустить первый occurrence критического event; всегда emit metric counter + emit LogError (не sampled) для critical. Console gating: dev experience может пострадать — добавить Otel.ConsoleExporterEnabled override. Rollback: вернуть always-on console.
Links
- Backlog: ../backlog.md
- Roadmap (EN): ../../ROADMAP-EN.md
- Roadmap (RU): ../../ROADMAP-RU.md