package observability import ( "sync/atomic" "time" ) type Snapshot struct { GeneratedAtUTC string `json:"generated_at_utc"` UptimeSeconds int64 `json:"uptime_seconds"` TotalRequests uint64 `json:"total_requests"` ClientErrors uint64 `json:"client_errors"` ServerErrors uint64 `json:"server_errors"` ClientErrorRatePct float64 `json:"client_error_rate_pct"` ServerErrorRatePct float64 `json:"server_error_rate_pct"` AvgLatencyMs float64 `json:"avg_latency_ms"` MaxLatencyMs float64 `json:"max_latency_ms"` } type Collector struct { startedAt time.Time totalRequests uint64 clientErrors uint64 serverErrors uint64 totalLatencyNs uint64 maxLatencyNs uint64 } func NewCollector() *Collector { return &Collector{startedAt: time.Now()} } func (c *Collector) Observe(status int, latency time.Duration) { atomic.AddUint64(&c.totalRequests, 1) atomic.AddUint64(&c.totalLatencyNs, uint64(latency.Nanoseconds())) if status >= 500 { atomic.AddUint64(&c.serverErrors, 1) } else if status >= 400 { atomic.AddUint64(&c.clientErrors, 1) } latNs := uint64(latency.Nanoseconds()) for { old := atomic.LoadUint64(&c.maxLatencyNs) if latNs <= old { break } if atomic.CompareAndSwapUint64(&c.maxLatencyNs, old, latNs) { break } } } func (c *Collector) Snapshot() Snapshot { now := time.Now() total := atomic.LoadUint64(&c.totalRequests) clientErr := atomic.LoadUint64(&c.clientErrors) serverErr := atomic.LoadUint64(&c.serverErrors) totalLatencyNs := atomic.LoadUint64(&c.totalLatencyNs) maxLatencyNs := atomic.LoadUint64(&c.maxLatencyNs) var clientRate float64 var serverRate float64 var avgLatencyMs float64 if total > 0 { clientRate = float64(clientErr) * 100 / float64(total) serverRate = float64(serverErr) * 100 / float64(total) avgLatencyMs = float64(totalLatencyNs) / float64(total) / 1e6 } return Snapshot{ GeneratedAtUTC: now.UTC().Format(time.RFC3339), UptimeSeconds: int64(now.Sub(c.startedAt).Seconds()), TotalRequests: total, ClientErrors: clientErr, ServerErrors: serverErr, ClientErrorRatePct: clientRate, ServerErrorRatePct: serverRate, AvgLatencyMs: avgLatencyMs, MaxLatencyMs: float64(maxLatencyNs) / 1e6, } }