| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- // Package observability is the shared logging + metrics + OTel stub.
- //
- // M0: slog (stdlib structured logging, JSON output) and Prometheus
- // metrics via promhttp. OTel is a stub — we wire it in M9.
- //
- // All services should call Init() in main and then use the returned
- // logger and the package-level metrics.
- package observability
- import (
- "context"
- "log/slog"
- "net/http"
- "os"
- "strings"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- )
- // Init returns a slog.Logger configured for the env. JSON in non-dev
- // for log shippers, text in dev for human eyes.
- func Init(env, level, serviceName string) *slog.Logger {
- lvl := parseLevel(level)
- opts := &slog.HandlerOptions{Level: lvl}
- var h slog.Handler
- if env == "dev" {
- h = slog.NewTextHandler(os.Stdout, opts)
- } else {
- h = slog.NewJSONHandler(os.Stdout, opts)
- }
- logger := slog.New(h).With(
- slog.String("service", serviceName),
- slog.String("env", env),
- )
- slog.SetDefault(logger)
- return logger
- }
- func parseLevel(s string) slog.Level {
- switch strings.ToLower(s) {
- case "debug":
- return slog.LevelDebug
- case "warn":
- return slog.LevelWarn
- case "error":
- return slog.LevelError
- default:
- return slog.LevelInfo
- }
- }
- // MetricsHandler returns an http.Handler that serves Prometheus
- // metrics from the given registry.
- func MetricsHandler(reg *prometheus.Registry) http.Handler {
- return promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})
- }
- // HealthHandler is the standard /health endpoint. Liveness only —
- // readiness (can serve traffic) is a separate concern, added per
- // service as it comes online.
- func HealthHandler(serviceName string) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{"status":"ok","service":"` + serviceName + `"}`))
- })
- }
- // CtxWithTrace is a stub for M9 when OTel is wired in. For now
- // it just attaches the request id (or generates one) so logs can
- // be correlated.
- func CtxWithTrace(ctx context.Context, traceID string) context.Context {
- if traceID == "" {
- // tiny random — not crypto-strong, but unique enough
- traceID = newTraceID()
- }
- return context.WithValue(ctx, traceKey{}, traceID)
- }
- // TraceFromCtx returns the trace id, or "" if none.
- func TraceFromCtx(ctx context.Context) string {
- v, _ := ctx.Value(traceKey{}).(string)
- return v
- }
- type traceKey struct{}
- func newTraceID() string {
- b := make([]byte, 8)
- if _, err := readRandom(b); err != nil {
- return "0"
- }
- return hexEncode(b)
- }
|