feat(server): wire sampler hot-reload callback (ADR-0023 Phase 3, sub-phase 3.3)

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.
This commit is contained in:
2026-05-05 09:42:21 +02:00
parent f97b6874c9
commit f2771bc704
2 changed files with 25 additions and 3 deletions

View File

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