// 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 // message is kept as a pointer to have shallow copies update this once // needed. message *string args []interface{} fmt string formatter Formatter formatted string } // Formatted returns the string-formatted version of a record. 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 a string message for outputting. Redacts any record args // that implement the Redactor interface 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() } } msg := fmt.Sprintf(r.fmt, r.args...) r.message = &msg } return *r.message } // Logger is a logging unit. It controls the flow of messages to a given // (swappable) 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 changes the backend of the logger. func (l *Logger) SetBackend(backend LeveledBackend) { l.backend = backend l.haveBackend = true } // GetLogger creates and returns a Logger object based on the module name. // TODO call NewLogger and remove MustGetLogger? 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 } // InitForTesting is a convenient method when using logging in a test. Once // called, the time will be frozen to January 1, 1970 UTC. func InitForTesting(level Level) *MemoryBackend { Reset() memoryBackend := NewMemoryBackend(10240) leveledBackend := AddModuleLevel(memoryBackend) leveledBackend.SetLevel(level, "") SetBackend(leveledBackend) timeNow = func() time.Time { return time.Unix(0, 0).UTC() } return memoryBackend } // 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{}) { s := fmt.Sprint(args...) l.log(CRITICAL, "%s", s) 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{}) { s := fmt.Sprint(args...) l.log(CRITICAL, "%s", s) panic(s) } // Panicf is equivalent to l.Critical followed by a call to panic(). func (l *Logger) Panicf(format string, args ...interface{}) { s := fmt.Sprintf(format, args...) l.log(CRITICAL, "%s", s) panic(s) } // Critical logs a message using CRITICAL as log level. (fmt.Sprint()) func (l *Logger) Critical(args ...interface{}) { l.log(CRITICAL, defaultArgsFormat(len(args)), 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. (fmt.Sprint()) func (l *Logger) Error(args ...interface{}) { l.log(ERROR, defaultArgsFormat(len(args)), 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, defaultArgsFormat(len(args)), 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, defaultArgsFormat(len(args)), 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, defaultArgsFormat(len(args)), 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, defaultArgsFormat(len(args)), args...) } // Debugf logs a message using DEBUG as log level. func (l *Logger) Debugf(format string, args ...interface{}) { l.log(DEBUG, format, args...) } func defaultArgsFormat(argc int) string { f := strings.Repeat("%s ", argc) if argc > 0 { f = f[:len(f) - 1] } return f } func init() { Reset() }