detached 77e28e6

fix: address second round of review feedback

- Use fresh context.Background() with 30s timeout for telemetry shutdown
  instead of app ctx (which may be canceled on SIGTERM)
- Only log 'telemetry initialized' when endpoint is non-empty
- Wrap /auth and /admin handlers with otelhttp.NewHandler for consistent
  trace instrumentation across all routes
- Clamp TraceSampleRate to [0,1] and update field doc comment
- Fix version.go doc comment (Get() -> Version(), Commit(), BuildDate())
commit
77e28e6b3d8c9e9d917ae2be1a5c620f3e3778f5
author
brice · · 2026-02-10 07:47 UTC
parent
eef0d4e
signature
unsigned
3 files · +27 -9
M internal/adapters/out/telemetry/provider.go +11 -4
@@ -27,7 +27,7 @@ type Config struct {
2727 Traces bool `mapstructure:"traces"` // Enable trace export
2828 Metrics bool `mapstructure:"metrics"` // Enable metric export
2929 Logs bool `mapstructure:"logs"` // Bridge zerowrap logs to OTLP
30- TraceSampleRate float64 `mapstructure:"trace_sample_rate"` // 0.0-1.0, default 1.0
30+ TraceSampleRate float64 `mapstructure:"trace_sample_rate"` // 0=disabled, (0,1)=ratio-based, >=1=always sample
3131 }
3232
3333 // Provider holds the initialized OTel providers.
@@ -141,11 +141,18 @@ func (p *Provider) setupTracing(ctx context.Context, ep *endpointConfig, cfg Con
141141 }
142142
143143 // 0 = never sample (disabled), (0,1) = ratio-based, >= 1 = always sample.
144+ // Clamp to [0, 1] so out-of-range values don't produce surprising behavior.
145+ rate := cfg.TraceSampleRate
146+ if rate < 0 {
147+ rate = 0
148+ } else if rate > 1 {
149+ rate = 1
150+ }
144151 sampler := trace.AlwaysSample()
145- if cfg.TraceSampleRate == 0 {
152+ if rate == 0 {
146153 sampler = trace.NeverSample()
147- } else if cfg.TraceSampleRate < 1.0 {
148- sampler = trace.TraceIDRatioBased(cfg.TraceSampleRate)
154+ } else if rate < 1.0 {
155+ sampler = trace.TraceIDRatioBased(rate)
149156 }
150157
151158 tp := trace.NewTracerProvider(
@@ -27,7 +27,7 @@ type Config struct {
27 Traces bool `mapstructure:"traces"` // Enable trace export27 Traces bool `mapstructure:"traces"` // Enable trace export
28 Metrics bool `mapstructure:"metrics"` // Enable metric export28 Metrics bool `mapstructure:"metrics"` // Enable metric export
29 Logs bool `mapstructure:"logs"` // Bridge zerowrap logs to OTLP29 Logs bool `mapstructure:"logs"` // Bridge zerowrap logs to OTLP
30- TraceSampleRate float64 `mapstructure:"trace_sample_rate"` // 0.0-1.0, default 1.030+ TraceSampleRate float64 `mapstructure:"trace_sample_rate"` // 0=disabled, (0,1)=ratio-based, >=1=always sample
31 }31 }
32 32
33 // Provider holds the initialized OTel providers.33 // Provider holds the initialized OTel providers.
@@ -141,11 +141,18 @@ func (p *Provider) setupTracing(ctx context.Context, ep *endpointConfig, cfg Con
141 }141 }
142 142
143 // 0 = never sample (disabled), (0,1) = ratio-based, >= 1 = always sample.143 // 0 = never sample (disabled), (0,1) = ratio-based, >= 1 = always sample.
144+ // Clamp to [0, 1] so out-of-range values don't produce surprising behavior.
145+ rate := cfg.TraceSampleRate
146+ if rate < 0 {
147+ rate = 0
148+ } else if rate > 1 {
149+ rate = 1
150+ }
144 sampler := trace.AlwaysSample()151 sampler := trace.AlwaysSample()
145- if cfg.TraceSampleRate == 0 {152+ if rate == 0 {
146 sampler = trace.NeverSample()153 sampler = trace.NeverSample()
147- } else if cfg.TraceSampleRate < 1.0 {154+ } else if rate < 1.0 {
148- sampler = trace.TraceIDRatioBased(cfg.TraceSampleRate)155+ sampler = trace.TraceIDRatioBased(rate)
149 }156 }
150 157
151 tp := trace.NewTracerProvider(158 tp := trace.NewTracerProvider(
M internal/app/run.go +15 -4
@@ -217,8 +217,13 @@ func Run(ctx context.Context, configPath string) error {
217217 if err != nil {
218218 log.Warn().Err(err).Msg("failed to initialize telemetry, continuing without it")
219219 } else {
220- defer telShutdown(ctx)
221- if cfg.Telemetry.Enabled {
220+ // Use a fresh context for shutdown so a canceled app ctx doesn't prevent flushing.
221+ defer func() {
222+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
223+ defer cancel()
224+ telShutdown(shutdownCtx)
225+ }()
226+ if cfg.Telemetry.Enabled && cfg.Telemetry.Endpoint != "" {
222227 // Bridge zerowrap logs to OTel if log export is enabled
223228 if cfg.Telemetry.Logs && telProvider.LogProvider != nil {
224229 otelHook := zerowrapotel.NewHookWithProvider(telProvider.LogProvider, "gordon")
@@ -1510,7 +1515,10 @@ func registerAuthRoutes(
15101515 authMiddlewares = append(authMiddlewares, cidrAllowlistMiddleware)
15111516 }
15121517 authMiddlewares = append(authMiddlewares, rateLimitMiddleware)
1513- authWithMiddleware := middleware.Chain(authMiddlewares...)(svc.authHandler)
1518+ authWithMiddleware := otelhttp.NewHandler(
1519+ middleware.Chain(authMiddlewares...)(svc.authHandler),
1520+ "gordon.auth",
1521+ )
15141522 registryMux.Handle("/auth/", authWithMiddleware)
15151523 }
15161524
@@ -1555,7 +1563,10 @@ func registerAdminRoutes(registryMux, proxyMux *http.ServeMux, svc *services, cf
15551563 })
15561564 }
15571565
1558- adminWithMiddleware := middleware.Chain(adminMiddlewares...)(svc.adminHandler)
1566+ adminWithMiddleware := otelhttp.NewHandler(
1567+ middleware.Chain(adminMiddlewares...)(svc.adminHandler),
1568+ "gordon.admin",
1569+ )
15591570 registryMux.Handle("/admin/", loopbackOnly(adminWithMiddleware, log))
15601571 proxyMux.Handle("/admin/", adminHostOnly(adminWithMiddleware, cfg.Server.RegistryDomain, log))
15611572 }
@@ -217,8 +217,13 @@ func Run(ctx context.Context, configPath string) error {
217 if err != nil {217 if err != nil {
218 log.Warn().Err(err).Msg("failed to initialize telemetry, continuing without it")218 log.Warn().Err(err).Msg("failed to initialize telemetry, continuing without it")
219 } else {219 } else {
220- defer telShutdown(ctx)220+ // Use a fresh context for shutdown so a canceled app ctx doesn't prevent flushing.
221- if cfg.Telemetry.Enabled {221+ defer func() {
222+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
223+ defer cancel()
224+ telShutdown(shutdownCtx)
225+ }()
226+ if cfg.Telemetry.Enabled && cfg.Telemetry.Endpoint != "" {
222 // Bridge zerowrap logs to OTel if log export is enabled227 // Bridge zerowrap logs to OTel if log export is enabled
223 if cfg.Telemetry.Logs && telProvider.LogProvider != nil {228 if cfg.Telemetry.Logs && telProvider.LogProvider != nil {
224 otelHook := zerowrapotel.NewHookWithProvider(telProvider.LogProvider, "gordon")229 otelHook := zerowrapotel.NewHookWithProvider(telProvider.LogProvider, "gordon")
@@ -1510,7 +1515,10 @@ func registerAuthRoutes(
1510 authMiddlewares = append(authMiddlewares, cidrAllowlistMiddleware)1515 authMiddlewares = append(authMiddlewares, cidrAllowlistMiddleware)
1511 }1516 }
1512 authMiddlewares = append(authMiddlewares, rateLimitMiddleware)1517 authMiddlewares = append(authMiddlewares, rateLimitMiddleware)
1513- authWithMiddleware := middleware.Chain(authMiddlewares...)(svc.authHandler)1518+ authWithMiddleware := otelhttp.NewHandler(
1519+ middleware.Chain(authMiddlewares...)(svc.authHandler),
1520+ "gordon.auth",
1521+ )
1514 registryMux.Handle("/auth/", authWithMiddleware)1522 registryMux.Handle("/auth/", authWithMiddleware)
1515 }1523 }
1516 1524
@@ -1555,7 +1563,10 @@ func registerAdminRoutes(registryMux, proxyMux *http.ServeMux, svc *services, cf
1555 })1563 })
1556 }1564 }
1557 1565
1558- adminWithMiddleware := middleware.Chain(adminMiddlewares...)(svc.adminHandler)1566+ adminWithMiddleware := otelhttp.NewHandler(
1567+ middleware.Chain(adminMiddlewares...)(svc.adminHandler),
1568+ "gordon.admin",
1569+ )
1559 registryMux.Handle("/admin/", loopbackOnly(adminWithMiddleware, log))1570 registryMux.Handle("/admin/", loopbackOnly(adminWithMiddleware, log))
1560 proxyMux.Handle("/admin/", adminHostOnly(adminWithMiddleware, cfg.Server.RegistryDomain, log))1571 proxyMux.Handle("/admin/", adminHostOnly(adminWithMiddleware, cfg.Server.RegistryDomain, log))
1561 }1572 }
M pkg/version/version.go +1 -1
@@ -1,5 +1,5 @@
11 // Package version holds build-time version info for Gordon.
2-// Set via main using Set(), read from anywhere via Get().
2+// Set via main using Set(), read from anywhere via Version(), Commit(), or BuildDate().
33 package version
44
55 // Build information, populated by Set() at startup.
@@ -1,5 +1,5 @@
1 // Package version holds build-time version info for Gordon.1 // Package version holds build-time version info for Gordon.
2-// Set via main using Set(), read from anywhere via Get().2+// Set via main using Set(), read from anywhere via Version(), Commit(), or BuildDate().
3 package version3 package version
4 4
5 // Build information, populated by Set() at startup.5 // Build information, populated by Set() at startup.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close