logger.go
3.27 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
package middleware
import (
"bytes"
"context"
"log"
"net/http"
"os"
"time"
)
var (
LogEntryCtxKey = &contextKey{"LogEntry"}
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags)})
)
// Logger is a middleware that logs the start and end of each request, along
// with some useful data about what was requested, what the response status was,
// and how long it took to return. When standard output is a TTY, Logger will
// print in color, otherwise it will print in black and white. Logger prints a
// request ID if one is provided.
//
// Alternatively, look at https://github.com/meiqia/lg and the `lg.RequestLogger`
// middleware pkg.
func Logger(next http.Handler) http.Handler {
return DefaultLogger(next)
}
func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
entry := f.NewLogEntry(r)
ww := NewWrapResponseWriter(w, r.ProtoMajor)
t1 := time.Now()
defer func() {
t2 := time.Now()
entry.Write(ww.Status(), ww.BytesWritten(), t2.Sub(t1))
}()
next.ServeHTTP(ww, WithLogEntry(r, entry))
}
return http.HandlerFunc(fn)
}
}
type LogFormatter interface {
NewLogEntry(r *http.Request) LogEntry
}
type LogEntry interface {
Write(status, bytes int, elapsed time.Duration)
Panic(v interface{}, stack []byte)
}
func GetLogEntry(r *http.Request) LogEntry {
entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)
return entry
}
func WithLogEntry(r *http.Request, entry LogEntry) *http.Request {
r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))
return r
}
type DefaultLogFormatter struct {
Logger *log.Logger
}
func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
entry := &defaultLogEntry{
DefaultLogFormatter: l,
request: r,
buf: &bytes.Buffer{},
}
reqID := GetReqID(r.Context())
if reqID != "" {
cW(entry.buf, nYellow, "[%s] ", reqID)
}
cW(entry.buf, nCyan, "\"")
cW(entry.buf, bMagenta, "%s ", r.Method)
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
cW(entry.buf, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
entry.buf.WriteString("from ")
entry.buf.WriteString(r.RemoteAddr)
entry.buf.WriteString(" - ")
return entry
}
type defaultLogEntry struct {
*DefaultLogFormatter
request *http.Request
buf *bytes.Buffer
}
func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
switch {
case status < 200:
cW(l.buf, bBlue, "%03d", status)
case status < 300:
cW(l.buf, bGreen, "%03d", status)
case status < 400:
cW(l.buf, bCyan, "%03d", status)
case status < 500:
cW(l.buf, bYellow, "%03d", status)
default:
cW(l.buf, bRed, "%03d", status)
}
cW(l.buf, bBlue, " %dB", bytes)
l.buf.WriteString(" in ")
if elapsed < 500*time.Millisecond {
cW(l.buf, nGreen, "%s", elapsed)
} else if elapsed < 5*time.Second {
cW(l.buf, nYellow, "%s", elapsed)
} else {
cW(l.buf, nRed, "%s", elapsed)
}
l.Logger.Print(l.buf.String())
}
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
cW(panicEntry.buf, bRed, "panic: %+v", v)
l.Logger.Print(panicEntry.buf.String())
l.Logger.Print(string(stack))
}