diff --git a/handlers/Index.go b/handlers/Index.go index c96719d..ed1eb59 100644 --- a/handlers/Index.go +++ b/handlers/Index.go @@ -84,7 +84,6 @@ func (h *IndexHandler) Get(w http.ResponseWriter, r *http.Request) { } func (h *IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.Log.Info("request", slog.String("method", r.Method), slog.String("path", r.URL.Path)) w.Header().Set("Content-Type", "text/html; charset=utf-8") if r.Method == http.MethodPost { // todo: handle POST diff --git a/main.go b/main.go index 4e2d58b..cabaa1e 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/mux" "github.com/joho/godotenv" "jirku.sk/mcmamina/handlers" + "jirku.sk/mcmamina/pkg/middleware" "jirku.sk/mcmamina/services" ) @@ -26,7 +27,7 @@ const ( var distFS embed.FS func main() { - log := slog.Default() + log := slog.New(slog.NewJSONHandler(os.Stdout, nil)) err := godotenv.Load() if err != nil { log.Error("Error loading .env file", slog.Any("error", err)) @@ -44,6 +45,7 @@ type configuration struct { func setupWebserver(log *slog.Logger, calendarService *services.CalendarService) { config := configuration{} router := mux.NewRouter() + flag.StringVar(&config.publicPath, "public", "", "Usage description of the flag") flag.StringVar(&config.host, "host", "0.0.0.0", "specify the app host") flag.IntVar(&config.port, "port", 8080, "specfiy the port application will listen") @@ -69,8 +71,13 @@ func setupWebserver(log *slog.Logger, calendarService *services.CalendarService) workingFolder = os.DirFS(config.publicPath) } - cssService := services.NewCSS(workingFolder, log) + cssService := services.NewCSS(workingFolder, serviceLog(log, "css")) sponsorService := services.NewSponsorService() + + // Logger middleware + router.Use(middleware.Recover(middlwareLog(log, "recover"))) + router.Use(middleware.Logger(middlwareLog(log, "logger"))) + router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) @@ -132,3 +139,11 @@ func getContentType(filePath string) string { func validatePort(port int) bool { return port > 0 && port <= 65535 } + +func middlwareLog(log *slog.Logger, name string) *slog.Logger { + return log.With(slog.String("type", "middleware"), slog.String("name", name)) +} + +func serviceLog(log *slog.Logger, name string) *slog.Logger { + return log.With(slog.String("type", "service"), slog.String("name", name)) +} diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go new file mode 100644 index 0000000..9e1a70a --- /dev/null +++ b/pkg/middleware/logger.go @@ -0,0 +1,20 @@ +package middleware + +import ( + "log/slog" + "net/http" +) + +func Logger(logger *slog.Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.Info("request", + slog.String("method", r.Method), + slog.String("path", r.URL.Path), + slog.String("referer", r.Referer()), + slog.String("user-agent", r.UserAgent()), + ) + next.ServeHTTP(w, r) + }) + } +} diff --git a/pkg/middleware/recover.go b/pkg/middleware/recover.go new file mode 100644 index 0000000..d83db12 --- /dev/null +++ b/pkg/middleware/recover.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "log/slog" + "net/http" + "runtime/debug" +) + +func Recover(logger *slog.Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + logger.Error("Recovered from panic", slog.Any("error", err), slog.Any("stack", debug.Stack())) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + }() + next.ServeHTTP(w, r) + }) + } +}