Files
etalon 976f5238f8 feat: scaffold app skeleton with config, ULID, health endpoints, compose stack (phase 1)
- internal .env loader (real env wins) + strictly validated config struct
- ULID package: 48-bit ms timestamp + 80-bit crypto randomness, sortable
- HTTP server with recovery/request-id/trusted-proxy/access-log middleware
- /healthz, /readyz (pluggable checkers), /metricsz (counter registry)
- multi-stage Dockerfile (alpine, non-root) + compose with healthchecks,
  app bound to 127.0.0.1:8787, mongo unpublished, graceful 15s shutdown

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:10:30 +02:00

59 lines
1.4 KiB
Go

// Package metrics is a tiny in-process registry of named counters and gauges
// exposed as JSON at /metricsz. No external metrics system is required.
package metrics
import (
"sync"
"sync/atomic"
)
type Registry struct {
mu sync.RWMutex
counters map[string]*atomic.Int64
gauges map[string]*atomic.Int64
}
func NewRegistry() *Registry {
return &Registry{
counters: make(map[string]*atomic.Int64),
gauges: make(map[string]*atomic.Int64),
}
}
func (r *Registry) cell(m map[string]*atomic.Int64, name string) *atomic.Int64 {
r.mu.RLock()
c, ok := m[name]
r.mu.RUnlock()
if ok {
return c
}
r.mu.Lock()
defer r.mu.Unlock()
if c, ok = m[name]; !ok {
c = new(atomic.Int64)
m[name] = c
}
return c
}
// Inc adds delta to a monotonic counter.
func (r *Registry) Inc(name string, delta int64) { r.cell(r.counters, name).Add(delta) }
// Set assigns the current value of a gauge (e.g. queue depth).
func (r *Registry) Set(name string, value int64) { r.cell(r.gauges, name).Store(value) }
// Snapshot returns a point-in-time copy of all values for /metricsz.
func (r *Registry) Snapshot() (counters, gauges map[string]int64) {
r.mu.RLock()
defer r.mu.RUnlock()
counters = make(map[string]int64, len(r.counters))
for k, v := range r.counters {
counters[k] = v.Load()
}
gauges = make(map[string]int64, len(r.gauges))
for k, v := range r.gauges {
gauges[k] = v.Load()
}
return counters, gauges
}