package apiserver import ( "context" "encoding/json" "net/http" "time" "github.com/google/uuid" "github.com/gorilla/mux" "github.com/sirupsen/logrus" ) const ( ctxKeyRequestID = iota ) type server struct { router *mux.Router logger *logrus.Logger } func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } func newServer() *server { s := &server{ router: mux.NewRouter(), logger: logrus.New(), } s.configureRouter() return s } func (s *server) configureRouter() { s.router.Use(s.setRequestID) s.router.Use(s.logRequest) s.router.HandleFunc("/", s.handleHome()) } func (s *server) handleHome() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) } } func (s *server) logRequest(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger := logrus.WithFields(logrus.Fields{ "remote_addr": r.RemoteAddr, "request_id": r.Context().Value(ctxKeyRequestID), }) logger.Infof("started %s %s", r.Method, r.RequestURI) start := time.Now() rw := newResponseWriter(w) rw.code = http.StatusOK next.ServeHTTP(rw, r) logger.Infof("completed with %d %s in %v", rw.code, http.StatusText(rw.code), time.Since(start)) }) } func (s *server) setRequestID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := uuid.New().String() w.Header().Set("X-Request-ID", id) next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyRequestID, id))) }) } // Helpers func (s *server) error(w http.ResponseWriter, r *http.Request, status int, err error) { s.respond(w, r, status, map[string]string{"error": err.Error()}) } func (s *server) respond(w http.ResponseWriter, r *http.Request, status int, data interface{}) { w.WriteHeader(status) if data != nil { json.NewEncoder(w).Encode(data) } }