| // Copyright 2013, Örjan Persson. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package logging implements a logging infrastructure for Go. It supports |
| // different logging backends like syslog, file and memory. Multiple backends |
| // can be utilized with different log levels per backend and logger. |
| package logging |
| |
| import ( |
| "bytes" |
| "fmt" |
| "log" |
| "os" |
| "strings" |
| "sync/atomic" |
| "time" |
| ) |
| |
| // Redactor is an interface for types that may contain sensitive information |
| // (like passwords), which shouldn't be printed to the log. The idea was found |
| // in relog as part of the vitness project. |
| type Redactor interface { |
| Redacted() interface{} |
| } |
| |
| // Redact returns a string of * having the same length as s. |
| func Redact(s string) string { |
| return strings.Repeat("*", len(s)) |
| } |
| |
| var ( |
| // Sequence number is incremented and utilized for all log records created. |
| sequenceNo uint64 |
| |
| // timeNow is a customizable for testing purposes. |
| timeNow = time.Now |
| ) |
| |
| // Record represents a log record and contains the timestamp when the record |
| // was created, an increasing id, filename and line and finally the actual |
| // formatted log line. |
| type Record struct { |
| ID uint64 |
| Time time.Time |
| Module string |
| Level Level |
| Args []interface{} |
| |
| // message is kept as a pointer to have shallow copies update this once |
| // needed. |
| message *string |
| fmt *string |
| formatter Formatter |
| formatted string |
| } |
| |
| // Formatted returns the formatted log record string. |
| func (r *Record) Formatted(calldepth int) string { |
| if r.formatted == "" { |
| var buf bytes.Buffer |
| r.formatter.Format(calldepth+1, r, &buf) |
| r.formatted = buf.String() |
| } |
| return r.formatted |
| } |
| |
| // Message returns the log record message. |
| func (r *Record) Message() string { |
| if r.message == nil { |
| // Redact the arguments that implements the Redactor interface |
| for i, arg := range r.Args { |
| if redactor, ok := arg.(Redactor); ok == true { |
| r.Args[i] = redactor.Redacted() |
| } |
| } |
| var buf bytes.Buffer |
| if r.fmt != nil { |
| fmt.Fprintf(&buf, *r.fmt, r.Args...) |
| } else { |
| // use Fprintln to make sure we always get space between arguments |
| fmt.Fprintln(&buf, r.Args...) |
| buf.Truncate(buf.Len() - 1) // strip newline |
| } |
| msg := buf.String() |
| r.message = &msg |
| } |
| return *r.message |
| } |
| |
| // Logger is the actual logger which creates log records based on the functions |
| // called and passes them to the underlying logging backend. |
| type Logger struct { |
| Module string |
| backend LeveledBackend |
| haveBackend bool |
| |
| // ExtraCallDepth can be used to add additional call depth when getting the |
| // calling function. This is normally used when wrapping a logger. |
| ExtraCalldepth int |
| } |
| |
| // SetBackend overrides any previously defined backend for this logger. |
| func (l *Logger) SetBackend(backend LeveledBackend) { |
| l.backend = backend |
| l.haveBackend = true |
| } |
| |
| // TODO call NewLogger and remove MustGetLogger? |
| |
| // GetLogger creates and returns a Logger object based on the module name. |
| func GetLogger(module string) (*Logger, error) { |
| return &Logger{Module: module}, nil |
| } |
| |
| // MustGetLogger is like GetLogger but panics if the logger can't be created. |
| // It simplifies safe initialization of a global logger for eg. a package. |
| func MustGetLogger(module string) *Logger { |
| logger, err := GetLogger(module) |
| if err != nil { |
| panic("logger: " + module + ": " + err.Error()) |
| } |
| return logger |
| } |
| |
| // Reset restores the internal state of the logging library. |
| func Reset() { |
| // TODO make a global Init() method to be less magic? or make it such that |
| // if there's no backends at all configured, we could use some tricks to |
| // automatically setup backends based if we have a TTY or not. |
| sequenceNo = 0 |
| b := SetBackend(NewLogBackend(os.Stderr, "", log.LstdFlags)) |
| b.SetLevel(DEBUG, "") |
| SetFormatter(DefaultFormatter) |
| timeNow = time.Now |
| } |
| |
| // IsEnabledFor returns true if the logger is enabled for the given level. |
| func (l *Logger) IsEnabledFor(level Level) bool { |
| return defaultBackend.IsEnabledFor(level, l.Module) |
| } |
| |
| func (l *Logger) log(lvl Level, format *string, args ...interface{}) { |
| if !l.IsEnabledFor(lvl) { |
| return |
| } |
| |
| // Create the logging record and pass it in to the backend |
| record := &Record{ |
| ID: atomic.AddUint64(&sequenceNo, 1), |
| Time: timeNow(), |
| Module: l.Module, |
| Level: lvl, |
| fmt: format, |
| Args: args, |
| } |
| |
| // TODO use channels to fan out the records to all backends? |
| // TODO in case of errors, do something (tricky) |
| |
| // calldepth=2 brings the stack up to the caller of the level |
| // methods, Info(), Fatal(), etc. |
| // ExtraCallDepth allows this to be extended further up the stack in case we |
| // are wrapping these methods, eg. to expose them package level |
| if l.haveBackend { |
| l.backend.Log(lvl, 2+l.ExtraCalldepth, record) |
| return |
| } |
| |
| defaultBackend.Log(lvl, 2+l.ExtraCalldepth, record) |
| } |
| |
| // Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1). |
| func (l *Logger) Fatal(args ...interface{}) { |
| l.log(CRITICAL, nil, args...) |
| os.Exit(1) |
| } |
| |
| // Fatalf is equivalent to l.Critical followed by a call to os.Exit(1). |
| func (l *Logger) Fatalf(format string, args ...interface{}) { |
| l.log(CRITICAL, &format, args...) |
| os.Exit(1) |
| } |
| |
| // Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic(). |
| func (l *Logger) Panic(args ...interface{}) { |
| l.log(CRITICAL, nil, args...) |
| panic(fmt.Sprint(args...)) |
| } |
| |
| // Panicf is equivalent to l.Critical followed by a call to panic(). |
| func (l *Logger) Panicf(format string, args ...interface{}) { |
| l.log(CRITICAL, &format, args...) |
| panic(fmt.Sprintf(format, args...)) |
| } |
| |
| // Critical logs a message using CRITICAL as log level. |
| func (l *Logger) Critical(args ...interface{}) { |
| l.log(CRITICAL, nil, args...) |
| } |
| |
| // Criticalf logs a message using CRITICAL as log level. |
| func (l *Logger) Criticalf(format string, args ...interface{}) { |
| l.log(CRITICAL, &format, args...) |
| } |
| |
| // Error logs a message using ERROR as log level. |
| func (l *Logger) Error(args ...interface{}) { |
| l.log(ERROR, nil, args...) |
| } |
| |
| // Errorf logs a message using ERROR as log level. |
| func (l *Logger) Errorf(format string, args ...interface{}) { |
| l.log(ERROR, &format, args...) |
| } |
| |
| // Warning logs a message using WARNING as log level. |
| func (l *Logger) Warning(args ...interface{}) { |
| l.log(WARNING, nil, args...) |
| } |
| |
| // Warningf logs a message using WARNING as log level. |
| func (l *Logger) Warningf(format string, args ...interface{}) { |
| l.log(WARNING, &format, args...) |
| } |
| |
| // Notice logs a message using NOTICE as log level. |
| func (l *Logger) Notice(args ...interface{}) { |
| l.log(NOTICE, nil, args...) |
| } |
| |
| // Noticef logs a message using NOTICE as log level. |
| func (l *Logger) Noticef(format string, args ...interface{}) { |
| l.log(NOTICE, &format, args...) |
| } |
| |
| // Info logs a message using INFO as log level. |
| func (l *Logger) Info(args ...interface{}) { |
| l.log(INFO, nil, args...) |
| } |
| |
| // Infof logs a message using INFO as log level. |
| func (l *Logger) Infof(format string, args ...interface{}) { |
| l.log(INFO, &format, args...) |
| } |
| |
| // Debug logs a message using DEBUG as log level. |
| func (l *Logger) Debug(args ...interface{}) { |
| l.log(DEBUG, nil, args...) |
| } |
| |
| // Debugf logs a message using DEBUG as log level. |
| func (l *Logger) Debugf(format string, args ...interface{}) { |
| l.log(DEBUG, &format, args...) |
| } |
| |
| func init() { |
| Reset() |
| } |