From f2771bc70406b49b5e122e0886e5b45ea0fcfdb8 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Tue, 5 May 2026 09:42:21 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(server):=20wire=20sampler=20ho?= =?UTF-8?q?t-reload=20callback=20(ADR-0023=20Phase=203,=20sub-phase=203.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes ADR-0023 hot-reload Phase 3 (telemetry sampler). Last sub-phase of the 3-PR sequence: 3.1 = ReconfigureTracerProvider in pkg/telemetry (PR #45), 3.2 = sampler callback plumbing in pkg/config (in main via PR #47 race-merge — see Q-038 in mistral-quirks.md), 3.3 = wire it in pkg/server.Run (this PR). Implementation: - Capture telemetrySetup pointer (was a local) so it outlives initial setup and the sampler callback can mutate it. - After WatchAndApply registration, register a callback that: 1. Mutates SamplerType/SamplerRatio on the captured Setup 2. Calls ReconfigureTracerProvider (which builds the new TP, swaps the global, drains the old) 3. Updates s.tracerProvider so graceful shutdown still drains the correct provider. - Callback only registered if telemetrySetup != nil (telemetry was successfully initialized at startup; hot-reloading telemetry-on is out of scope per ADR-0023). ADR-0023 status updated: Phase 1+2+3 Implemented. api.v2_enabled remains explicitly deferred (router refactor needed; separate ADR). Race detector clean on touched packages. Full BDD suite green. --- adr/0023-config-hot-reloading.md | 2 +- pkg/server/server.go | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/adr/0023-config-hot-reloading.md b/adr/0023-config-hot-reloading.md index 09739b8..f75923b 100644 --- a/adr/0023-config-hot-reloading.md +++ b/adr/0023-config-hot-reloading.md @@ -1,6 +1,6 @@ # Config Hot Reloading Strategy -**Status:** Phase 1+2 Implemented (2026-05-05 — `logging.level` and `auth.jwt.ttl` hot-reloadable via `Config.WatchAndApply` in `pkg/config/config.go`, wired in `pkg/server/server.go Run`. Phase 2 also fixed a pre-existing bug where the hardcoded 24h TTL ignored `auth.jwt.ttl` from config entirely.) Phase 3 sub-phase 3.1 Implemented (2026-05-05 — `ReconfigureTracerProvider` in `pkg/telemetry/telemetry.go` added). Phase 3 sub-phase 3.2 In Flight (2026-05-05 — `telemetry.sampler.type` + `telemetry.sampler.ratio` hot-reload via `SetSamplerReconfigureCallback` in `pkg/config/config.go`. Remaining field: `api.v2_enabled`.) +**Status:** Phase 1+2+3 Implemented (2026-05-05). Hot-reloadable fields: `logging.level`, `auth.jwt.ttl`, `telemetry.sampler.type`, `telemetry.sampler.ratio`. Plumbing: `Config.WatchAndApply` in `pkg/config/config.go`, `ReconfigureTracerProvider` in `pkg/telemetry/telemetry.go`, sampler reconfigure callback wired in `pkg/server/server.go Run`. Phase 2 also fixed a pre-existing bug where the hardcoded 24h TTL ignored `auth.jwt.ttl` from config. Remaining field `api.v2_enabled` is **deferred**: hot-reloading routing requires either an always-register-with-middleware-gate refactor of the chi router or an atomic router swap — different complexity class, separate ADR if reopened. **Authors:** Gabriel Radureau, AI Agent **Date:** 2026-04-05 **Last Updated:** 2026-05-05 diff --git a/pkg/server/server.go b/pkg/server/server.go index 9255fff..0e0fc2a 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -679,10 +679,11 @@ func (s *Server) Router() http.Handler { func (s *Server) Run() error { // Initialize OpenTelemetry if enabled var err error + var telemetrySetup *telemetry.Setup if s.withOTEL { log.Trace().Msg("Initializing OpenTelemetry tracing") - telemetrySetup := &telemetry.Setup{ + telemetrySetup = &telemetry.Setup{ ServiceName: s.config.GetServiceName(), OTLPEndpoint: s.config.GetOTLPEndpoint(), Insecure: s.config.GetTelemetryInsecure(), @@ -694,6 +695,7 @@ func (s *Server) Run() error { if s.tracerProvider, err = telemetrySetup.InitializeTracing(context.Background()); err != nil { log.Error().Err(err).Msg("Failed to initialize OpenTelemetry, continuing without tracing") s.withOTEL = false + telemetrySetup = nil } else { log.Trace().Msg("OpenTelemetry tracing initialized successfully") } @@ -714,7 +716,27 @@ func (s *Server) Run() error { s.userService.StartJWTSecretCleanupLoop(rootCtx, s.config.GetJWTSecretCleanupInterval()) } - // Start config hot-reload watcher (ADR-0023 Phase 1: logging.level only). + // Wire the sampler hot-reload callback (ADR-0023 Phase 3, sub-phase 3.3). + // telemetrySetup is non-nil only when telemetry was successfully initialized + // at startup — hot-reloading telemetry-on is out of scope (see ADR-0023). + // The callback updates the SamplerType/Ratio on the captured Setup, then + // rebuilds the global tracer provider via ReconfigureTracerProvider. + if telemetrySetup != nil { + s.config.SetSamplerReconfigureCallback(func(ctx context.Context, samplerType string, samplerRatio float64) error { + telemetrySetup.SamplerType = samplerType + telemetrySetup.SamplerRatio = samplerRatio + newTP, rerr := telemetrySetup.ReconfigureTracerProvider(ctx, s.tracerProvider) + if rerr != nil { + return rerr + } + if newTP != nil { + s.tracerProvider = newTP + } + return nil + }) + } + + // Start config hot-reload watcher (ADR-0023 Phase 1+2+3). // Stops automatically on rootCtx cancellation. s.config.WatchAndApply(rootCtx) -- 2.49.1