log.go

46 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
package router

import (
	"log"
	"net/http"
	"os"
	"time"
)

// WithLogger returns middleware that logs all HTTP requests.
// Production (ENV=production): structured JSON. Development: human-readable.
func WithLogger() func(http.Handler) http.Handler {
	production := os.Getenv("ENV") == "production"

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()
			wrapped := &logWriter{ResponseWriter: w, status: http.StatusOK}
			next.ServeHTTP(wrapped, r)
			duration := time.Since(start)

			if production {
				log.Printf(`{"method":"%s","path":"%s","status":%d,"duration_ms":%d,"ip":"%s"}`,
					r.Method, r.URL.Path, wrapped.status, duration.Milliseconds(), ClientIP(r))
			} else {
				log.Printf("%s %s %d %s", r.Method, r.URL.Path, wrapped.status, duration)
			}
		})
	}
}

type logWriter struct {
	http.ResponseWriter
	status int
}

func (w *logWriter) WriteHeader(code int) {
	w.status = code
	w.ResponseWriter.WriteHeader(code)
}

func (w *logWriter) Flush() {
	if f, ok := w.ResponseWriter.(http.Flusher); ok {
		f.Flush()
	}
}