diff --git a/_examples/logging/main.go b/_examples/logging/main.go index 3d090e07..44649999 100644 --- a/_examples/logging/main.go +++ b/_examples/logging/main.go @@ -1,141 +1,9 @@ -// This example demonstrates how to use middleware.RequestLogger, -// middleware.LogFormatter and middleware.LogEntry to build a structured -// logger using the preview version of the new log/slog package as the logging -// backend. -// -// Also: check out https://github.com/goware/httplog for an improved context -// logger with support for HTTP request logging, based on the example below. package main -import ( - "fmt" - "net/http" - "os" - "time" - - "golang.org/x/exp/slog" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" -) +// Please see https://github.com/go-chi/httplog for a complete package +// and example for writing a structured logger on chi built on +// the Go 1.21+ "log/slog" package. func main() { - // Setup a JSON handler for the new log/slog library - slogJSONHandler := slog.HandlerOptions{ - // Remove default time slog.Attr, we create our own later - ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { - if a.Key == slog.TimeKey { - return slog.Attr{} - } - return a - }, - }.NewJSONHandler(os.Stdout) - - // Routes - r := chi.NewRouter() - r.Use(middleware.RequestID) - r.Use(NewStructuredLogger(slogJSONHandler)) - r.Use(middleware.Recoverer) - - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("welcome")) - }) - r.Get("/wait", func(w http.ResponseWriter, r *http.Request) { - time.Sleep(1 * time.Second) - LogEntrySetField(r, "wait", true) - w.Write([]byte("hi")) - }) - r.Get("/panic", func(w http.ResponseWriter, r *http.Request) { - panic("oops") - }) - r.Get("/add_fields", func(w http.ResponseWriter, r *http.Request) { - LogEntrySetFields(r, map[string]interface{}{"foo": "bar", "bar": "foo"}) - }) - http.ListenAndServe(":3333", r) -} - -// StructuredLogger is a simple, but powerful implementation of a custom structured -// logger backed on log/slog. I encourage users to copy it, adapt it and make it their -// own. Also take a look at https://github.com/go-chi/httplog for a dedicated pkg based -// on this work, designed for context-based http routers. - -func NewStructuredLogger(handler slog.Handler) func(next http.Handler) http.Handler { - return middleware.RequestLogger(&StructuredLogger{Logger: handler}) -} - -type StructuredLogger struct { - Logger slog.Handler -} - -func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { - var logFields []slog.Attr - logFields = append(logFields, slog.String("ts", time.Now().UTC().Format(time.RFC1123))) - - if reqID := middleware.GetReqID(r.Context()); reqID != "" { - logFields = append(logFields, slog.String("req_id", reqID)) - } - - scheme := "http" - if r.TLS != nil { - scheme = "https" - } - - handler := l.Logger.WithAttrs(append(logFields, - slog.String("http_scheme", scheme), - slog.String("http_proto", r.Proto), - slog.String("http_method", r.Method), - slog.String("remote_addr", r.RemoteAddr), - slog.String("user_agent", r.UserAgent()), - slog.String("uri", fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)))) - - entry := StructuredLoggerEntry{Logger: slog.New(handler)} - - entry.Logger.LogAttrs(slog.LevelInfo, "request started") - - return &entry -} - -type StructuredLoggerEntry struct { - Logger *slog.Logger -} - -func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) { - l.Logger.LogAttrs(slog.LevelInfo, "request complete", - slog.Int("resp_status", status), - slog.Int("resp_byte_length", bytes), - slog.Float64("resp_elapsed_ms", float64(elapsed.Nanoseconds())/1000000.0), - ) -} - -func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) { - l.Logger.LogAttrs(slog.LevelInfo, "", - slog.String("stack", string(stack)), - slog.String("panic", fmt.Sprintf("%+v", v)), - ) -} - -// Helper methods used by the application to get the request-scoped -// logger entry and set additional fields between handlers. -// -// This is a useful pattern to use to set state on the entry as it -// passes through the handler chain, which at any point can be logged -// with a call to .Print(), .Info(), etc. - -func GetLogEntry(r *http.Request) *slog.Logger { - entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry) - return entry.Logger -} - -func LogEntrySetField(r *http.Request, key string, value interface{}) { - if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok { - entry.Logger = entry.Logger.With(key, value) - } -} - -func LogEntrySetFields(r *http.Request, fields map[string]interface{}) { - if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok { - for k, v := range fields { - entry.Logger = entry.Logger.With(k, v) - } - } + // See https://github.com/go-chi/httplog/blob/master/_example/main.go }