Last active
September 23, 2019 10:38
-
-
Save kokizzu/369eb0c73c79313bbce66d675228b718 to your computer and use it in GitHub Desktop.
Graceful shutdown from https://marcofranssen.nl/improved-graceful-shutdown-webserver/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// old version: https://marcofranssen.nl/go-webserver-with-gracefull-shutdown/ | |
package main | |
import ( | |
"context" | |
"log" | |
"net/http" | |
"os" | |
"os/signal" | |
"strings" | |
"time" | |
"github.com/go-chi/chi" | |
"github.com/go-chi/chi/middleware" | |
"go.uber.org/zap" | |
"go.uber.org/zap/zapcore" | |
) | |
// Server provides an http.Server | |
type Server struct { | |
l *zap.Logger | |
*http.Server | |
} | |
// NewServer creates and configures a server serving all application routes. | |
// | |
// The server implements a gracefull shutdown and utilizes zap.Logger for logging purposes. | |
// chi.Mux is used for registering some convenient middlewares and easy configuration of | |
// routes using different http verbs. | |
func NewServer(listenAddr string) (*Server, error) { | |
logger, err := zap.NewDevelopment(zap.AddStacktrace(zapcore.FatalLevel)) | |
if err != nil { | |
log.Fatalf("Can't initialize zap logger: %v", err) | |
} | |
defer logger.Sync() | |
logger.Info("Configure API") | |
api := newAPI(logger) | |
errorLog, _ := zap.NewStdLogAt(logger, zap.ErrorLevel) | |
srv := http.Server{ | |
Addr: listenAddr, | |
Handler: api, | |
ErrorLog: errorLog, | |
ReadTimeout: 5 * time.Second, | |
WriteTimeout: 10 * time.Second, | |
IdleTimeout: 15 * time.Second, | |
} | |
return &Server{logger, &srv}, nil | |
} | |
// Start runs ListenAndServe on the http.Server with graceful shutdown | |
func (srv *Server) Start() { | |
srv.l.Info("Starting server...") | |
defer srv.l.Sync() | |
go func() { | |
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { | |
srv.l.Fatal("Could not listen on", zap.String("addr", srv.Addr), zap.Error(err)) | |
} | |
}() | |
srv.l.Info("Server is ready to handle requests", zap.String("addr", srv.Addr)) | |
srv.gracefullShutdown() | |
} | |
func newAPI(logger *zap.Logger) *chi.Mux { | |
r := chi.NewRouter() | |
r.Use(middleware.RequestID) | |
r.Use(zapLogger(logger)) | |
r.Use(middleware.Recoverer) | |
r.Get("/", func(w http.ResponseWriter, r *http.Request) { | |
w.WriteHeader(http.StatusOK) | |
}) | |
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) { | |
w.Write([]byte("pong")) | |
}) | |
// register more routes over here... | |
logRoutes(r, logger) | |
return r | |
} | |
func zapLogger(l *zap.Logger) func(next http.Handler) http.Handler { | |
return func(next http.Handler) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) | |
t1 := time.Now() | |
defer func() { | |
l.Info("Served", | |
zap.String("proto", r.Proto), | |
zap.String("path", r.URL.Path), | |
zap.Duration("lat", time.Since(t1)), | |
zap.Int("status", ww.Status()), | |
zap.Int("size", ww.BytesWritten()), | |
zap.String("reqId", middleware.GetReqID(r.Context())), | |
) | |
}() | |
next.ServeHTTP(ww, r) | |
}) | |
} | |
} | |
func logRoutes(r *chi.Mux, logger *zap.Logger) { | |
if err := chi.Walk(r, zapPrintRoute(logger)); err != nil { | |
logger.Error("Failed to walk routes:", zap.Error(err)) | |
} | |
} | |
func zapPrintRoute(logger *zap.Logger) chi.WalkFunc { | |
return func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { | |
route = strings.Replace(route, "/*/", "/", -1) | |
logger.Debug("Registering route", zap.String("method", method), zap.String("route", route)) | |
return nil | |
} | |
} | |
func (srv *Server) gracefullShutdown() { | |
quit := make(chan os.Signal, 1) | |
signal.Notify(quit, os.Interrupt) | |
sig := <-quit | |
srv.l.Info("Server is shutting down", zap.String("reason", sig.String())) | |
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | |
defer cancel() | |
srv.SetKeepAlivesEnabled(false) | |
if err := srv.Shutdown(ctx); err != nil { | |
srv.l.Fatal("Could not gracefully shutdown the server", zap.Error(err)) | |
} | |
srv.l.Info("Server stopped") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment