middleware/http/middleware.go

// Package http provides net/http middleware that injects a logger into the
// request context and emits an access log entry. Intended for Lambda-behind-
// ALB and for local development of handlers that also run outside Lambda.
//
// See mercemay.top/src/lambdalog/middleware/http/.
package http

import (
	"context"
	"net/http"
	"sync/atomic"
)

// Logger is the narrow interface consumed by the middleware. It mirrors the
// other adapters so user code can share a single logger shim.
type Logger interface {
	Info(msg string, fields ...Field)
	Error(msg string, fields ...Field)
	With(fields ...Field) Logger
}

// Field mirrors encoder.Field.
type Field struct {
	Key   string
	Value any
}

type ctxKey struct{}

var loggerKey ctxKey

// WithLogger attaches l to ctx. Handlers downstream can read it with
// LoggerFrom.
func WithLogger(ctx context.Context, l Logger) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, loggerKey, l)
}

// LoggerFrom returns the logger attached by WithLogger, or fallback if none
// is attached.
func LoggerFrom(ctx context.Context, fallback Logger) Logger {
	if ctx == nil {
		return fallback
	}
	if l, ok := ctx.Value(loggerKey).(Logger); ok && l != nil {
		return l
	}
	return fallback
}

// Chain composes multiple middleware. The returned function wraps the final
// handler with the first element of the chain outermost.
func Chain(mws ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
	return func(h http.Handler) http.Handler {
		for i := len(mws) - 1; i >= 0; i-- {
			h = mws[i](h)
		}
		return h
	}
}

// Counter is a simple atomic counter used by middleware for per-route tallies.
type Counter struct{ v atomic.Int64 }

// Inc increments the counter.
func (c *Counter) Inc() { c.v.Add(1) }

// Load returns the current value.
func (c *Counter) Load() int64 { return c.v.Load() }

// Reset sets the counter back to zero.
func (c *Counter) Reset() { c.v.Store(0) }

// InjectLogger returns a middleware that attaches l to each request.
func InjectLogger(l Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			r = r.WithContext(WithLogger(r.Context(), l))
			next.ServeHTTP(w, r)
		})
	}
}