package logger import ( "bytes" "fmt" "os" "runtime" "strings" "sync" "time" ) type Severity int const ( FATAL Severity = iota ERROR WARNING INFO DEBUG ) var severityName = []string{ FATAL: "FATAL", ERROR: "ERROR", WARNING: "WARNING", INFO: "INFO", DEBUG: "DEBUG", } const ( numSeverity = 5 ) type Backend interface { Log(s Severity, msg []byte) close() } type stdBackend struct{} func (self *stdBackend) Log(s Severity, msg []byte) { os.Stdout.Write(msg) } func (self *stdBackend) close() {} type Logger struct { s Severity backend Backend mu sync.Mutex freeList *buffer freeListMu sync.Mutex logToStderr bool } //resued buffer for fast format the output string type buffer struct { bytes.Buffer tmp [64]byte next *buffer } func (self *Logger) getBuffer() *buffer { self.freeListMu.Lock() b := self.freeList if b != nil { self.freeList = b.next } self.freeListMu.Unlock() if b == nil { b = new(buffer) } else { b.next = nil b.Reset() } return b } // Some custom tiny helper functions to print the log header efficiently. const digits = "0123456789" // twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. func (buf *buffer) twoDigits(i, d int) { buf.tmp[i+1] = digits[d%10] d /= 10 buf.tmp[i] = digits[d%10] } // nDigits formats an n-digit integer at buf.tmp[i], // padding with pad on the left. // It assumes d >= 0. func (buf *buffer) nDigits(n, i, d int, pad byte) { j := n - 1 for ; j >= 0 && d > 0; j-- { buf.tmp[i+j] = digits[d%10] d /= 10 } for ; j >= 0; j-- { buf.tmp[i+j] = pad } } // someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. func (buf *buffer) someDigits(i, d int) int { // Print into the top, then copy down. We know there's space for at least // a 10-digit number. j := len(buf.tmp) for { j-- buf.tmp[j] = digits[d%10] d /= 10 if d == 0 { break } } return copy(buf.tmp[i:], buf.tmp[j:]) } func (self *Logger) putBuffer(b *buffer) { if b.Len() >= 256 { // Let big buffers die a natural death. return } self.freeListMu.Lock() b.next = self.freeList self.freeList = b self.freeListMu.Unlock() } func (self *Logger) formatHeader(s Severity, file string, line int) *buffer { now := time.Now() if line < 0 { line = 0 // not a real line number, but acceptable to someDigits } buf := self.getBuffer() // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. // It's worth about 3X. Fprintf is hard. year, month, day := now.Date() hour, minute, second := now.Clock() //2015-06-16 12:00:35 ERROR test.go:12 ... buf.nDigits(4, 0, year, '0') buf.tmp[4] = '-' buf.twoDigits(5, int(month)) buf.tmp[7] = '-' buf.twoDigits(8, day) buf.tmp[10] = ' ' buf.twoDigits(11, hour) buf.tmp[13] = ':' buf.twoDigits(14, minute) buf.tmp[16] = ':' buf.twoDigits(17, second) buf.tmp[19] = '.' buf.nDigits(6, 20, now.Nanosecond()/1000, '0') buf.tmp[26] = ' ' buf.Write(buf.tmp[:27]) buf.WriteString(severityName[s]) buf.WriteByte(' ') buf.WriteString(file) buf.tmp[0] = ':' n := buf.someDigits(1, line) buf.tmp[n+1] = ' ' buf.Write(buf.tmp[:n+2]) return buf } func (self *Logger) header(s Severity, depth int) *buffer { _, file, line, ok := runtime.Caller(3 + depth) if !ok { file = "???" line = 1 } else { dirs := strings.Split(file, "/") if len(dirs) >= 2 { file = dirs[len(dirs)-2] + "/" + dirs[len(dirs)-1] } else { file = dirs[len(dirs)-1] } } return self.formatHeader(s, file, line) } func (self *Logger) print(s Severity, args ...interface{}) { self.printDepth(s, 1, args...) } func (self *Logger) printf(s Severity, format string, args ...interface{}) { self.printfDepth(s, 1, format, args...) } func (self *Logger) printDepth(s Severity, depth int, args ...interface{}) { if self.s < s { return } buf := self.header(s, depth) fmt.Fprint(buf, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } self.output(s, buf) } func (self *Logger) printfDepth(s Severity, depth int, format string, args ...interface{}) { if self.s < s { return } buf := self.header(s, depth) fmt.Fprintf(buf, format, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } self.output(s, buf) } func (self *Logger) printfSimple(format string, args ...interface{}) { buf := self.getBuffer() fmt.Fprintf(buf, format, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } self.output(INFO, buf) } func (self *Logger) output(s Severity, buf *buffer) { if self.s < s { return } if self.logToStderr { os.Stderr.Write(buf.Bytes()) } else { self.backend.Log(s, buf.Bytes()) } if s == FATAL { trace := stacks(true) os.Stderr.Write(trace) os.Exit(255) } self.putBuffer(buf) } func stacks(all bool) []byte { // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. n := 10000 if all { n = 100000 } var trace []byte for i := 0; i < 5; i++ { trace = make([]byte, n) nbytes := runtime.Stack(trace, all) if nbytes < len(trace) { return trace[:nbytes] } n *= 2 } return trace } /*--------------------------logger public functions--------------------------*/ func NewLogger(level interface{}, backend Backend) *Logger { l := new(Logger) l.SetSeverity(level) l.backend = backend return l } func (l *Logger) SetSeverity(level interface{}) { if s, ok := level.(Severity); ok { l.s = s } else { if s, ok := level.(string); ok { for i, name := range severityName { if name == s { l.s = Severity(i) } } } } } func (l *Logger) Close() { if l.backend != nil { l.backend.close() } } func (l *Logger) LogToStderr() { l.logToStderr = true } func (l *Logger) Debug(args ...interface{}) { l.print(DEBUG, args...) } func (l *Logger) Debugf(format string, args ...interface{}) { l.printf(DEBUG, format, args...) } func (l *Logger) Info(args ...interface{}) { l.print(INFO, args...) } func (l *Logger) Infof(format string, args ...interface{}) { l.printf(INFO, format, args...) } func (l *Logger) Warning(args ...interface{}) { l.print(WARNING, args...) } func (l *Logger) Warningf(format string, args ...interface{}) { l.printf(WARNING, format, args...) } func (l *Logger) Error(args ...interface{}) { l.print(ERROR, args...) } func (l *Logger) Errorf(format string, args ...interface{}) { l.printf(ERROR, format, args...) } func (l *Logger) Fatal(args ...interface{}) { l.print(FATAL, args...) } func (l *Logger) Fatalf(format string, args ...interface{}) { l.printf(FATAL, format, args...) } func (l *Logger) SetLogging(level interface{}, backend Backend) { l.SetSeverity(level) l.backend = backend } ///////////////////////////////////////////////////////////////// // depth version, only a low level api func (l *Logger) LogDepth(s Severity, depth int, format string, args ...interface{}) { l.printfDepth(s, depth+1, format, args...) } func (l *Logger) PrintfSimple(format string, args ...interface{}) { l.printfSimple(format, args...) } /*---------------------------------------------------------------------------*/ var logging Logger var fileback *FileBackend = nil var sysback *syslogBackend = nil func init() { SetLogging(DEBUG, &stdBackend{}) } func SetLogging(level interface{}, backend Backend) { logging.SetLogging(level, backend) } func SetSeverity(level interface{}) { logging.SetSeverity(level) } func Close() { logging.Close() } func LogToStderr() { logging.LogToStderr() } /*-----------------------------public functions------------------------------*/ func Debug(args ...interface{}) { logging.print(DEBUG, args...) } func Debugf(format string, args ...interface{}) { logging.printf(DEBUG, format, args...) } func Info(args ...interface{}) { logging.print(INFO, args...) } func Infof(format string, args ...interface{}) { logging.printf(INFO, format, args...) } func Warning(args ...interface{}) { logging.print(WARNING, args...) } func Warningf(format string, args ...interface{}) { logging.printf(WARNING, format, args...) } func Error(args ...interface{}) { logging.print(ERROR, args...) } func Errorf(format string, args ...interface{}) { logging.printf(ERROR, format, args...) } func Fatal(args ...interface{}) { logging.print(FATAL, args...) } func Fatalf(format string, args ...interface{}) { logging.printf(FATAL, format, args...) } func LogDepth(s Severity, depth int, format string, args ...interface{}) { logging.printfDepth(s, depth+1, format, args...) } func Printf(format string, args ...interface{}) { logging.printfSimple(format, args...) } func GetLogger() *Logger { return &logging }