diff --git a/go.mod b/go.mod index 4a0584e..19c47fd 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/gorilla/mux v1.8.0 + github.com/justinas/alice v1.2.0 github.com/rs/zerolog v1.20.0 github.com/spf13/viper v1.7.1 ) diff --git a/go.sum b/go.sum index 096aec3..0d3f0cb 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/httpserver/handleadmin.go b/httpserver/handleadmin.go new file mode 100644 index 0000000..2b220e2 --- /dev/null +++ b/httpserver/handleadmin.go @@ -0,0 +1,12 @@ +package httpserver + +import ( + "fmt" + "net/http" +) + +func (s *server) handleLogin() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello login") + } +} diff --git a/httpserver/http.go b/httpserver/http.go index 040f87b..23ae9fe 100644 --- a/httpserver/http.go +++ b/httpserver/http.go @@ -6,8 +6,6 @@ import ( "fmt" "net/http" "time" - - "github.com/rs/zerolog/log" ) func (s *server) serveUntilShutdown() { @@ -24,9 +22,9 @@ func (s *server) serveUntilShutdown() { // this does any necessary setup in each handler s.routes() - log.Info().Str("listenaddr", listenAddr).Msg("http begin listen") + s.log.Info().Str("listenaddr", listenAddr).Msg("http begin listen") if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Error().Msgf("listen:%+s\n", err) + s.log.Error().Msgf("listen:%+s\n", err) if s.cancelFunc != nil { s.cancelFunc() } @@ -39,22 +37,21 @@ func (s *server) respondJSON(w http.ResponseWriter, r *http.Request, data interf if data != nil { err := json.NewEncoder(w).Encode(data) if err != nil { - log.Error().Err(err).Msg("json encode error") + s.log.Error().Err(err).Msg("json encode error") } } } -func (s *server) decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { +func (s *server) decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { // nolint return json.NewDecoder(r.Body).Decode(v) } func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // FIXME logging or any other general purpose middleware here s.router.ServeHTTP(w, r) } func (s *server) cleanupForExit() { - log.Info().Msg("cleaning up") + s.log.Info().Msg("cleaning up") // FIXME unimplemented // close database connections or whatever } @@ -62,11 +59,14 @@ func (s *server) cleanupForExit() { func (s *server) cleanShutdown() { // initiate clean shutdown s.exitCode = 0 - ctxShutdown, _ := context.WithTimeout(context.Background(), 5*time.Second) + ctxShutdown, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) if err := s.httpServer.Shutdown(ctxShutdown); err != nil { - log.Error(). + s.log.Error(). Err(err). Msg("server clean shutdown failed") } + if shutdownCancel != nil { + shutdownCancel() + } s.cleanupForExit() } diff --git a/httpserver/main.go b/httpserver/main.go index f05b324..5c589b5 100644 --- a/httpserver/main.go +++ b/httpserver/main.go @@ -66,12 +66,12 @@ func (s *server) serve() int { // signal watcher go func() { - c := make(chan os.Signal) + c := make(chan os.Signal, 1) signal.Ignore(syscall.SIGPIPE) signal.Notify(c, os.Interrupt, syscall.SIGTERM) // block and wait for signal sig := <-c - log.Info().Msgf("signal received: %+v", sig) + s.log.Info().Msgf("signal received: %+v", sig) if s.cancelFunc != nil { // cancelling the main context will trigger a clean // shutdown. @@ -81,15 +81,12 @@ func (s *server) serve() int { go s.serveUntilShutdown() - for { - select { - case <-s.ctx.Done(): - //aforementioned clean shutdown upon main context - //cancellation - s.cleanShutdown() - return s.exitCode - } + for range s.ctx.Done() { + //aforementioned clean shutdown upon main context + //cancellation } + s.cleanShutdown() + return s.exitCode } @@ -130,7 +127,6 @@ func (s *server) setupLogging() { zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() } - log.Logger = log.With().Caller().Logger() zerolog.SetGlobalLevel(zerolog.InfoLevel) tty := false @@ -138,11 +134,10 @@ func (s *server) setupLogging() { tty = true } - log.Info().Bool("tty", tty).Msg("tty detection") - var writers []io.Writer if tty { + // this does cool colorization for console/dev consoleWriter := zerolog.NewConsoleWriter( func(w *zerolog.ConsoleWriter) { // Customize time format @@ -152,10 +147,13 @@ func (s *server) setupLogging() { writers = append(writers, consoleWriter) } else { + // log json in prod for the machines writers = append(writers, os.Stdout) } /* + // this is how you log to a file, if you do that + // sort of thing still logfile := viper.GetString("Logfile") if logfile != "" { logfileDir := filepath.Dir(logfile) @@ -170,24 +168,24 @@ func (s *server) setupLogging() { } writers = append(writers, hp.logfh) - } */ multi := zerolog.MultiLevelWriter(writers...) logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger() - log.Logger = logger + s.log = &logger + //log.Logger = logger if viper.GetBool("debug") { zerolog.SetGlobalLevel(zerolog.DebugLevel) - log.Debug().Bool("debug", true).Send() + s.log.Debug().Bool("debug", true).Send() } s.identify() } func (s *server) identify() { - log.Info(). + s.log.Info(). Str("appname", s.appname). Str("version", s.version). Str("buildarch", s.buildarch). diff --git a/httpserver/middlewares.go b/httpserver/middlewares.go new file mode 100644 index 0000000..7ea14f3 --- /dev/null +++ b/httpserver/middlewares.go @@ -0,0 +1,75 @@ +package httpserver + +import ( + "net" + "net/http" + "time" +) + +// the following is from +// https://learning-cloud-native-go.github.io/docs/a6.adding_zerolog_logger/ + +func ipFromHostPort(hp string) string { + h, _, err := net.SplitHostPort(hp) + if err != nil { + return "" + } + if len(h) > 0 && h[0] == '[' { + return h[1 : len(h)-1] + } + return h +} + +type loggingResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { + return &loggingResponseWriter{w, http.StatusOK} +} + +func (lrw *loggingResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + lrw.ResponseWriter.WriteHeader(code) +} + +// type Middleware func(http.Handler) http.Handler +// this returns a Middleware that is designed to do every request through the +// mux, note the signature: +func (s *server) LoggingMiddleware() func(http.Handler) http.Handler { + // FIXME this should use https://github.com/google/go-cloud/blob/master/server/requestlog/requestlog.go + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + lrw := NewLoggingResponseWriter(w) + defer func() { + latency := time.Since(start) + s.log.Info(). + Time("request_start", start). + Str("method", r.Method). + Str("url", r.URL.String()). + Str("useragent", r.UserAgent()). + Str("referer", r.Referer()). + Str("proto", r.Proto). + Str("remoteIP", ipFromHostPort(r.RemoteAddr)). + Int("status", lrw.statusCode). + Dur("latency", latency). + Send() + + }() + + next.ServeHTTP(lrw, r) + + }) + } +} + +// this is the HandleFunc wrapper type for a *specific route*, it doesn't go +// on the mux. +func (s *server) AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + s.log.Info().Msg("AUTH: before request") + next(w, r) + } +} diff --git a/httpserver/routes.go b/httpserver/routes.go index b2cc2a0..e1eb736 100644 --- a/httpserver/routes.go +++ b/httpserver/routes.go @@ -1,9 +1,15 @@ package httpserver -import "github.com/gorilla/mux" +import ( + "github.com/gorilla/mux" +) func (s *server) routes() { s.router = mux.NewRouter() + s.router.HandleFunc("/", s.handleIndex()).Methods("GET") + s.router.HandleFunc("/login", s.AuthMiddleware(s.handleLogin())).Methods("GET") s.router.HandleFunc("/.well-known/healthcheck.json", s.handleHealthCheck()).Methods("GET") + + s.router.Use(s.LoggingMiddleware()) }