-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathhandler.go
157 lines (133 loc) · 4.82 KB
/
handler.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
151
152
153
154
155
156
157
package log
import (
"io"
"os"
"strconv"
"strings"
"time"
)
// handler.go contains Handler interface and builtin implementations
const (
defaultTimeStampFormat = time.RFC3339
)
// Handler formats log message and writes to underlying storage, stdout, file, remote server etc.
// It MUST be thread safe because logger calls handler concurrently without any locking.
// There is NO log entry struct in gommon/log, which is used in many logging packages, the reason is
// if extra field is added to the interface, compiler would throw error on stale handler implementations.
type Handler interface {
// HandleLog requires level, now, msg, all the others are optional
// source is Caller which contains full file line TODO: pass frame instead of string so handler can use trace for error handling?
// context are fields attached to the logger instance
// fields are ad-hoc fields from logger method like DebugF(msg, fields)
HandleLog(level Level, now time.Time, msg string, source Caller, context Fields, fields Fields)
// Flush writes the buffer to underlying storage
Flush()
}
// HandlerFunc is an adapter to allow use of ordinary functions as log entry handlers
type HandlerFunc func(level Level, now time.Time, msg string, source string, context Fields, fields Fields)
// TODO: why the receiver is value instead of pointer https://github.com/dyweb/gommon/issues/30 and what's the overhead
func (f HandlerFunc) HandleLog(level Level, now time.Time, msg string, source string, context Fields, fields Fields) {
f(level, now, msg, source, context, fields)
}
var _ Syncer = (*os.File)(nil)
// Syncer is implemented by os.File, handler implementation should check this interface and call Sync
// if they support using file as sink
// TODO: about sync
// - in go, both os.Stderr and os.Stdout are not (line) buffered
// - what would happen if os.Stderr.Close()
// - os.Stderr.Sync() will there be any different if stderr/stdout is redirected to a file
type Syncer interface {
Sync() error
}
var defaultHandler = NewTextHandler(os.Stderr)
// DefaultHandler returns the singleton defaultHandler instance, which logs to stderr in text format
// TODO: should allow customize default handler like default level, i.e. use json as default handler
func DefaultHandler() Handler {
return defaultHandler
}
// MultiHandler creates a handler that duplicates the log to all the provided handlers, it runs in
// serial and don't handle panic
func MultiHandler(handlers ...Handler) Handler {
return &multiHandler{handlers: handlers}
}
// textHandler writes log to io.Writer
type textHandler struct {
w io.Writer
}
// NewTextHandler formats log in human readable format without any escape, thus it is NOT machine readable
// Default handler is a textHandler using os.Stderr
func NewTextHandler(w io.Writer) Handler {
return &textHandler{w: w}
}
func (h *textHandler) HandleLog(level Level, time time.Time, msg string, source Caller, context Fields, fields Fields) {
b := make([]byte, 0, 50+len(msg)+len(source.File)+30*len(context)+30*len(fields))
// level
b = append(b, level.AlignedUpperString()...)
// time
b = append(b, ' ')
b = time.AppendFormat(b, defaultTimeStampFormat)
// source, optional
if source.Line != 0 {
b = append(b, ' ')
last := strings.LastIndex(source.File, "/")
b = append(b, source.File[last+1:]...)
b = append(b, ':')
b = strconv.AppendInt(b, int64(source.Line), 10)
}
// message
b = append(b, ' ')
b = append(b, msg...)
// context
if len(context) > 0 {
b = append(b, ' ')
b = formatFields(b, context)
}
// fields
if len(fields) > 0 {
b = append(b, ' ')
b = formatFields(b, fields)
}
b = append(b, '\n')
h.w.Write(b)
}
// Flush implements Handler interface
func (h *textHandler) Flush() {
if s, ok := h.w.(Syncer); ok {
s.Sync()
}
}
// ----------------- start of multi handler ---------------------------
var _ Handler = (*multiHandler)(nil)
// https://github.com/dyweb/gommon/issues/87
type multiHandler struct {
handlers []Handler
}
func (m *multiHandler) HandleLog(level Level, now time.Time, msg string, source Caller, context Fields, fields Fields) {
for _, h := range m.handlers {
h.HandleLog(level, now, msg, source, context, fields)
}
}
func (m *multiHandler) Flush() {
for _, h := range m.handlers {
h.Flush()
}
}
// ----------------- end of multi handler ---------------------------
// ----------------- start of text format util ---------------------------
// it has an extra tailing space, which can be updated in place to a \n
func formatFields(b []byte, fields Fields) []byte {
for _, f := range fields {
b = append(b, f.Key...)
b = append(b, '=')
switch f.Type {
case IntType:
b = strconv.AppendInt(b, f.Int, 10)
case StringType:
b = append(b, f.Str...)
}
b = append(b, ' ')
}
b = b[:len(b)-1] // remove trailing space
return b
}
// ----------------- end of text format util ---------------------------