package log import ( "context" "net" "net/http" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/kjk/betterguid" ) type contextKey string // Request wraps the given HTTP handler with a log entry that logs how long it has been running. // It makes a preconfigured logger for the request available as part of the request context. func Request(next http.Handler, logger log.Logger) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { remote := r.Header.Get("X-Forwarded-For") if remote == "" { remote = r.RemoteAddr } // Try to figure out a name for the remote. If there are multiple, we select the first. names, err := net.DefaultResolver.LookupAddr(r.Context(), remote) if err != nil { // Can't resolve name, but that's ok. There might not be a PTR record for the remote. names = []string{"n/a"} } l := log.With(logger, "method", r.Method, "url", r.URL, "remote", remote, "host", names[0], "request_id", betterguid.New()) start := time.Now() defer func() { d := time.Since(start) _ = level.Info(l). Log("duration", d, "msg", "request handled") }() key := contextKey("logger") ctx := context.WithValue(r.Context(), key, l) next.ServeHTTP(w, r.WithContext(ctx)) }) } // Get returns the logger for the given HTTP request. It will return a logger that discards all writes // if r does not have a logger in its context. func Get(r *http.Request) log.Logger { return GetContext(r.Context()) } // GetContext returns the logger for the given context. If ctx has no logger, it returns // a logger that will discard all logged events. func GetContext(ctx context.Context) log.Logger { val := ctx.Value(contextKey("logger")) if val == nil { val = log.NewNopLogger() } return val.(log.Logger) }