main.go
4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//
// 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/meiqia/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/Sirupsen/logrus"
"github.com/meiqia/chi"
"github.com/meiqia/chi/middleware"
)
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/meiqia/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_elasped_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)
}
}