forked from thoas/bokchoy
/
logger.go
150 lines (121 loc) · 4.36 KB
/
logger.go
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
142
143
144
145
146
147
148
149
150
package middleware
import (
"bytes"
"context"
"log"
"os"
"time"
"github.com/thoas/bokchoy"
)
var (
// LogEntryCtxKey is the context.Context key to store the request log entry.
LogEntryCtxKey = &contextKey{"LogEntry"}
// DefaultLogger is called by the Logger middleware handler to log each request.
// Its made a package-level variable so that it can be reconfigured for custom
// logging configurations.
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
)
// RequestLogger returns a logger handler using a custom LogFormatter.
func RequestLogger(f LogFormatter) func(next bokchoy.Handler) bokchoy.Handler {
return func(next bokchoy.Handler) bokchoy.Handler {
fn := func(r *bokchoy.Request) error {
entry := f.NewLogEntry(r)
t1 := time.Now()
ctx := bokchoy.WithContextAfterRequestFunc(r.Context(), func() {
entry.Write(r, time.Since(t1))
})
r = r.WithContext(ctx)
next.Handle(WithLogEntry(r, entry))
return nil
}
return bokchoy.HandlerFunc(fn)
}
}
// LogFormatter initiates the beginning of a new LogEntry per request.
// See DefaultLogFormatter for an example implementation.
type LogFormatter interface {
NewLogEntry(r *bokchoy.Request) LogEntry
}
// LogEntry records the final log when a request completes.
// See defaultLogEntry for an example implementation.
type LogEntry interface {
Write(r *bokchoy.Request, elapsed time.Duration)
Panic(v interface{}, stack []byte)
}
// GetLogEntry returns the in-context LogEntry for a request.
func GetLogEntry(r *bokchoy.Request) LogEntry {
entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)
return entry
}
// WithLogEntry sets the in-context LogEntry for a request.
func WithLogEntry(r *bokchoy.Request, entry LogEntry) *bokchoy.Request {
r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))
return r
}
// LoggerInterface accepts printing to stdlib logger or compatible logger.
type LoggerInterface interface {
Print(v ...interface{})
}
// DefaultLogFormatter is a simple logger that implements a LogFormatter.
type DefaultLogFormatter struct {
Logger LoggerInterface
NoColor bool
}
// NewLogEntry creates a new LogEntry for the request.
func (l *DefaultLogFormatter) NewLogEntry(r *bokchoy.Request) LogEntry {
useColor := !l.NoColor
entry := &defaultLogEntry{
DefaultLogFormatter: l,
request: r,
buf: &bytes.Buffer{},
useColor: useColor,
}
reqID := GetReqID(r.Context())
if reqID != "" {
bokchoy.ColorWrite(entry.buf, useColor, bokchoy.ColorYellow, "[%s] ", reqID)
}
task := r.Task
bokchoy.ColorWrite(entry.buf, useColor, bokchoy.ColorBrightMagenta, "<Task id=%s name=%s payload=%v>", task.ID, task.Name, task.Payload)
entry.buf.WriteString(" - ")
return entry
}
type defaultLogEntry struct {
*DefaultLogFormatter
request *bokchoy.Request
buf *bytes.Buffer
useColor bool
}
func (l *defaultLogEntry) Write(r *bokchoy.Request, elapsed time.Duration) {
task := r.Task
switch {
case task.IsStatusProcessing():
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorBrightBlue, "%s", task.StatusDisplay())
case task.IsStatusSucceeded():
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorBrightGreen, "%s", task.StatusDisplay())
case task.IsStatusCanceled():
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorBrightYellow, "%s", task.StatusDisplay())
case task.IsStatusFailed():
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorBrightRed, "%s", task.StatusDisplay())
}
l.buf.WriteString(" - ")
if task.Result == nil {
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorBrightBlue, "result: (empty)")
} else {
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorBrightBlue, "result: \"%s\"", task.Result)
}
l.buf.WriteString(" in ")
if elapsed < 500*time.Millisecond {
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorGreen, "%s", elapsed)
} else if elapsed < 5*time.Second {
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorYellow, "%s", elapsed)
} else {
bokchoy.ColorWrite(l.buf, l.useColor, bokchoy.ColorRed, "%s", elapsed)
}
l.Logger.Print(l.buf.String())
}
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
bokchoy.ColorWrite(panicEntry.buf, l.useColor, bokchoy.ColorRed, "panic: %+v", v)
l.Logger.Print(panicEntry.buf.String())
l.Logger.Print(string(stack))
}