22 _levelToBracket = map[Level]string{
31 // Given the options (nil for defaults), create a new Logger
32 func New(opts *LoggerOptions) Logger {
34 opts = &LoggerOptions{}
54 json: opts.JSONFormat,
55 caller: opts.IncludeLocation,
57 timeFormat: TimeFormat,
58 w: bufio.NewWriter(output),
61 if opts.TimeFormat != "" {
62 ret.timeFormat = opts.TimeFormat
64 atomic.StoreInt32(ret.level, int32(level))
68 // The internal logger implementation. Internal in that it is defined entirely
70 type intLogger struct {
76 // this is a pointer so that it's shared by any derived loggers, since
77 // those derived loggers share the bufio.Writer as well.
85 // Make sure that intLogger is a Logger
86 var _ Logger = &intLogger{}
88 // The time format to use for logging. This is a version of RFC3339 that
89 // contains millisecond precision
90 const TimeFormat = "2006-01-02T15:04:05.000Z0700"
92 // Log a message and a set of key/value pairs if the given level is at
93 // or more severe that the threshold configured in the Logger.
94 func (z *intLogger) Log(level Level, msg string, args ...interface{}) {
95 if level < Level(atomic.LoadInt32(z.level)) {
105 z.logJson(t, level, msg, args...)
107 z.log(t, level, msg, args...)
113 // Cleanup a path by returning the last 2 segments of the path only.
114 func trimCallerPath(path string) string {
115 // lovely borrowed from zap
116 // nb. To make sure we trim the path correctly on Windows too, we
117 // counter-intuitively need to use '/' and *not* os.PathSeparator here,
118 // because the path given originates from Go stdlib, specifically
119 // runtime.Caller() which (as of Mar/17) returns forward slashes even on
122 // See https://github.com/golang/go/issues/3335
123 // and https://github.com/golang/go/issues/18151
125 // for discussion on the issue on Go side.
128 // Find the last separator.
130 idx := strings.LastIndexByte(path, '/')
135 // Find the penultimate separator.
136 idx = strings.LastIndexByte(path[:idx], '/')
144 // Non-JSON logging format function
145 func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) {
146 z.w.WriteString(t.Format(z.timeFormat))
149 s, ok := _levelToBracket[level]
153 z.w.WriteString("[?????]")
157 if _, file, line, ok := runtime.Caller(3); ok {
159 z.w.WriteString(trimCallerPath(file))
161 z.w.WriteString(strconv.Itoa(line))
169 z.w.WriteString(z.name)
170 z.w.WriteString(": ")
175 args = append(z.implied, args...)
177 var stacktrace CapturedStacktrace
179 if args != nil && len(args) > 0 {
180 if len(args)%2 != 0 {
181 cs, ok := args[len(args)-1].(CapturedStacktrace)
183 args = args[:len(args)-1]
186 args = append(args, "<unknown>")
193 for i := 0; i < len(args); i = i + 2 {
199 switch st := args[i+1].(type) {
203 val = strconv.FormatInt(int64(st), 10)
205 val = strconv.FormatInt(int64(st), 10)
207 val = strconv.FormatInt(int64(st), 10)
209 val = strconv.FormatInt(int64(st), 10)
211 val = strconv.FormatInt(int64(st), 10)
213 val = strconv.FormatUint(uint64(st), 10)
215 val = strconv.FormatUint(uint64(st), 10)
217 val = strconv.FormatUint(uint64(st), 10)
219 val = strconv.FormatUint(uint64(st), 10)
221 val = strconv.FormatUint(uint64(st), 10)
222 case CapturedStacktrace:
226 val = fmt.Sprintf(st[0].(string), st[1:]...)
228 v := reflect.ValueOf(st)
229 if v.Kind() == reflect.Slice {
230 val = z.renderSlice(v)
233 val = fmt.Sprintf("%v", st)
238 z.w.WriteString(args[i].(string))
241 if !raw && strings.ContainsAny(val, " \t\n\r") {
251 z.w.WriteString("\n")
253 if stacktrace != "" {
254 z.w.WriteString(string(stacktrace))
258 func (z *intLogger) renderSlice(v reflect.Value) string {
263 for i := 0; i < v.Len(); i++ {
265 buf.WriteString(", ")
275 case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
276 val = strconv.FormatInt(sv.Int(), 10)
277 case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
278 val = strconv.FormatUint(sv.Uint(), 10)
280 val = fmt.Sprintf("%v", sv.Interface())
283 if strings.ContainsAny(val, " \t\n\r") {
297 // JSON logging function
298 func (z *intLogger) logJson(t time.Time, level Level, msg string, args ...interface{}) {
299 vals := map[string]interface{}{
301 "@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"),
320 vals["@level"] = levelStr
323 vals["@module"] = z.name
327 if _, file, line, ok := runtime.Caller(3); ok {
328 vals["@caller"] = fmt.Sprintf("%s:%d", file, line)
332 args = append(z.implied, args...)
334 if args != nil && len(args) > 0 {
335 if len(args)%2 != 0 {
336 cs, ok := args[len(args)-1].(CapturedStacktrace)
338 args = args[:len(args)-1]
339 vals["stacktrace"] = cs
341 args = append(args, "<unknown>")
345 for i := 0; i < len(args); i = i + 2 {
346 if _, ok := args[i].(string); !ok {
347 // As this is the logging function not much we can do here
348 // without injecting into logs...
352 switch sv := val.(type) {
354 // Check if val is of type error. If error type doesn't
355 // implement json.Marshaler or encoding.TextMarshaler
356 // then set val to err.Error() so that it gets marshaled
358 case json.Marshaler, encoding.TextMarshaler:
363 val = fmt.Sprintf(sv[0].(string), sv[1:]...)
366 vals[args[i].(string)] = val
370 err := json.NewEncoder(z.w).Encode(vals)
376 // Emit the message and args at DEBUG level
377 func (z *intLogger) Debug(msg string, args ...interface{}) {
378 z.Log(Debug, msg, args...)
381 // Emit the message and args at TRACE level
382 func (z *intLogger) Trace(msg string, args ...interface{}) {
383 z.Log(Trace, msg, args...)
386 // Emit the message and args at INFO level
387 func (z *intLogger) Info(msg string, args ...interface{}) {
388 z.Log(Info, msg, args...)
391 // Emit the message and args at WARN level
392 func (z *intLogger) Warn(msg string, args ...interface{}) {
393 z.Log(Warn, msg, args...)
396 // Emit the message and args at ERROR level
397 func (z *intLogger) Error(msg string, args ...interface{}) {
398 z.Log(Error, msg, args...)
401 // Indicate that the logger would emit TRACE level logs
402 func (z *intLogger) IsTrace() bool {
403 return Level(atomic.LoadInt32(z.level)) == Trace
406 // Indicate that the logger would emit DEBUG level logs
407 func (z *intLogger) IsDebug() bool {
408 return Level(atomic.LoadInt32(z.level)) <= Debug
411 // Indicate that the logger would emit INFO level logs
412 func (z *intLogger) IsInfo() bool {
413 return Level(atomic.LoadInt32(z.level)) <= Info
416 // Indicate that the logger would emit WARN level logs
417 func (z *intLogger) IsWarn() bool {
418 return Level(atomic.LoadInt32(z.level)) <= Warn
421 // Indicate that the logger would emit ERROR level logs
422 func (z *intLogger) IsError() bool {
423 return Level(atomic.LoadInt32(z.level)) <= Error
426 // Return a sub-Logger for which every emitted log message will contain
427 // the given key/value pairs. This is used to create a context specific
429 func (z *intLogger) With(args ...interface{}) Logger {
430 if len(args)%2 != 0 {
431 panic("With() call requires paired arguments")
434 var nz intLogger = *z
436 result := make(map[string]interface{}, len(z.implied)+len(args))
437 keys := make([]string, 0, len(z.implied)+len(args))
439 // Read existing args, store map and key for consistent sorting
440 for i := 0; i < len(z.implied); i += 2 {
441 key := z.implied[i].(string)
442 keys = append(keys, key)
443 result[key] = z.implied[i+1]
445 // Read new args, store map and key for consistent sorting
446 for i := 0; i < len(args); i += 2 {
447 key := args[i].(string)
448 _, exists := result[key]
450 keys = append(keys, key)
452 result[key] = args[i+1]
455 // Sort keys to be consistent
458 nz.implied = make([]interface{}, 0, len(z.implied)+len(args))
459 for _, k := range keys {
460 nz.implied = append(nz.implied, k)
461 nz.implied = append(nz.implied, result[k])
467 // Create a new sub-Logger that a name decending from the current name.
468 // This is used to create a subsystem specific Logger.
469 func (z *intLogger) Named(name string) Logger {
470 var nz intLogger = *z
473 nz.name = nz.name + "." + name
481 // Create a new sub-Logger with an explicit name. This ignores the current
482 // name. This is used to create a standalone logger that doesn't fall
483 // within the normal hierarchy.
484 func (z *intLogger) ResetNamed(name string) Logger {
485 var nz intLogger = *z
492 // Update the logging level on-the-fly. This will affect all subloggers as
494 func (z *intLogger) SetLevel(level Level) {
495 atomic.StoreInt32(z.level, int32(level))
498 // Create a *log.Logger that will send it's data through this Logger. This
499 // allows packages that expect to be using the standard library log to actually
501 func (z *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
503 opts = &StandardLoggerOptions{}
506 return log.New(&stdlogAdapter{z, opts.InferLevels}, "", 0)