20 _levelToBracket = map[Level]string{
29 // Given the options (nil for defaults), create a new Logger
30 func New(opts *LoggerOptions) Logger {
32 opts = &LoggerOptions{}
52 json: opts.JSONFormat,
53 caller: opts.IncludeLocation,
55 timeFormat: TimeFormat,
56 w: bufio.NewWriter(output),
59 if opts.TimeFormat != "" {
60 ret.timeFormat = opts.TimeFormat
62 atomic.StoreInt32(ret.level, int32(level))
66 // The internal logger implementation. Internal in that it is defined entirely
68 type intLogger struct {
74 // this is a pointer so that it's shared by any derived loggers, since
75 // those derived loggers share the bufio.Writer as well.
83 // Make sure that intLogger is a Logger
84 var _ Logger = &intLogger{}
86 // The time format to use for logging. This is a version of RFC3339 that
87 // contains millisecond precision
88 const TimeFormat = "2006-01-02T15:04:05.000Z0700"
90 // Log a message and a set of key/value pairs if the given level is at
91 // or more severe that the threshold configured in the Logger.
92 func (z *intLogger) Log(level Level, msg string, args ...interface{}) {
93 if level < Level(atomic.LoadInt32(z.level)) {
103 z.logJson(t, level, msg, args...)
105 z.log(t, level, msg, args...)
111 // Cleanup a path by returning the last 2 segments of the path only.
112 func trimCallerPath(path string) string {
113 // lovely borrowed from zap
114 // nb. To make sure we trim the path correctly on Windows too, we
115 // counter-intuitively need to use '/' and *not* os.PathSeparator here,
116 // because the path given originates from Go stdlib, specifically
117 // runtime.Caller() which (as of Mar/17) returns forward slashes even on
120 // See https://github.com/golang/go/issues/3335
121 // and https://github.com/golang/go/issues/18151
123 // for discussion on the issue on Go side.
126 // Find the last separator.
128 idx := strings.LastIndexByte(path, '/')
133 // Find the penultimate separator.
134 idx = strings.LastIndexByte(path[:idx], '/')
142 // Non-JSON logging format function
143 func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) {
144 z.w.WriteString(t.Format(z.timeFormat))
147 s, ok := _levelToBracket[level]
151 z.w.WriteString("[UNKN ]")
155 if _, file, line, ok := runtime.Caller(3); ok {
157 z.w.WriteString(trimCallerPath(file))
159 z.w.WriteString(strconv.Itoa(line))
167 z.w.WriteString(z.name)
168 z.w.WriteString(": ")
173 args = append(z.implied, args...)
175 var stacktrace CapturedStacktrace
177 if args != nil && len(args) > 0 {
178 if len(args)%2 != 0 {
179 cs, ok := args[len(args)-1].(CapturedStacktrace)
181 args = args[:len(args)-1]
184 args = append(args, "<unknown>")
191 for i := 0; i < len(args); i = i + 2 {
194 switch st := args[i+1].(type) {
198 val = strconv.FormatInt(int64(st), 10)
200 val = strconv.FormatInt(int64(st), 10)
202 val = strconv.FormatInt(int64(st), 10)
204 val = strconv.FormatInt(int64(st), 10)
206 val = strconv.FormatInt(int64(st), 10)
208 val = strconv.FormatUint(uint64(st), 10)
210 val = strconv.FormatUint(uint64(st), 10)
212 val = strconv.FormatUint(uint64(st), 10)
214 val = strconv.FormatUint(uint64(st), 10)
216 val = strconv.FormatUint(uint64(st), 10)
217 case CapturedStacktrace:
221 val = fmt.Sprintf(st[0].(string), st[1:]...)
223 val = fmt.Sprintf("%v", st)
227 z.w.WriteString(args[i].(string))
230 if strings.ContainsAny(val, " \t\n\r") {
240 z.w.WriteString("\n")
242 if stacktrace != "" {
243 z.w.WriteString(string(stacktrace))
247 // JSON logging function
248 func (z *intLogger) logJson(t time.Time, level Level, msg string, args ...interface{}) {
249 vals := map[string]interface{}{
251 "@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"),
270 vals["@level"] = levelStr
273 vals["@module"] = z.name
277 if _, file, line, ok := runtime.Caller(3); ok {
278 vals["@caller"] = fmt.Sprintf("%s:%d", file, line)
282 args = append(z.implied, args...)
284 if args != nil && len(args) > 0 {
285 if len(args)%2 != 0 {
286 cs, ok := args[len(args)-1].(CapturedStacktrace)
288 args = args[:len(args)-1]
289 vals["stacktrace"] = cs
291 args = append(args, "<unknown>")
295 for i := 0; i < len(args); i = i + 2 {
296 if _, ok := args[i].(string); !ok {
297 // As this is the logging function not much we can do here
298 // without injecting into logs...
302 switch sv := val.(type) {
304 // Check if val is of type error. If error type doesn't
305 // implement json.Marshaler or encoding.TextMarshaler
306 // then set val to err.Error() so that it gets marshaled
308 case json.Marshaler, encoding.TextMarshaler:
313 val = fmt.Sprintf(sv[0].(string), sv[1:]...)
316 vals[args[i].(string)] = val
320 err := json.NewEncoder(z.w).Encode(vals)
326 // Emit the message and args at DEBUG level
327 func (z *intLogger) Debug(msg string, args ...interface{}) {
328 z.Log(Debug, msg, args...)
331 // Emit the message and args at TRACE level
332 func (z *intLogger) Trace(msg string, args ...interface{}) {
333 z.Log(Trace, msg, args...)
336 // Emit the message and args at INFO level
337 func (z *intLogger) Info(msg string, args ...interface{}) {
338 z.Log(Info, msg, args...)
341 // Emit the message and args at WARN level
342 func (z *intLogger) Warn(msg string, args ...interface{}) {
343 z.Log(Warn, msg, args...)
346 // Emit the message and args at ERROR level
347 func (z *intLogger) Error(msg string, args ...interface{}) {
348 z.Log(Error, msg, args...)
351 // Indicate that the logger would emit TRACE level logs
352 func (z *intLogger) IsTrace() bool {
353 return Level(atomic.LoadInt32(z.level)) == Trace
356 // Indicate that the logger would emit DEBUG level logs
357 func (z *intLogger) IsDebug() bool {
358 return Level(atomic.LoadInt32(z.level)) <= Debug
361 // Indicate that the logger would emit INFO level logs
362 func (z *intLogger) IsInfo() bool {
363 return Level(atomic.LoadInt32(z.level)) <= Info
366 // Indicate that the logger would emit WARN level logs
367 func (z *intLogger) IsWarn() bool {
368 return Level(atomic.LoadInt32(z.level)) <= Warn
371 // Indicate that the logger would emit ERROR level logs
372 func (z *intLogger) IsError() bool {
373 return Level(atomic.LoadInt32(z.level)) <= Error
376 // Return a sub-Logger for which every emitted log message will contain
377 // the given key/value pairs. This is used to create a context specific
379 func (z *intLogger) With(args ...interface{}) Logger {
380 if len(args)%2 != 0 {
381 panic("With() call requires paired arguments")
384 var nz intLogger = *z
386 result := make(map[string]interface{}, len(z.implied)+len(args))
387 keys := make([]string, 0, len(z.implied)+len(args))
389 // Read existing args, store map and key for consistent sorting
390 for i := 0; i < len(z.implied); i += 2 {
391 key := z.implied[i].(string)
392 keys = append(keys, key)
393 result[key] = z.implied[i+1]
395 // Read new args, store map and key for consistent sorting
396 for i := 0; i < len(args); i += 2 {
397 key := args[i].(string)
398 _, exists := result[key]
400 keys = append(keys, key)
402 result[key] = args[i+1]
405 // Sort keys to be consistent
408 nz.implied = make([]interface{}, 0, len(z.implied)+len(args))
409 for _, k := range keys {
410 nz.implied = append(nz.implied, k)
411 nz.implied = append(nz.implied, result[k])
417 // Create a new sub-Logger that a name decending from the current name.
418 // This is used to create a subsystem specific Logger.
419 func (z *intLogger) Named(name string) Logger {
420 var nz intLogger = *z
423 nz.name = nz.name + "." + name
431 // Create a new sub-Logger with an explicit name. This ignores the current
432 // name. This is used to create a standalone logger that doesn't fall
433 // within the normal hierarchy.
434 func (z *intLogger) ResetNamed(name string) Logger {
435 var nz intLogger = *z
442 // Update the logging level on-the-fly. This will affect all subloggers as
444 func (z *intLogger) SetLevel(level Level) {
445 atomic.StoreInt32(z.level, int32(level))
448 // Create a *log.Logger that will send it's data through this Logger. This
449 // allows packages that expect to be using the standard library log to actually
451 func (z *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
453 opts = &StandardLoggerOptions{}
456 return log.New(&stdlogAdapter{z, opts.InferLevels}, "", 0)