1 package middleware
2
3 import (
4 "bytes"
5 "context"
6 "log"
7 "net/http"
8 "os"
9 "time"
10 )
11
12 var (
13
14 LogEntryCtxKey = &contextKey{"LogEntry"}
15
16
17
18
19 DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
20 )
21
22
23
24
25
26
27
28
29
30 func Logger(next http.Handler) http.Handler {
31 return DefaultLogger(next)
32 }
33
34
35 func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {
36 return func(next http.Handler) http.Handler {
37 fn := func(w http.ResponseWriter, r *http.Request) {
38 entry := f.NewLogEntry(r)
39 ww := NewWrapResponseWriter(w, r.ProtoMajor)
40
41 t1 := time.Now()
42 defer func() {
43 entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil)
44 }()
45
46 next.ServeHTTP(ww, WithLogEntry(r, entry))
47 }
48 return http.HandlerFunc(fn)
49 }
50 }
51
52
53
54 type LogFormatter interface {
55 NewLogEntry(r *http.Request) LogEntry
56 }
57
58
59
60 type LogEntry interface {
61 Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{})
62 Panic(v interface{}, stack []byte)
63 }
64
65
66 func GetLogEntry(r *http.Request) LogEntry {
67 entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)
68 return entry
69 }
70
71
72 func WithLogEntry(r *http.Request, entry LogEntry) *http.Request {
73 r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))
74 return r
75 }
76
77
78 type LoggerInterface interface {
79 Print(v ...interface{})
80 }
81
82
83 type DefaultLogFormatter struct {
84 Logger LoggerInterface
85 NoColor bool
86 }
87
88
89 func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
90 useColor := !l.NoColor
91 entry := &defaultLogEntry{
92 DefaultLogFormatter: l,
93 request: r,
94 buf: &bytes.Buffer{},
95 useColor: useColor,
96 }
97
98 reqID := GetReqID(r.Context())
99 if reqID != "" {
100 cW(entry.buf, useColor, nYellow, "[%s] ", reqID)
101 }
102 cW(entry.buf, useColor, nCyan, "\"")
103 cW(entry.buf, useColor, bMagenta, "%s ", r.Method)
104
105 scheme := "http"
106 if r.TLS != nil {
107 scheme = "https"
108 }
109 cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
110
111 entry.buf.WriteString("from ")
112 entry.buf.WriteString(r.RemoteAddr)
113 entry.buf.WriteString(" - ")
114
115 return entry
116 }
117
118 type defaultLogEntry struct {
119 *DefaultLogFormatter
120 request *http.Request
121 buf *bytes.Buffer
122 useColor bool
123 }
124
125 func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
126 switch {
127 case status < 200:
128 cW(l.buf, l.useColor, bBlue, "%03d", status)
129 case status < 300:
130 cW(l.buf, l.useColor, bGreen, "%03d", status)
131 case status < 400:
132 cW(l.buf, l.useColor, bCyan, "%03d", status)
133 case status < 500:
134 cW(l.buf, l.useColor, bYellow, "%03d", status)
135 default:
136 cW(l.buf, l.useColor, bRed, "%03d", status)
137 }
138
139 cW(l.buf, l.useColor, bBlue, " %dB", bytes)
140
141 l.buf.WriteString(" in ")
142 if elapsed < 500*time.Millisecond {
143 cW(l.buf, l.useColor, nGreen, "%s", elapsed)
144 } else if elapsed < 5*time.Second {
145 cW(l.buf, l.useColor, nYellow, "%s", elapsed)
146 } else {
147 cW(l.buf, l.useColor, nRed, "%s", elapsed)
148 }
149
150 l.Logger.Print(l.buf.String())
151 }
152
153 func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
154 PrintPrettyStack(v)
155 }
156
View as plain text