-
Notifications
You must be signed in to change notification settings - Fork 12
/
xlog.go
349 lines (317 loc) · 10 KB
/
xlog.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
// Package xlog is a logger coupled with HTTP net/context aware middleware.
//
// Unlike most loggers, xlog will never block your application because one its
// outputs is lagging. The log commands are connected to their outputs through
// a buffered channel and will prefer to discard messages if the buffer get full.
// All message formatting, serialization and transport happen in a dedicated go
// routine.
//
// Features:
//
// - Per request log context
// - Per request and/or per message key/value fields
// - Log levels (Debug, Info, Warn, Error)
// - Color output when terminal is detected
// - Custom output (JSON, logfmt, …)
// - Automatic gathering of request context like User-Agent, IP etc.
// - Drops message rather than blocking execution
// - Easy access logging thru github.com/rs/xaccess
//
// It works best in combination with github.com/rs/xhandler.
package xlog // import "github.com/rs/xlog"
import (
"fmt"
"io"
"log"
"os"
"path"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
// Logger defines the interface for a xlog compatible logger
type Logger interface {
// Implements io.Writer so it can be set a output of log.Logger
io.Writer
// SetField sets a field on the logger's context. All future messages on this logger
// will have this field set.
SetField(name string, value interface{})
// GetFields returns all the fields set on the logger
GetFields() F
// Debug logs a debug message. If last parameter is a map[string]string, it's content
// is added as fields to the message.
Debug(v ...interface{})
// Debug logs a debug message with format. If last parameter is a map[string]string,
// it's content is added as fields to the message.
Debugf(format string, v ...interface{})
// Info logs a info message. If last parameter is a map[string]string, it's content
// is added as fields to the message.
Info(v ...interface{})
// Info logs a info message with format. If last parameter is a map[string]string,
// it's content is added as fields to the message.
Infof(format string, v ...interface{})
// Warn logs a warning message. If last parameter is a map[string]string, it's content
// is added as fields to the message.
Warn(v ...interface{})
// Warn logs a warning message with format. If last parameter is a map[string]string,
// it's content is added as fields to the message.
Warnf(format string, v ...interface{})
// Error logs an error message. If last parameter is a map[string]string, it's content
// is added as fields to the message.
Error(v ...interface{})
// Error logs an error message with format. If last parameter is a map[string]string,
// it's content is added as fields to the message.
Errorf(format string, v ...interface{})
// Fatal logs an error message followed by a call to os.Exit(1). If last parameter is a
// map[string]string, it's content is added as fields to the message.
Fatal(v ...interface{})
// Fatalf logs an error message with format followed by a call to ox.Exit(1). If last
// parameter is a map[string]string, it's content is added as fields to the message.
Fatalf(format string, v ...interface{})
// Output mimics std logger interface
Output(calldepth int, s string) error
// OutputF outputs message with fields.
OutputF(level Level, calldepth int, msg string, fields map[string]interface{})
}
// LoggerCopier defines a logger with copy support
type LoggerCopier interface {
// Copy returns a copy of the logger
Copy() Logger
}
// Config defines logger's configuration
type Config struct {
// Level is the maximum level to output, logs with lower level are discarded.
Level Level
// Fields defines default fields to use with all messages.
Fields map[string]interface{}
// Output to use to write log messages to.
//
// You should always wrap your output with an OutputChannel otherwise your
// logger will be connected to its output synchronously.
Output Output
// DisablePooling removes the use of a sync.Pool for cases where logger
// instances are needed beyond the scope of a request handler. This option
// puts a greater pressure on GC and increases the amount of memory allocated
// and freed. Use only if persistent loggers are a requirement.
DisablePooling bool
}
// F represents a set of log message fields
type F map[string]interface{}
type logger struct {
level Level
output Output
fields F
disablePooling bool
}
// Common field names for log messages.
var (
KeyTime = "time"
KeyMessage = "message"
KeyLevel = "level"
KeyFile = "file"
)
var now = time.Now
var exit1 = func() { os.Exit(1) }
// critialLogger is a logger to use when xlog is not able to deliver a message
var critialLogger = log.New(os.Stderr, "xlog: ", log.Ldate|log.Ltime|log.LUTC|log.Lshortfile)
var loggerPool = &sync.Pool{
New: func() interface{} {
return &logger{}
},
}
// New manually creates a logger.
//
// This function should only be used out of a request. Use FromContext in request.
func New(c Config) Logger {
var l *logger
if c.DisablePooling {
l = &logger{}
} else {
l = loggerPool.Get().(*logger)
}
l.level = c.Level
l.output = c.Output
if l.output == nil {
l.output = NewOutputChannel(NewConsoleOutput())
}
for k, v := range c.Fields {
l.SetField(k, v)
}
l.disablePooling = c.DisablePooling
return l
}
// Copy returns a copy of the passed logger if the logger implements
// LoggerCopier or the NopLogger otherwise.
func Copy(l Logger) Logger {
if l, ok := l.(LoggerCopier); ok {
return l.Copy()
}
return NopLogger
}
// Copy returns a copy of the logger
func (l *logger) Copy() Logger {
l2 := &logger{
level: l.level,
output: l.output,
fields: map[string]interface{}{},
disablePooling: l.disablePooling,
}
for k, v := range l.fields {
l2.fields[k] = v
}
return l2
}
// close returns the logger to the pool for reuse
func (l *logger) close() {
if !l.disablePooling {
l.level = 0
l.output = nil
l.fields = nil
loggerPool.Put(l)
}
}
func (l *logger) send(level Level, calldepth int, msg string, fields map[string]interface{}) {
if level < l.level || l.output == nil {
return
}
data := make(map[string]interface{}, 4+len(fields)+len(l.fields))
data[KeyTime] = now()
data[KeyLevel] = level.String()
data[KeyMessage] = msg
if _, file, line, ok := runtime.Caller(calldepth); ok {
data[KeyFile] = path.Base(file) + ":" + strconv.FormatInt(int64(line), 10)
}
for k, v := range fields {
data[k] = v
}
if l.fields != nil {
for k, v := range l.fields {
data[k] = v
}
}
if err := l.output.Write(data); err != nil {
critialLogger.Print("send error: ", err.Error())
}
}
func extractFields(v *[]interface{}) map[string]interface{} {
if l := len(*v); l > 0 {
if f, ok := (*v)[l-1].(map[string]interface{}); ok {
*v = (*v)[:l-1]
return f
}
if f, ok := (*v)[l-1].(F); ok {
*v = (*v)[:l-1]
return f
}
}
return nil
}
// SetField implements Logger interface
func (l *logger) SetField(name string, value interface{}) {
if l.fields == nil {
l.fields = map[string]interface{}{}
}
l.fields[name] = value
}
// GetFields implements Logger interface
func (l *logger) GetFields() F {
return l.fields
}
// Output implements Logger interface
func (l *logger) OutputF(level Level, calldepth int, msg string, fields map[string]interface{}) {
l.send(level, calldepth+1, msg, fields)
}
// Debug implements Logger interface
func (l *logger) Debug(v ...interface{}) {
f := extractFields(&v)
l.send(LevelDebug, 2, fmt.Sprint(v...), f)
}
// Debugf implements Logger interface
func (l *logger) Debugf(format string, v ...interface{}) {
f := extractFields(&v)
l.send(LevelDebug, 2, fmt.Sprintf(format, v...), f)
}
// Info implements Logger interface
func (l *logger) Info(v ...interface{}) {
f := extractFields(&v)
l.send(LevelInfo, 2, fmt.Sprint(v...), f)
}
// Infof implements Logger interface
func (l *logger) Infof(format string, v ...interface{}) {
f := extractFields(&v)
l.send(LevelInfo, 2, fmt.Sprintf(format, v...), f)
}
// Warn implements Logger interface
func (l *logger) Warn(v ...interface{}) {
f := extractFields(&v)
l.send(LevelWarn, 2, fmt.Sprint(v...), f)
}
// Warnf implements Logger interface
func (l *logger) Warnf(format string, v ...interface{}) {
f := extractFields(&v)
l.send(LevelWarn, 2, fmt.Sprintf(format, v...), f)
}
// Error implements Logger interface
func (l *logger) Error(v ...interface{}) {
f := extractFields(&v)
l.send(LevelError, 2, fmt.Sprint(v...), f)
}
// Errorf implements Logger interface
//
// Go vet users: you may append %v at the end of you format when using xlog.F{} as a last
// argument to workaround go vet false alarm.
func (l *logger) Errorf(format string, v ...interface{}) {
f := extractFields(&v)
if f != nil {
// Let user add a %v at the end of the message when fields are passed to satisfy go vet
l := len(format)
if l > 2 && format[l-2] == '%' && format[l-1] == 'v' {
format = format[0 : l-2]
}
}
l.send(LevelError, 2, fmt.Sprintf(format, v...), f)
}
// Fatal implements Logger interface
func (l *logger) Fatal(v ...interface{}) {
f := extractFields(&v)
l.send(LevelFatal, 2, fmt.Sprint(v...), f)
if o, ok := l.output.(*OutputChannel); ok {
o.Close()
}
exit1()
}
// Fatalf implements Logger interface
//
// Go vet users: you may append %v at the end of you format when using xlog.F{} as a last
// argument to workaround go vet false alarm.
func (l *logger) Fatalf(format string, v ...interface{}) {
f := extractFields(&v)
if f != nil {
// Let user add a %v at the end of the message when fields are passed to satisfy go vet
l := len(format)
if l > 2 && format[l-2] == '%' && format[l-1] == 'v' {
format = format[0 : l-2]
}
}
l.send(LevelFatal, 2, fmt.Sprintf(format, v...), f)
if o, ok := l.output.(*OutputChannel); ok {
o.Close()
}
exit1()
}
// Write implements io.Writer interface
func (l *logger) Write(p []byte) (int, error) {
msg := strings.TrimRight(string(p), "\n")
l.send(LevelInfo, 4, msg, nil)
if o, ok := l.output.(*OutputChannel); ok {
o.Flush()
}
return len(p), nil
}
// Output implements common logger interface
func (l *logger) Output(calldepth int, s string) error {
l.send(LevelInfo, 2, s, nil)
return nil
}