-
-
Notifications
You must be signed in to change notification settings - Fork 990
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update _examples/logging source to point at go-chi/httplog
- Loading branch information
Showing
1 changed file
with
4 additions
and
136 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |