Skip to content
Snippets Groups Projects
log.go 1.76 KiB
Newer Older
gbe's avatar
gbe committed
package log

import (
	"context"
gbe's avatar
gbe committed
	"net"
gbe's avatar
gbe committed
	"net/http"
	"time"

gbe's avatar
gbe committed
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
gbe's avatar
gbe committed
	"github.com/kjk/betterguid"
)

type contextKey string

gbe's avatar
gbe committed
// 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 {
gbe's avatar
gbe committed
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		remote := r.Header.Get("X-Forwarded-For")
		if remote == "" {
			remote = r.RemoteAddr
		}

gbe's avatar
gbe committed
		// Try to figure out a name for the remote. If there are multiple, we select the first.
		names, err := net.LookupAddr(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"}
		}

gbe's avatar
gbe committed
		l := log.With(logger,
gbe's avatar
gbe committed
			"method", r.Method,
			"url", r.URL,
			"remote", remote,
gbe's avatar
gbe committed
			"host", names[0],
			"request_id", betterguid.New())
gbe's avatar
gbe committed

		start := time.Now()
		defer func() {
			d := time.Since(start)

gbe's avatar
gbe committed
			level.Info(l).
gbe's avatar
gbe committed
				Log("duration", d, "msg", "request handled")
gbe's avatar
gbe committed
		}()

		key := contextKey("logger")
gbe's avatar
gbe committed
		ctx := context.WithValue(r.Context(), key, l)
gbe's avatar
gbe committed

		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

gbe's avatar
gbe committed
// 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.
gbe's avatar
gbe committed
func Get(r *http.Request) log.Logger {
gbe's avatar
gbe committed
	return GetContext(r.Context())
}

gbe's avatar
gbe committed
// GetContext returns the logger for the given context. If ctx has no logger, it returns
// a logger that will discard all logged events.
gbe's avatar
gbe committed
func GetContext(ctx context.Context) log.Logger {
gbe's avatar
gbe committed
	val := ctx.Value(contextKey("logger"))
	if val == nil {
		val = log.NewNopLogger()
	}

	return val.(log.Logger)
gbe's avatar
gbe committed
}