// // Custom Structured Logger // ======================== // This example demonstrates how to use middleware.RequestLogger, // middleware.LogFormatter and middleware.LogEntry to build a structured // logger using the amazing sirupsen/logrus package as the logging // backend. // // Also: check out https://github.com/pressly/lg for an improved context // logger with support for HTTP request logging, based on the example // below. // package main import ( "fmt" "net/http" "time" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/sirupsen/logrus" ) func main() { // Setup the logger backend using sirupsen/logrus and configure // it to use a custom JSONFormatter. See the logrus docs for how to // configure the backend at github.com/sirupsen/logrus logger := logrus.New() logger.Formatter = &logrus.JSONFormatter{ // disable, as we set our own DisableTimestamp: true, } // Routes r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(NewStructuredLogger(logger)) 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") }) http.ListenAndServe(":3333", r) } // StructuredLogger is a simple, but powerful implementation of a custom structured // logger backed on logrus. I encourage users to copy it, adapt it and make it their // own. Also take a look at https://github.com/pressly/lg for a dedicated pkg based // on this work, designed for context-based http routers. func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler { return middleware.RequestLogger(&StructuredLogger{logger}) } type StructuredLogger struct { Logger *logrus.Logger } func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)} logFields := logrus.Fields{} logFields["ts"] = time.Now().UTC().Format(time.RFC1123) if reqID := middleware.GetReqID(r.Context()); reqID != "" { logFields["req_id"] = reqID } scheme := "http" if r.TLS != nil { scheme = "https" } logFields["http_scheme"] = scheme logFields["http_proto"] = r.Proto logFields["http_method"] = r.Method logFields["remote_addr"] = r.RemoteAddr logFields["user_agent"] = r.UserAgent() logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI) entry.Logger = entry.Logger.WithFields(logFields) entry.Logger.Infoln("request started") return entry } type StructuredLoggerEntry struct { Logger logrus.FieldLogger } func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) { l.Logger = l.Logger.WithFields(logrus.Fields{ "resp_status": status, "resp_bytes_length": bytes, "resp_elapsed_ms": float64(elapsed.Nanoseconds()) / 1000000.0, }) l.Logger.Infoln("request complete") } func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) { l.Logger = l.Logger.WithFields(logrus.Fields{ "stack": string(stack), "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) logrus.FieldLogger { 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.WithField(key, value) } } func LogEntrySetFields(r *http.Request, fields map[string]interface{}) { if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok { entry.Logger = entry.Logger.WithFields(fields) } }