1 package reqlog
2
3 import (
4 "net/http"
5 "sync"
6 "time"
7
8 "github.com/sirupsen/logrus"
9 "github.com/urfave/negroni"
10
11 "github.com/ory/x/logrusx"
12 )
13
14 type timer interface {
15 Now() time.Time
16 Since(time.Time) time.Duration
17 }
18
19 type realClock struct{}
20
21 func (rc *realClock) Now() time.Time {
22 return time.Now()
23 }
24
25 func (rc *realClock) Since(t time.Time) time.Duration {
26 return time.Since(t)
27 }
28
29
30 type Middleware struct {
31
32 Logger *logrusx.Logger
33
34 Name string
35 Before func(*logrusx.Logger, *http.Request, string) *logrusx.Logger
36 After func(*logrusx.Logger, *http.Request, negroni.ResponseWriter, time.Duration, string) *logrusx.Logger
37
38 logStarting bool
39
40 clock timer
41
42 logLevel logrus.Level
43
44
45 silencePaths map[string]bool
46
47 sync.RWMutex
48 }
49
50
51 func NewMiddleware() *Middleware {
52 return NewCustomMiddleware(logrus.InfoLevel, &logrus.TextFormatter{}, "web")
53 }
54
55
56 func NewCustomMiddleware(level logrus.Level, formatter logrus.Formatter, name string) *Middleware {
57 log := logrusx.New(name, "", logrusx.ForceFormatter(formatter), logrusx.ForceLevel(level))
58 return &Middleware{
59 Logger: log,
60 Name: name,
61 Before: DefaultBefore,
62 After: DefaultAfter,
63
64 logLevel: logrus.InfoLevel,
65 logStarting: true,
66 clock: &realClock{},
67 silencePaths: map[string]bool{},
68 }
69 }
70
71
72 func NewMiddlewareFromLogger(logger *logrusx.Logger, name string) *Middleware {
73 return &Middleware{
74 Logger: logger,
75 Name: name,
76 Before: DefaultBefore,
77 After: DefaultAfter,
78
79 logLevel: logrus.InfoLevel,
80 logStarting: true,
81 clock: &realClock{},
82 silencePaths: map[string]bool{},
83 }
84 }
85
86
87
88 func (m *Middleware) SetLogStarting(v bool) {
89 m.logStarting = v
90 }
91
92
93 func (m *Middleware) ExcludePaths(paths ...string) *Middleware {
94 for _, path := range paths {
95 m.Lock()
96 m.silencePaths[path] = true
97 m.Unlock()
98 }
99 return m
100 }
101
102 func (m *Middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
103 if m.Before == nil {
104 m.Before = DefaultBefore
105 }
106
107 if m.After == nil {
108 m.After = DefaultAfter
109 }
110
111 logLevel := m.logLevel
112 m.RLock()
113 if _, ok := m.silencePaths[r.URL.Path]; ok {
114 logLevel = logrus.TraceLevel
115 }
116 m.RUnlock()
117
118 start := m.clock.Now()
119
120
121 remoteAddr := r.RemoteAddr
122 if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
123 remoteAddr = realIP
124 }
125
126 entry := m.Logger.NewEntry()
127
128 entry = m.Before(entry, r, remoteAddr)
129
130 if m.logStarting {
131 entry.Log(logLevel, "started handling request")
132 }
133
134 next(rw, r)
135
136 latency := m.clock.Since(start)
137 res := rw.(negroni.ResponseWriter)
138
139 m.After(entry, r, res, latency, m.Name).Log(logLevel, "completed handling request")
140 }
141
142
143
144 type BeforeFunc func(*logrusx.Logger, *http.Request, string) *logrusx.Logger
145
146
147
148 type AfterFunc func(*logrusx.Logger, negroni.ResponseWriter, time.Duration, string) *logrusx.Logger
149
150
151 func DefaultBefore(entry *logrusx.Logger, req *http.Request, remoteAddr string) *logrusx.Logger {
152 return entry.WithRequest(req)
153 }
154
155
156 func DefaultAfter(entry *logrusx.Logger, req *http.Request, res negroni.ResponseWriter, latency time.Duration, name string) *logrusx.Logger {
157 return entry.WithRequest(req).WithField("http_response", map[string]interface{}{
158 "status": res.Status(),
159 "text_status": http.StatusText(res.Status()),
160 "took": latency,
161 })
162 }
163
View as plain text