// Copyright 2014-2021 Ulrich Kunitz. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package xlog provides a simple logging package that allows to disable // certain message categories. It defines a type, Logger, with multiple // methods for formatting output. The package has also a predefined // 'standard' Logger accessible through helper function Print[f|ln], // Fatal[f|ln], Panic[f|ln], Warn[f|ln], Print[f|ln] and Debug[f|ln] // that are easier to use then creating a Logger manually. That logger // writes to standard error and prints the date and time of each logged // message, which can be configured using the function SetFlags. // // The Fatal functions call os.Exit(1) after the message is output // unless not suppressed by the flags. The Panic functions call panic // after the writing the log message unless suppressed. package xlog import ( "fmt" "io" "os" "runtime" "sync" "time" ) // The flags define what information is prefixed to each log entry // generated by the Logger. The Lno* versions allow the suppression of // specific output. The bits are or'ed together to control what will be // printed. There is no control over the order of the items printed and // the format. The full format is: // // 2009-01-23 01:23:23.123123 /a/b/c/d.go:23: message // const ( Ldate = 1 << iota // the date: 2009-01-23 Ltime // the time: 01:23:23 Lmicroseconds // microsecond resolution: 01:23:23.123123 Llongfile // full file name and line number: /a/b/c/d.go:23 Lshortfile // final file name element and line number: d.go:23 Lnopanic // suppresses output from Panic[f|ln] but not the panic call Lnofatal // suppresses output from Fatal[f|ln] but not the exit Lnowarn // suppresses output from Warn[f|ln] Lnoprint // suppresses output from Print[f|ln] Lnodebug // suppresses output from Debug[f|ln] // initial values for the standard logger Lstdflags = Ldate | Ltime | Lnodebug ) // A Logger represents an active logging object that generates lines of // output to an io.Writer. Each logging operation if not suppressed // makes a single call to the Writer's Write method. A Logger can be // used simultaneously from multiple goroutines; it guarantees to // serialize access to the Writer. type Logger struct { mu sync.Mutex // ensures atomic writes; and protects the following // fields prefix string // prefix to write at beginning of each line flag int // properties out io.Writer // destination for output buf []byte // for accumulating text to write } // New creates a new Logger. The out argument sets the destination to // which the log output will be written. The prefix appears at the // beginning of each log line. The flag argument defines the logging // properties. func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{out: out, prefix: prefix, flag: flag} } // std is the standard logger used by the package scope functions. var std = New(os.Stderr, "", Lstdflags) // itoa converts the integer to ASCII. A negative widths will avoid // zero-padding. The function supports only non-negative integers. func itoa(buf *[]byte, i int, wid int) { var u = uint(i) if u == 0 && wid <= 1 { *buf = append(*buf, '0') return } var b [32]byte bp := len(b) for ; u > 0 || wid > 0; u /= 10 { bp-- wid-- b[bp] = byte(u%10) + '0' } *buf = append(*buf, b[bp:]...) } // formatHeader puts the header into the buf field of the buffer. func (l *Logger) formatHeader(t time.Time, file string, line int) { l.buf = append(l.buf, l.prefix...) if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.flag&Ldate != 0 { year, month, day := t.Date() itoa(&l.buf, year, 4) l.buf = append(l.buf, '-') itoa(&l.buf, int(month), 2) l.buf = append(l.buf, '-') itoa(&l.buf, day, 2) l.buf = append(l.buf, ' ') } if l.flag&(Ltime|Lmicroseconds) != 0 { hour, min, sec := t.Clock() itoa(&l.buf, hour, 2) l.buf = append(l.buf, ':') itoa(&l.buf, min, 2) l.buf = append(l.buf, ':') itoa(&l.buf, sec, 2) if l.flag&Lmicroseconds != 0 { l.buf = append(l.buf, '.') itoa(&l.buf, t.Nanosecond()/1e3, 6) } l.buf = append(l.buf, ' ') } } if l.flag&(Lshortfile|Llongfile) != 0 { if l.flag&Lshortfile != 0 { short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { short = file[i+1:] break } } file = short } l.buf = append(l.buf, file...) l.buf = append(l.buf, ':') itoa(&l.buf, line, -1) l.buf = append(l.buf, ": "...) } } func (l *Logger) output(calldepth int, now time.Time, s string) error { var file string var line int if l.flag&(Lshortfile|Llongfile) != 0 { l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() } l.buf = l.buf[:0] l.formatHeader(now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } _, err := l.out.Write(l.buf) return err } // Output writes the string s with the header controlled by the flags to // the l.out writer. A newline will be appended if s doesn't end in a // newline. Calldepth is used to recover the PC, although all current // calls of Output use the call depth 2. Access to the function is serialized. func (l *Logger) Output(calldepth, noflag int, v ...interface{}) error { now := time.Now() l.mu.Lock() defer l.mu.Unlock() if l.flag&noflag != 0 { return nil } s := fmt.Sprint(v...) return l.output(calldepth+1, now, s) } // Outputf works like output but formats the output like Printf. func (l *Logger) Outputf(calldepth int, noflag int, format string, v ...interface{}) error { now := time.Now() l.mu.Lock() defer l.mu.Unlock() if l.flag&noflag != 0 { return nil } s := fmt.Sprintf(format, v...) return l.output(calldepth+1, now, s) } // Outputln works like output but formats the output like Println. func (l *Logger) Outputln(calldepth int, noflag int, v ...interface{}) error { now := time.Now() l.mu.Lock() defer l.mu.Unlock() if l.flag&noflag != 0 { return nil } s := fmt.Sprintln(v...) return l.output(calldepth+1, now, s) } // Panic prints the message like Print and calls panic. The printing // might be suppressed by the flag Lnopanic. func (l *Logger) Panic(v ...interface{}) { l.Output(2, Lnopanic, v...) s := fmt.Sprint(v...) panic(s) } // Panic prints the message like Print and calls panic. The printing // might be suppressed by the flag Lnopanic. func Panic(v ...interface{}) { std.Output(2, Lnopanic, v...) s := fmt.Sprint(v...) panic(s) } // Panicf prints the message like Printf and calls panic. The printing // might be suppressed by the flag Lnopanic. func (l *Logger) Panicf(format string, v ...interface{}) { l.Outputf(2, Lnopanic, format, v...) s := fmt.Sprintf(format, v...) panic(s) } // Panicf prints the message like Printf and calls panic. The printing // might be suppressed by the flag Lnopanic. func Panicf(format string, v ...interface{}) { std.Outputf(2, Lnopanic, format, v...) s := fmt.Sprintf(format, v...) panic(s) } // Panicln prints the message like Println and calls panic. The printing // might be suppressed by the flag Lnopanic. func (l *Logger) Panicln(v ...interface{}) { l.Outputln(2, Lnopanic, v...) s := fmt.Sprintln(v...) panic(s) } // Panicln prints the message like Println and calls panic. The printing // might be suppressed by the flag Lnopanic. func Panicln(v ...interface{}) { std.Outputln(2, Lnopanic, v...) s := fmt.Sprintln(v...) panic(s) } // Fatal prints the message like Print and calls os.Exit(1). The // printing might be suppressed by the flag Lnofatal. func (l *Logger) Fatal(v ...interface{}) { l.Output(2, Lnofatal, v...) os.Exit(1) } // Fatal prints the message like Print and calls os.Exit(1). The // printing might be suppressed by the flag Lnofatal. func Fatal(v ...interface{}) { std.Output(2, Lnofatal, v...) os.Exit(1) } // Fatalf prints the message like Printf and calls os.Exit(1). The // printing might be suppressed by the flag Lnofatal. func (l *Logger) Fatalf(format string, v ...interface{}) { l.Outputf(2, Lnofatal, format, v...) os.Exit(1) } // Fatalf prints the message like Printf and calls os.Exit(1). The // printing might be suppressed by the flag Lnofatal. func Fatalf(format string, v ...interface{}) { std.Outputf(2, Lnofatal, format, v...) os.Exit(1) } // Fatalln prints the message like Println and calls os.Exit(1). The // printing might be suppressed by the flag Lnofatal. func (l *Logger) Fatalln(format string, v ...interface{}) { l.Outputln(2, Lnofatal, v...) os.Exit(1) } // Fatalln prints the message like Println and calls os.Exit(1). The // printing might be suppressed by the flag Lnofatal. func Fatalln(format string, v ...interface{}) { std.Outputln(2, Lnofatal, v...) os.Exit(1) } // Warn prints the message like Print. The printing might be suppressed // by the flag Lnowarn. func (l *Logger) Warn(v ...interface{}) { l.Output(2, Lnowarn, v...) } // Warn prints the message like Print. The printing might be suppressed // by the flag Lnowarn. func Warn(v ...interface{}) { std.Output(2, Lnowarn, v...) } // Warnf prints the message like Printf. The printing might be suppressed // by the flag Lnowarn. func (l *Logger) Warnf(format string, v ...interface{}) { l.Outputf(2, Lnowarn, format, v...) } // Warnf prints the message like Printf. The printing might be suppressed // by the flag Lnowarn. func Warnf(format string, v ...interface{}) { std.Outputf(2, Lnowarn, format, v...) } // Warnln prints the message like Println. The printing might be suppressed // by the flag Lnowarn. func (l *Logger) Warnln(v ...interface{}) { l.Outputln(2, Lnowarn, v...) } // Warnln prints the message like Println. The printing might be suppressed // by the flag Lnowarn. func Warnln(v ...interface{}) { std.Outputln(2, Lnowarn, v...) } // Print prints the message like fmt.Print. The printing might be suppressed // by the flag Lnoprint. func (l *Logger) Print(v ...interface{}) { l.Output(2, Lnoprint, v...) } // Print prints the message like fmt.Print. The printing might be suppressed // by the flag Lnoprint. func Print(v ...interface{}) { std.Output(2, Lnoprint, v...) } // Printf prints the message like fmt.Printf. The printing might be suppressed // by the flag Lnoprint. func (l *Logger) Printf(format string, v ...interface{}) { l.Outputf(2, Lnoprint, format, v...) } // Printf prints the message like fmt.Printf. The printing might be suppressed // by the flag Lnoprint. func Printf(format string, v ...interface{}) { std.Outputf(2, Lnoprint, format, v...) } // Println prints the message like fmt.Println. The printing might be // suppressed by the flag Lnoprint. func (l *Logger) Println(v ...interface{}) { l.Outputln(2, Lnoprint, v...) } // Println prints the message like fmt.Println. The printing might be // suppressed by the flag Lnoprint. func Println(v ...interface{}) { std.Outputln(2, Lnoprint, v...) } // Debug prints the message like Print. The printing might be suppressed // by the flag Lnodebug. func (l *Logger) Debug(v ...interface{}) { l.Output(2, Lnodebug, v...) } // Debug prints the message like Print. The printing might be suppressed // by the flag Lnodebug. func Debug(v ...interface{}) { std.Output(2, Lnodebug, v...) } // Debugf prints the message like Printf. The printing might be suppressed // by the flag Lnodebug. func (l *Logger) Debugf(format string, v ...interface{}) { l.Outputf(2, Lnodebug, format, v...) } // Debugf prints the message like Printf. The printing might be suppressed // by the flag Lnodebug. func Debugf(format string, v ...interface{}) { std.Outputf(2, Lnodebug, format, v...) } // Debugln prints the message like Println. The printing might be suppressed // by the flag Lnodebug. func (l *Logger) Debugln(v ...interface{}) { l.Outputln(2, Lnodebug, v...) } // Debugln prints the message like Println. The printing might be suppressed // by the flag Lnodebug. func Debugln(v ...interface{}) { std.Outputln(2, Lnodebug, v...) } // Flags returns the current flags used by the logger. func (l *Logger) Flags() int { l.mu.Lock() defer l.mu.Unlock() return l.flag } // Flags returns the current flags used by the standard logger. func Flags() int { return std.Flags() } // SetFlags sets the flags of the logger. func (l *Logger) SetFlags(flag int) { l.mu.Lock() defer l.mu.Unlock() l.flag = flag } // SetFlags sets the flags for the standard logger. func SetFlags(flag int) { std.SetFlags(flag) } // Prefix returns the prefix used by the logger. func (l *Logger) Prefix() string { l.mu.Lock() defer l.mu.Unlock() return l.prefix } // Prefix returns the prefix used by the standard logger of the package. func Prefix() string { return std.Prefix() } // SetPrefix sets the prefix for the logger. func (l *Logger) SetPrefix(prefix string) { l.mu.Lock() defer l.mu.Unlock() l.prefix = prefix } // SetPrefix sets the prefix of the standard logger of the package. func SetPrefix(prefix string) { std.SetPrefix(prefix) } // SetOutput sets the output of the logger. func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() defer l.mu.Unlock() l.out = w } // SetOutput sets the output for the standard logger of the package. func SetOutput(w io.Writer) { std.SetOutput(w) }