diff options
| author | Anhgelus Morhtuuzh <william@herges.fr> | 2025-12-26 21:55:57 +0100 |
|---|---|---|
| committer | Anhgelus Morhtuuzh <william@herges.fr> | 2025-12-26 21:55:57 +0100 |
| commit | af11793ca48244eafd7dcdf66ac1dff83995a775 (patch) | |
| tree | 9fab2b55b9d2ec85e6fdfd683759c84a178fa895 | |
| parent | 89875be4a5cea6f7b054483417cb6707bb9fc93d (diff) | |
feat(backend): use custom logger
| -rw-r--r-- | backend/admin.go | 3 | ||||
| -rw-r--r-- | backend/data.go | 8 | ||||
| -rw-r--r-- | backend/logger.go | 78 | ||||
| -rw-r--r-- | backend/router.go | 57 | ||||
| -rw-r--r-- | backend/section.go | 2 | ||||
| -rw-r--r-- | go.mod | 1 | ||||
| -rw-r--r-- | go.sum | 2 |
7 files changed, 96 insertions, 55 deletions
diff --git a/backend/admin.go b/backend/admin.go index 087a3ea..9cb5376 100644 --- a/backend/admin.go +++ b/backend/admin.go @@ -1,7 +1,6 @@ package backend import ( - "log/slog" "net/http" "strconv" @@ -33,7 +32,7 @@ func HandleAdmin(r *chi.Mux) { if rawPage != "" { page, err = strconv.Atoi(rawPage) if err != nil || page < 1 { - slog.Warn("invalid page number", "rawPage", rawPage) + GetLogger(ctx).Warn("invalid page number", "requested", rawPage) http.Error(w, "Bad request", http.StatusBadRequest) return } diff --git a/backend/data.go b/backend/data.go index 76c2ccc..3a8acf0 100644 --- a/backend/data.go +++ b/backend/data.go @@ -8,7 +8,6 @@ import ( "html/template" "io" "io/fs" - "log/slog" "math/rand" "net/http" "regexp" @@ -179,18 +178,19 @@ func getAsset(ctx context.Context, path string) *assetData { return asset } asset = &assetData{} + logger := GetLogger(ctx) var b []byte if regexIsHttp.MatchString(path) { asset.Src = path resp, err := http.Get(path) if err != nil { - slog.Warn("get remote asset", "error", err) + logger.Warn("get remote asset", "error", err) return asset } defer resp.Body.Close() b, err = io.ReadAll(resp.Body) if err != nil { - slog.Warn("read remote asset", "error", err) + logger.Warn("read remote asset", "error", err) return asset } } else { @@ -199,7 +199,7 @@ func getAsset(ctx context.Context, path string) *assetData { var err error b, err = fs.ReadFile(aFS, path) if err != nil { - slog.Warn("read asset", "error", err) + logger.Warn("read asset", "error", err) return asset } } diff --git a/backend/logger.go b/backend/logger.go new file mode 100644 index 0000000..da5fadb --- /dev/null +++ b/backend/logger.go @@ -0,0 +1,78 @@ +package backend + +import ( + "context" + "log" + "log/slog" + "net/http" + "os" + "runtime/debug" +) + +const ( + loggerKey = "logger" + statusCode = "status_code" +) + +func GetLogger(ctx context.Context) *slog.Logger { + return ctx.Value(loggerKey).(*slog.Logger) +} + +type customWriter struct { + http.ResponseWriter + statusCode int +} + +func (c *customWriter) WriteHeader(statusCode int) { + c.statusCode = statusCode + if statusCode != c.statusCode { + c.ResponseWriter.WriteHeader(statusCode) + } +} + +func GetStatusCode(ctx context.Context) func() int { + return ctx.Value(statusCode).(func() int) +} + +func SetLogger(l *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 := l.With("uri", r.RequestURI, "method", r.Method) + ww := &customWriter{ResponseWriter: w, statusCode: http.StatusOK} + ctx := context.WithValue(r.Context(), statusCode, func() int { + return ww.statusCode + }) + ctx = context.WithValue(ctx, loggerKey, logger) + defer func(logger *slog.Logger) { + rec := recover() + if rec == nil { + return + } + if rec == http.ErrAbortHandler { + panic(rec) + } + logger.Error("crashed, recovered", "error", rec, "status", http.StatusInternalServerError) + log.New(os.Stderr, "", 0).Printf("%s\n", debug.Stack()) + http.Error(ww, "internal error", http.StatusInternalServerError) + }(logger) + + next.ServeHTTP(ww, r.WithContext(ctx)) + + if ww.statusCode == http.StatusNotFound { + return + } + var lvl slog.Level + switch { + case ww.statusCode >= 500: + lvl = slog.LevelError + case ww.statusCode >= 400: + lvl = slog.LevelWarn + case ww.statusCode >= 300: + return + default: + lvl = slog.LevelInfo + } + logger.Log(context.Background(), lvl, "handled", "status", ww.statusCode) + }) + } +} diff --git a/backend/router.go b/backend/router.go index df5fb75..c742006 100644 --- a/backend/router.go +++ b/backend/router.go @@ -17,7 +17,6 @@ import ( "git.anhgelus.world/anhgelus/small-web/backend/storage" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/httplog/v3" ) const ( @@ -32,16 +31,13 @@ const ( var templates embed.FS func SetupLogger(debug bool) { - logFormat := httplog.SchemaECS.Concise(!debug) - logLevel := slog.LevelInfo if debug { logLevel = slog.LevelDebug } logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - ReplaceAttr: logFormat.ReplaceAttr, - Level: logLevel, + Level: logLevel, })).With( slog.String("app", "anhgelus/small-web"), slog.String("version", Version), @@ -50,37 +46,11 @@ func SetupLogger(debug bool) { slog.SetDefault(logger) } -type customWriter struct { - http.ResponseWriter - statusCode int -} - -func (c *customWriter) WriteHeader(statusCode int) { - c.statusCode = statusCode - c.ResponseWriter.WriteHeader(statusCode) -} - func NewRouter(debug bool, cfg *Config, db *sql.DB, assets fs.FS) *chi.Mux { r := chi.NewRouter() - logLevel := slog.LevelWarn - if debug { - logLevel = slog.LevelDebug - } - r.Use(middleware.Timeout(30 * time.Second)) - r.Use(httplog.RequestLogger(slog.Default(), &httplog.Options{ - Level: logLevel, - // Set log output to Elastic Common Schema (ECS) format. - Schema: httplog.SchemaECS.Concise(!debug), - RecoverPanics: true, - Skip: func(req *http.Request, respStatus int) bool { - return respStatus == http.StatusNotFound || respStatus == http.StatusMethodNotAllowed - }, - // Optionally, log selected request/response headers explicitly. - LogRequestHeaders: []string{"Origin"}, - LogResponseHeaders: []string{}, - })) + r.Use(SetLogger(slog.Default())) // security headers r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -100,12 +70,13 @@ func NewRouter(debug bool, cfg *Config, db *sql.DB, assets fs.FS) *chi.Mux { next.ServeHTTP(w, r) }) }) + // context setContext := func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, configKey, cfg) ctx = context.WithValue(ctx, assetsFSKey, assets) ctx = context.WithValue(ctx, debugKey, debug) return context.WithValue(ctx, storage.DBKey, db) - } // context + } r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r.WithContext( @@ -130,24 +101,20 @@ func NewRouter(debug bool, cfg *Config, db *sql.DB, assets fs.FS) *chi.Mux { }) r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ww := &customWriter{ResponseWriter: w} - next.ServeHTTP(ww, r) - cfg := r.Context().Value(configKey).(*Config) - go func(cfg *Config, w *customWriter, r *http.Request) { - // because Go automatically adds http.StatusOK when not set - if w.statusCode == 0 { - w.statusCode = http.StatusOK - } - if w.statusCode >= 299 { - slog.Debug("not updating stats for status code above 299", "code", w.statusCode) + next.ServeHTTP(w, r) + go func(ctx context.Context, r *http.Request) { + statusCode := GetStatusCode(ctx)() + logger := GetLogger(ctx) + if statusCode >= 299 { + logger.Debug("not updating stats for status code above 299", "status", statusCode) return } ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() if err := storage.UpdateStats(setContext(ctx), r, cfg.Domain); err != nil { - slog.Error("updating stats", "error", err) + logger.Error("updating stats", "error", err) } - }(cfg, ww, r) + }(r.Context(), r) }) }) diff --git a/backend/section.go b/backend/section.go index 1b5ab9f..5bbf558 100644 --- a/backend/section.go +++ b/backend/section.go @@ -269,7 +269,7 @@ func (s *Section) handlePagination(w http.ResponseWriter, r *http.Request, maxLo var err error page, err = strconv.Atoi(rawPage) if err != nil || page < 1 { - slog.Warn("invalid page number", "rawPage", rawPage) + GetLogger(r.Context()).Warn("invalid page number", "requested", rawPage) http.Error(w, "Bad request", http.StatusBadRequest) return nil } @@ -4,7 +4,6 @@ go 1.25.1 require ( github.com/go-chi/chi/v5 v5.2.3 - github.com/go-chi/httplog/v3 v3.2.2 github.com/joho/godotenv v1.5.1 github.com/pelletier/go-toml/v2 v2.2.4 ) @@ -1,7 +1,5 @@ github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-chi/httplog/v3 v3.2.2 h1:G0oYv3YYcikNjijArHFUlqfR78cQNh9fGT43i6StqVc= -github.com/go-chi/httplog/v3 v3.2.2/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= |
