diff --git a/src/log/entry.go b/src/log/entry.go new file mode 100644 index 00000000000000..874a625ea49263 --- /dev/null +++ b/src/log/entry.go @@ -0,0 +1,247 @@ +package log + +import ( + "context" + "fmt" + "os" +) + +// The Entry is a logging entry that contains context set by the user and needed data +// for the log. +// Entry fields are used by a custom formatter while formatting output. +type Entry struct { + logger *Logger // logger which will be used to log the entry + context context.Context // context set by the user + calldepth int // calldepth is the count of the number of frames to skip + level Level // level of the entry + message string // message contains the text to print +} + +// NewEntry creates a new Entry. The logger variable sets the +// the logger which will be used to log the entry. +func NewEntry(logger *Logger) *Entry { + return &Entry{ + logger: logger, + } +} + +// Logger returns the logger which will write entry to the output destination. +func (e *Entry) Logger() *Logger { + return e.logger +} + +// Context returns the context set by the user for entry. +func (e *Entry) Context() context.Context { + return e.context +} + +// LogLevel returns the log level for entry. +func (e *Entry) LogLevel() Level { + return e.level +} + +// Message returns the log message for entry. +func (e *Entry) Message() string { + return e.message +} + +// CallDepth returns the calldepth for entry. +func (e *Entry) CallDepth() int { + return e.calldepth +} + +// Print calls e.Output to print to the logger. +// Arguments are handled in the manner of fmt.Print. +func (e *Entry) Print(v ...interface{}) { + e.OutputLevel(NoneLevel, 2, fmt.Sprint(v...)) +} + +// Printf calls e.Output to print to the logger. +// Arguments are handled in the manner of fmt.Printf. +func (e *Entry) Printf(format string, v ...interface{}) { + e.OutputLevel(NoneLevel, 2, fmt.Sprintf(format, v...)) +} + +// Println calls e.Output to print to the logger. +// Arguments are handled in the manner of fmt.Println. +func (e *Entry) Println(v ...interface{}) { + e.OutputLevel(NoneLevel, 2, fmt.Sprintln(v...)) +} + +// Fatal is equivalent to Print() followed by a call to os.Exit(1). +func (e *Entry) Fatal(v ...interface{}) { + e.OutputLevel(FatalLevel, 2, fmt.Sprint(v...)) + os.Exit(1) +} + +// Fatalf is equivalent to Printf() followed by a call to os.Exit(1). +func (e *Entry) Fatalf(format string, v ...interface{}) { + e.OutputLevel(FatalLevel, 2, fmt.Sprintf(format, v...)) + os.Exit(1) +} + +// Fatalln is equivalent to Println() followed by a call to os.Exit(1). +func (e *Entry) Fatalln(v ...interface{}) { + e.OutputLevel(FatalLevel, 2, fmt.Sprintln(v...)) + os.Exit(1) +} + +// Panic is equivalent to Print() and logs the message at level Error +// followed by a call to panic(). +func (e *Entry) Panic(v ...interface{}) { + s := fmt.Sprint(v...) + e.OutputLevel(PanicLevel, 2, s) + panic(s) +} + +// Panicf is equivalent to Printf() and logs the message at level Error +// followed by a call to panic(). +func (e *Entry) Panicf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + e.OutputLevel(PanicLevel, 2, s) + panic(s) +} + +// Panicln is equivalent to Println() and logs the message at level Error +// followed by a call to panic(). +func (e *Entry) Panicln(v ...interface{}) { + s := fmt.Sprintln(v...) + e.OutputLevel(PanicLevel, 2, s) + panic(s) +} + +// Error is equivalent to Print() and logs the message at level Error. +func (e *Entry) Error(v ...interface{}) { + e.OutputLevel(ErrorLevel, 2, fmt.Sprint(v...)) +} + +// Errorf is equivalent to Printf() and logs the message at level Error. +func (e *Entry) Errorf(format string, v ...interface{}) { + e.OutputLevel(ErrorLevel, 2, fmt.Sprintf(format, v...)) +} + +// Errorln is equivalent to Println() and logs the message at level Error. +func (e *Entry) Errorln(v ...interface{}) { + e.OutputLevel(ErrorLevel, 2, fmt.Sprintln(v...)) +} + +// Warn is equivalent to Print() and logs the message at level Warning. +func (e *Entry) Warn(v ...interface{}) { + e.OutputLevel(WarnLevel, 2, fmt.Sprint(v...)) +} + +// Warnf is equivalent to Printf() and logs the message at level Warning. +func (e *Entry) Warnf(format string, v ...interface{}) { + e.OutputLevel(WarnLevel, 2, fmt.Sprintf(format, v...)) +} + +// Warnln is equivalent to Println() and logs the message at level Warning. +func (e *Entry) Warnln(v ...interface{}) { + e.OutputLevel(WarnLevel, 2, fmt.Sprintln(v...)) +} + +// Info is equivalent to Print() and logs the message at level Info. +func (e *Entry) Info(v ...interface{}) { + e.OutputLevel(InfoLevel, 2, fmt.Sprint(v...)) +} + +// Infof is equivalent to Printf() and logs the message at level Info. +func (e *Entry) Infof(format string, v ...interface{}) { + e.OutputLevel(InfoLevel, 2, fmt.Sprintf(format, v...)) +} + +// Infoln is equivalent to Println() and logs the message at level Info. +func (e *Entry) Infoln(v ...interface{}) { + e.OutputLevel(InfoLevel, 2, fmt.Sprintln(v...)) +} + +// Debug is equivalent to Print() and logs the message at level Debug. +func (e *Entry) Debug(v ...interface{}) { + e.OutputLevel(DebugLevel, 2, fmt.Sprint(v...)) +} + +// Debugf is equivalent to Printf() and logs the message at level Debug. +func (e *Entry) Debugf(format string, v ...interface{}) { + e.OutputLevel(DebugLevel, 2, fmt.Sprintf(format, v...)) +} + +// Debugln is equivalent to Println() and logs the message at level Debug. +func (e *Entry) Debugln(v ...interface{}) { + e.OutputLevel(DebugLevel, 2, fmt.Sprintln(v...)) +} + +// Trace is equivalent to Print() and logs the message at level Trace. +func (e *Entry) Trace(v ...interface{}) { + e.OutputLevel(TraceLevel, 2, fmt.Sprint(v...)) +} + +// Tracef is equivalent to Printf() and logs the message at level Trace. +func (e *Entry) Tracef(format string, v ...interface{}) { + e.OutputLevel(TraceLevel, 2, fmt.Sprintf(format, v...)) +} + +// Traceln is equivalent to Println() and logs the message at level Trace. +func (e *Entry) Traceln(v ...interface{}) { + e.OutputLevel(TraceLevel, 2, fmt.Sprintln(v...)) +} + +// Output writes the output for a logging event. The string s contains +// the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not +// already a newline. Calldepth is the count of the number of +// frames to skip when computing the file name and line number +// if Llongfile or Lshortfile is set; a value of 1 will print the details +// for the caller of Output. +func (e *Entry) Output(calldepth int, s string) error { + return e.OutputLevel(NoneLevel, calldepth+1, s) // +1 for this frame +} + +// OutputLevel writes the output for a logging event with level. The string s +// contains the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not already +// a newline. Calldepth is the count of the number of frames to skip when +// computing the file name and line number if Llongfile or Lshortfile is set; +// a value of 1 will print the details for the caller of Output. Level is the +// log level for the output. If any formatter is configured for the logger, +// it will be used to format the output. +func (e *Entry) OutputLevel(level Level, calldepth int, s string) error { + var formatter LoggerFormatter + + e.logger.mu.Lock() + if e.logger.rootLogger != nil { + e.logger.rootLogger.mu.Lock() + formatter = e.logger.rootLogger.formatter + e.logger.rootLogger.mu.Unlock() + } + if formatter == nil { + formatter = e.logger.formatter + } + e.logger.mu.Unlock() + + if formatter != nil { + // +1 for this frame. + e.calldepth = calldepth + 1 + e.message = s + e.level = level + + serialized, err := formatter.Format(e) + + if err == nil && serialized != nil { + // if the logger has got a root logger, use the output + // destination of the root logger. + if e.logger.rootLogger != nil { + e.logger.rootLogger.mu.Lock() + _, err = e.logger.rootLogger.out.Write(serialized) + e.logger.rootLogger.mu.Unlock() + } else { + e.logger.mu.Lock() + _, err = e.logger.out.Write(serialized) + e.logger.mu.Unlock() + } + } + + return err + } + + return e.logger.OutputLevel(level, calldepth+1, s) // +1 for this frame. +} diff --git a/src/log/formatter.go b/src/log/formatter.go new file mode 100644 index 00000000000000..b3a8459c88c605 --- /dev/null +++ b/src/log/formatter.go @@ -0,0 +1,7 @@ +package log + +// The LoggerFormatter interface is used to implement a custom formatter. +// So the log output can be customized by implementing this interface. +type LoggerFormatter interface { + Format(entry *Entry) ([]byte, error) +} diff --git a/src/log/formatter_test.go b/src/log/formatter_test.go new file mode 100644 index 00000000000000..12b4ac9e586466 --- /dev/null +++ b/src/log/formatter_test.go @@ -0,0 +1,40 @@ +package log + +import ( + "bytes" + "fmt" + "testing" +) + +type testFormatter struct { +} + +func (f *testFormatter) Format(entry *Entry) ([]byte, error) { + testString := "formatter message: " + entry.Message() + return []byte(testString), nil +} + +func ExampleLoggerWithFormatter() { + var ( + buf bytes.Buffer + logger = New(&buf, "logger: ", Lshortfile) + ) + logger.SetFormatter(&testFormatter{}) + logger.Info("Hello, log file!") + + fmt.Print(&buf) + // Output: + // formatter message: Hello, log file! +} + +func BenchmarkPrintlnWithFormatter(b *testing.B) { + const testString = "test" + var buf bytes.Buffer + l := New(&buf, "", 0) + l.SetFormatter(&testFormatter{}) + + for i := 0; i < b.N; i++ { + buf.Reset() + l.Println(testString) + } +} diff --git a/src/log/log.go b/src/log/log.go index 3172384718880a..9dc0f94f7fb261 100644 --- a/src/log/log.go +++ b/src/log/log.go @@ -15,6 +15,7 @@ package log import ( + "context" "fmt" "io" "os" @@ -43,20 +44,61 @@ const ( Lshortfile // final file name element and line number: d.go:23. overrides Llongfile LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone Lmsgprefix // move the "prefix" from the beginning of the line to before the message + Llevel // logger level LstdFlags = Ldate | Ltime // initial values for the standard logger ) +type Level uint32 + +// These are the different logger levels. You can set the logger level +// on your instance of logger +const ( + NoneLevel Level = -(iota + 1) + PanicLevel + FatalLevel + ErrorLevel + WarnLevel + InfoLevel + DebugLevel + TraceLevel +) + +// These are level texts corresponding to logger levels. +var levelText = map[Level]string{ + // Print methods have none log level and they are considered as INFO by default. + // This is the level text for the print methods. + NoneLevel: "INFO", + PanicLevel: "PANIC", + FatalLevel: "FATAL", + ErrorLevel: "ERROR", + WarnLevel: "WARN", + InfoLevel: "INFO", + DebugLevel: "DEBUG", + TraceLevel: "TRACE", +} + +// LevelText returns a text for the logger level. It returns the empty +// string if the level is unknown. +func LevelText(code Level) string { + return levelText[code] +} + // A Logger represents an active logging object that generates lines of // output to an io.Writer. Each logging operation 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; protects the following fields - prefix string // prefix on each line to identify the logger (but see Lmsgprefix) - flag int // properties - out io.Writer // destination for output - buf []byte // for accumulating text to write - isDiscard int32 // atomic boolean: whether out == io.Discard + mu sync.Mutex // ensures atomic writes; protects the following fields + prefix string // prefix on each line to identify the logger (but see Lmsgprefix) + flag int // properties + out io.Writer // destination for output + buf []byte // for accumulating text to write + isDiscard int32 // atomic boolean: whether out == io.Discard + level Level // logger level + ctx context.Context // logger context + formatter LoggerFormatter // logger formatter to format the log output + rootLogger *Logger // root logger for logger + entryPool sync.Pool // entry pool } // New creates a new Logger. The out variable sets the @@ -65,23 +107,47 @@ type Logger struct { // after the log header if the Lmsgprefix flag is provided. // The flag argument defines the logging properties. func New(out io.Writer, prefix string, flag int) *Logger { - l := &Logger{out: out, prefix: prefix, flag: flag} - if out == io.Discard { - l.isDiscard = 1 - } + l := &Logger{out: out, prefix: prefix, flag: flag, level: DebugLevel} + return l } +// GetLogger returns a new Logger with a root logger and a different prefix. +// The logger will write the same output destination as the root logger. +// However, it has got a separate context and a logger level that can be configured. +func (l *Logger) GetLogger(prefix string) *Logger { + rootLogger := l.RootLogger() + + if rootLogger == nil { + rootLogger = l + } + + return &Logger{prefix: prefix, rootLogger: rootLogger, level: DebugLevel} +} + +// RootLogger returns the root logger for the logger. +func (l *Logger) RootLogger() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + return l.rootLogger +} + +// WithContext creates an entry from the logger and adds a context to it. +func (l *Logger) WithContext(ctx context.Context) *Entry { + entry := l.newEntry() + entry.context = ctx + return entry +} + // SetOutput sets the output destination for the logger. +// If the logger has got a root logger, the output destination will not be set +// because the output destination of the root logger is used. func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() defer l.mu.Unlock() - l.out = w - isDiscard := int32(0) - if w == io.Discard { - isDiscard = 1 + if l.rootLogger == nil { + l.out = w } - atomic.StoreInt32(&l.isDiscard, isDiscard) } var std = New(os.Stderr, "", LstdFlags) @@ -109,17 +175,18 @@ func itoa(buf *[]byte, i int, wid int) { // formatHeader writes log header to buf in following order: // * l.prefix (if it's not blank and Lmsgprefix is unset), // * date and/or time (if corresponding flags are provided), +// * log level (if corresponding flags are provided) // * file and line number (if corresponding flags are provided), // * l.prefix (if it's not blank and Lmsgprefix is set). -func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { - if l.flag&Lmsgprefix == 0 { +func (l *Logger) formatHeader(flag int, buf *[]byte, t time.Time, file string, line int, level Level) { + if flag&Lmsgprefix == 0 { *buf = append(*buf, l.prefix...) } - if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { - if l.flag&LUTC != 0 { + if flag&(Ldate|Ltime|Lmicroseconds) != 0 { + if flag&LUTC != 0 { t = t.UTC() } - if l.flag&Ldate != 0 { + if flag&Ldate != 0 { year, month, day := t.Date() itoa(buf, year, 4) *buf = append(*buf, '/') @@ -128,22 +195,25 @@ func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { itoa(buf, day, 2) *buf = append(*buf, ' ') } - if l.flag&(Ltime|Lmicroseconds) != 0 { + if flag&(Ltime|Lmicroseconds) != 0 { hour, min, sec := t.Clock() itoa(buf, hour, 2) *buf = append(*buf, ':') itoa(buf, min, 2) *buf = append(*buf, ':') itoa(buf, sec, 2) - if l.flag&Lmicroseconds != 0 { + if flag&Lmicroseconds != 0 { *buf = append(*buf, '.') itoa(buf, t.Nanosecond()/1e3, 6) } *buf = append(*buf, ' ') } } - if l.flag&(Lshortfile|Llongfile) != 0 { - if l.flag&Lshortfile != 0 { + if flag&Llevel != 0 { + *buf = append(*buf, fmt.Sprintf("%-5s ", levelText[level])...) + } + if flag&(Lshortfile|Llongfile) != 0 { + if flag&Lshortfile != 0 { short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { @@ -158,7 +228,7 @@ func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { itoa(buf, line, -1) *buf = append(*buf, ": "...) } - if l.flag&Lmsgprefix != 0 { + if flag&Lmsgprefix != 0 { *buf = append(*buf, l.prefix...) } } @@ -166,16 +236,87 @@ func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { // Output writes the output for a logging event. The string s contains // the text to print after the prefix specified by the flags of the // Logger. A newline is appended if the last character of s is not -// already a newline. Calldepth is used to recover the PC and is -// provided for generality, although at the moment on all pre-defined -// paths it will be 2. +// already a newline. Calldepth is the count of the number of +// frames to skip when computing the file name and line number +// if Llongfile or Lshortfile is set; a value of 1 will print the details +// for the caller of Output. func (l *Logger) Output(calldepth int, s string) error { + return l.OutputLevel(NoneLevel, calldepth+1, s) // +1 for this frame +} + +// OutputLevel writes the output for a logging event with level. The string s +// contains the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not already +// a newline. Calldepth is the count of the number of frames to skip when +// computing the file name and line number if Llongfile or Lshortfile is set; +// a value of 1 will print the details for the caller of Output. Level is the +// log level for the output. If any formatter is configured for the logger, +// it will be used to format the output. +func (l *Logger) OutputLevel(level Level, calldepth int, s string) error { + var formatter LoggerFormatter + + l.mu.Lock() + if l.rootLogger != nil { + l.rootLogger.mu.Lock() + formatter = l.rootLogger.formatter + l.rootLogger.mu.Unlock() + } + if formatter == nil { + formatter = l.formatter + } + l.mu.Unlock() + + if formatter != nil { + entry := l.newEntry() + entry.calldepth = calldepth + 1 // +1 for this frame + entry.message = s + entry.logger = l + entry.context = nil + entry.level = level + + serialized, err := formatter.Format(entry) + + if err == nil && serialized != nil { + l.mu.Lock() + if l.rootLogger != nil { + l.rootLogger.mu.Lock() + _, err = l.rootLogger.out.Write(serialized) + l.rootLogger.mu.Unlock() + } else { + _, err = l.out.Write(serialized) + } + l.mu.Unlock() + } + + l.releaseEntry(entry) + return err + } + now := time.Now() // get this early. var file string var line int l.mu.Lock() defer l.mu.Unlock() - if l.flag&(Lshortfile|Llongfile) != 0 { + + var flag int + + if l.rootLogger != nil { + l.rootLogger.mu.Lock() + flag = l.rootLogger.flag + l.rootLogger.mu.Unlock() + } else { + flag = l.flag + } + + if flag&Llevel != 0 { + if level == NoneLevel && l.level < InfoLevel { + return nil + } else if level != NoneLevel && l.level < level { + return nil + } + } + + if flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it's expensive. l.mu.Unlock() var ok bool @@ -187,12 +328,20 @@ func (l *Logger) Output(calldepth int, s string) error { l.mu.Lock() } l.buf = l.buf[:0] - l.formatHeader(&l.buf, now, file, line) + l.formatHeader(flag, &l.buf, now, file, line, level) 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) + + var err error + if l.rootLogger != nil { + l.rootLogger.mu.Lock() + _, err = l.rootLogger.out.Write(l.buf) + l.rootLogger.mu.Unlock() + } else { + _, err = l.out.Write(l.buf) + } return err } @@ -202,7 +351,7 @@ func (l *Logger) Printf(format string, v ...interface{}) { if atomic.LoadInt32(&l.isDiscard) != 0 { return } - l.Output(2, fmt.Sprintf(format, v...)) + l.OutputLevel(NoneLevel, 2, fmt.Sprintf(format, v...)) } // Print calls l.Output to print to the logger. @@ -211,7 +360,7 @@ func (l *Logger) Print(v ...interface{}) { if atomic.LoadInt32(&l.isDiscard) != 0 { return } - l.Output(2, fmt.Sprint(v...)) + l.OutputLevel(NoneLevel, 2, fmt.Sprint(v...)) } // Println calls l.Output to print to the logger. @@ -220,62 +369,158 @@ func (l *Logger) Println(v ...interface{}) { if atomic.LoadInt32(&l.isDiscard) != 0 { return } - l.Output(2, fmt.Sprintln(v...)) + l.OutputLevel(NoneLevel, 2, fmt.Sprintln(v...)) } // Fatal is equivalent to l.Print() followed by a call to os.Exit(1). func (l *Logger) Fatal(v ...interface{}) { - l.Output(2, fmt.Sprint(v...)) + l.OutputLevel(FatalLevel, 2, fmt.Sprint(v...)) os.Exit(1) } // Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1). func (l *Logger) Fatalf(format string, v ...interface{}) { - l.Output(2, fmt.Sprintf(format, v...)) + l.OutputLevel(FatalLevel, 2, fmt.Sprintf(format, v...)) os.Exit(1) } // Fatalln is equivalent to l.Println() followed by a call to os.Exit(1). func (l *Logger) Fatalln(v ...interface{}) { - l.Output(2, fmt.Sprintln(v...)) + l.OutputLevel(FatalLevel, 2, fmt.Sprintln(v...)) os.Exit(1) } // Panic is equivalent to l.Print() followed by a call to panic(). func (l *Logger) Panic(v ...interface{}) { s := fmt.Sprint(v...) - l.Output(2, s) + l.OutputLevel(PanicLevel, 2, s) panic(s) } // Panicf is equivalent to l.Printf() followed by a call to panic(). func (l *Logger) Panicf(format string, v ...interface{}) { s := fmt.Sprintf(format, v...) - l.Output(2, s) + l.OutputLevel(PanicLevel, 2, s) panic(s) } // Panicln is equivalent to l.Println() followed by a call to panic(). func (l *Logger) Panicln(v ...interface{}) { s := fmt.Sprintln(v...) - l.Output(2, s) + l.OutputLevel(PanicLevel, 2, s) panic(s) } +// Error is equivalent to Print() and logs the message at level Error. +func (l *Logger) Error(v ...interface{}) { + s := fmt.Sprint(v...) + l.OutputLevel(ErrorLevel, 2, s) +} + +// Errorf is equivalent to Printf() and logs the message at level Error. +func (l *Logger) Errorf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.OutputLevel(ErrorLevel, 2, s) +} + +// Errorln is equivalent to Println() and logs the message at level Error. +func (l *Logger) Errorln(v ...interface{}) { + l.OutputLevel(ErrorLevel, 2, fmt.Sprintln(v...)) +} + +// Warn is equivalent to Print() and logs the message at level Warning. +func (l *Logger) Warn(v ...interface{}) { + s := fmt.Sprint(v...) + l.OutputLevel(WarnLevel, 2, s) +} + +// Warnf is equivalent to Printf() and logs the message at level Warning. +func (l *Logger) Warnf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.OutputLevel(WarnLevel, 2, s) +} + +// Warnln is equivalent to Println() and logs the message at level Warning. +func (l *Logger) Warnln(v ...interface{}) { + l.OutputLevel(WarnLevel, 2, fmt.Sprintln(v...)) +} + +// Info is equivalent to Print() and logs the message at level Info. +func (l *Logger) Info(v ...interface{}) { + s := fmt.Sprint(v...) + l.OutputLevel(InfoLevel, 2, s) +} + +// Infof is equivalent to Printf() and logs the message at level Info. +func (l *Logger) Infof(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.OutputLevel(InfoLevel, 2, s) +} + +// Infoln is equivalent to Println() and logs the message at level Info. +func (l *Logger) Infoln(v ...interface{}) { + l.OutputLevel(InfoLevel, 2, fmt.Sprintln(v...)) +} + +// Debug is equivalent to Print() and logs the message at level Debug. +func (l *Logger) Debug(v ...interface{}) { + s := fmt.Sprint(v...) + l.OutputLevel(DebugLevel, 2, s) +} + +// Debugf is equivalent to Printf() and logs the message at level Debug. +func (l *Logger) Debugf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.OutputLevel(DebugLevel, 2, s) +} + +// Debugln is equivalent to Println() and logs the message at level Debug. +func (l *Logger) Debugln(v ...interface{}) { + l.OutputLevel(DebugLevel, 2, fmt.Sprintln(v...)) +} + +// Trace is equivalent to Print() and logs the message at level Trace. +func (l *Logger) Trace(v ...interface{}) { + s := fmt.Sprint(v...) + l.OutputLevel(TraceLevel, 2, s) +} + +// Tracef is equivalent to Printf() and logs the message at level Trace. +func (l *Logger) Tracef(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.OutputLevel(TraceLevel, 2, s) +} + +// Traceln is equivalent to Println() and logs the message at level Trace. +func (l *Logger) Traceln(v ...interface{}) { + l.OutputLevel(TraceLevel, 2, fmt.Sprintln(v...)) +} + // Flags returns the output flags for the logger. // The flag bits are Ldate, Ltime, and so on. +// If the logger has got a root logger, the output flags of the root +// logger are returned. func (l *Logger) Flags() int { l.mu.Lock() defer l.mu.Unlock() + if l.rootLogger != nil { + l.rootLogger.mu.Lock() + defer l.rootLogger.mu.Unlock() + return l.rootLogger.flag + } return l.flag } // SetFlags sets the output flags for the logger. // The flag bits are Ldate, Ltime, and so on. +// If the logger has got a root logger, the output flags will not be set +// because the output flags of the root logger is used. func (l *Logger) SetFlags(flag int) { l.mu.Lock() defer l.mu.Unlock() - l.flag = flag + if l.rootLogger == nil { + l.flag = flag + } } // Prefix returns the output prefix for the logger. @@ -292,16 +537,110 @@ func (l *Logger) SetPrefix(prefix string) { l.prefix = prefix } +// LoggerLevel returns the log level for the logger. +func (l *Logger) LoggerLevel() Level { + l.mu.Lock() + defer l.mu.Unlock() + return l.level +} + +// SetLoggerLevel sets the log level for the logger. +func (l *Logger) SetLoggerLevel(level Level) { + l.mu.Lock() + defer l.mu.Unlock() + l.level = level +} + +// Context returns the context for the logger. +func (l *Logger) Context() context.Context { + l.mu.Lock() + defer l.mu.Unlock() + return l.ctx +} + +// SetContext sets the context for the logger. +func (l *Logger) SetContext(ctx context.Context) { + l.mu.Lock() + defer l.mu.Unlock() + l.ctx = ctx +} + +// Formatter returns the formatter for the logger. +// If the logger has got a root logger, the formatter of the root +// logger is returned. +func (l *Logger) Formatter() LoggerFormatter { + l.mu.Lock() + defer l.mu.Unlock() + + if l.rootLogger != nil { + l.rootLogger.mu.Lock() + defer l.rootLogger.mu.Unlock() + return l.rootLogger.formatter + } + + return l.formatter +} + +// SetFormatter sets the formatter for the logger. +// If the logger has got a root logger, the formatter will not be set +// because the formatter of the root logger is used. +func (l *Logger) SetFormatter(formatter LoggerFormatter) { + l.mu.Lock() + defer l.mu.Unlock() + + if l.rootLogger == nil { + l.formatter = formatter + } +} + // Writer returns the output destination for the logger. +// If the logger has got a root logger, the output destination +// of the root logger is returned. func (l *Logger) Writer() io.Writer { l.mu.Lock() defer l.mu.Unlock() + if l.rootLogger != nil { + return l.rootLogger.Writer() + } return l.out } +// newEntry either gets an entry from the pool or creates a new entry. +func (l *Logger) newEntry() *Entry { + entry, ok := l.entryPool.Get().(*Entry) + if ok { + return entry + } + return NewEntry(l) +} + +// releaseEntry puts the entry into the pool. +func (l *Logger) releaseEntry(entry *Entry) { + l.entryPool.Put(entry) +} + +// GetLogger returns a new Logger whose root logger is the standard logger. +// The logger will write the same output destination as the standard logger. +// However, it has got a separate context and a logger level that can be configured. +func GetLogger(prefix string) *Logger { + return std.GetLogger(prefix) +} + +// RootLogger returns the root logger of the standard logger. +func RootLogger() *Logger { + return std.RootLogger() +} + +// WithContext creates an entry from the standard logger and adds a context to it. +func WithContext(ctx context.Context) *Entry { + return std.WithContext(ctx) +} + // SetOutput sets the output destination for the standard logger. func SetOutput(w io.Writer) { - std.SetOutput(w) + std.mu.Lock() + defer std.mu.Unlock() + std.out = w } // Flags returns the output flags for the standard logger. @@ -326,6 +665,36 @@ func SetPrefix(prefix string) { std.SetPrefix(prefix) } +// LoggerLevel returns the logger level for the standard logger. +func LoggerLevel() Level { + return std.LoggerLevel() +} + +// SetLoggerLevel sets the logger level for the standard logger. +func SetLoggerLevel(level Level) { + std.SetLoggerLevel(level) +} + +// Context returns the context for the standard logger. +func Context() context.Context { + return std.Context() +} + +// SetContext sets the context for the standard logger. +func SetContext(ctx context.Context) { + std.SetContext(ctx) +} + +// Formatter returns the logger formatter for the standard logger. +func Formatter() LoggerFormatter { + return std.Formatter() +} + +// SetFormatter sets the logger formatter for the standard logger. +func SetFormatter(formatter LoggerFormatter) { + std.SetFormatter(formatter) +} + // Writer returns the output destination for the standard logger. func Writer() io.Writer { return std.Writer() @@ -339,7 +708,7 @@ func Print(v ...interface{}) { if atomic.LoadInt32(&std.isDiscard) != 0 { return } - std.Output(2, fmt.Sprint(v...)) + std.OutputLevel(NoneLevel, 2, fmt.Sprint(v...)) } // Printf calls Output to print to the standard logger. @@ -348,7 +717,7 @@ func Printf(format string, v ...interface{}) { if atomic.LoadInt32(&std.isDiscard) != 0 { return } - std.Output(2, fmt.Sprintf(format, v...)) + std.OutputLevel(NoneLevel, 2, fmt.Sprintf(format, v...)) } // Println calls Output to print to the standard logger. @@ -357,48 +726,138 @@ func Println(v ...interface{}) { if atomic.LoadInt32(&std.isDiscard) != 0 { return } - std.Output(2, fmt.Sprintln(v...)) + std.OutputLevel(NoneLevel, 2, fmt.Sprintln(v...)) } // Fatal is equivalent to Print() followed by a call to os.Exit(1). func Fatal(v ...interface{}) { - std.Output(2, fmt.Sprint(v...)) + std.OutputLevel(FatalLevel, 2, fmt.Sprint(v...)) os.Exit(1) } // Fatalf is equivalent to Printf() followed by a call to os.Exit(1). func Fatalf(format string, v ...interface{}) { - std.Output(2, fmt.Sprintf(format, v...)) + std.OutputLevel(FatalLevel, 2, fmt.Sprintf(format, v...)) os.Exit(1) } // Fatalln is equivalent to Println() followed by a call to os.Exit(1). func Fatalln(v ...interface{}) { - std.Output(2, fmt.Sprintln(v...)) + std.OutputLevel(FatalLevel, 2, fmt.Sprintln(v...)) os.Exit(1) } // Panic is equivalent to Print() followed by a call to panic(). func Panic(v ...interface{}) { s := fmt.Sprint(v...) - std.Output(2, s) + std.OutputLevel(PanicLevel, 2, s) panic(s) } // Panicf is equivalent to Printf() followed by a call to panic(). func Panicf(format string, v ...interface{}) { s := fmt.Sprintf(format, v...) - std.Output(2, s) + std.OutputLevel(PanicLevel, 2, s) panic(s) } // Panicln is equivalent to Println() followed by a call to panic(). func Panicln(v ...interface{}) { s := fmt.Sprintln(v...) - std.Output(2, s) + std.OutputLevel(PanicLevel, 2, s) panic(s) } +// Error logs a message at level Error on the standard logger. +func Error(v ...interface{}) { + s := fmt.Sprint(v...) + std.OutputLevel(ErrorLevel, 2, s) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.OutputLevel(ErrorLevel, 2, s) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.OutputLevel(ErrorLevel, 2, s) +} + +// Warn logs a message at level Warning on the standard logger. +func Warn(v ...interface{}) { + s := fmt.Sprint(v...) + std.OutputLevel(WarnLevel, 2, s) +} + +// Warnf logs a message at level Warning on the standard logger. +func Warnf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.OutputLevel(WarnLevel, 2, s) +} + +// Warnln logs a message at level Warning on the standard logger. +func Warnln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.OutputLevel(WarnLevel, 2, s) +} + +// Info logs a message at level Info on the standard logger. +func Info(v ...interface{}) { + s := fmt.Sprint(v...) + std.OutputLevel(InfoLevel, 2, s) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.OutputLevel(InfoLevel, 2, s) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.OutputLevel(InfoLevel, 2, s) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(v ...interface{}) { + s := fmt.Sprint(v...) + std.OutputLevel(DebugLevel, 2, s) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.OutputLevel(DebugLevel, 2, s) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.OutputLevel(DebugLevel, 2, s) +} + +// Trace logs a message at level Trace on the standard logger. +func Trace(v ...interface{}) { + s := fmt.Sprint(v...) + std.OutputLevel(TraceLevel, 2, s) +} + +// Tracef logs a message at level Trace on the standard logger. +func Tracef(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.OutputLevel(TraceLevel, 2, s) +} + +// Traceln logs a message at level Trace on the standard logger. +func Traceln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.OutputLevel(TraceLevel, 2, s) +} + // Output writes the output for a logging event. The string s contains // the text to print after the prefix specified by the flags of the // Logger. A newline is appended if the last character of s is not @@ -407,5 +866,17 @@ func Panicln(v ...interface{}) { // if Llongfile or Lshortfile is set; a value of 1 will print the details // for the caller of Output. func Output(calldepth int, s string) error { - return std.Output(calldepth+1, s) // +1 for this frame. + return std.OutputLevel(NoneLevel, calldepth+1, s) // +1 for this frame. +} + +// OutputLevel writes the output for a logging event with level. The string s +// contains the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not already +// a newline. Calldepth is the count of the number of frames to skip when +// computing the file name and line number if Llongfile or Lshortfile is set; +// a value of 1 will print the details for the caller of Output. Level is the +// log level for the output. If any formatter is configured for the logger, +// it will be used to format the output. +func OutputLevel(level Level, calldepth int, s string) error { + return std.OutputLevel(level, calldepth+1, s) // +1 for this frame. } diff --git a/src/log/log_test.go b/src/log/log_test.go index 938ed423578cb8..381c95ad57de7b 100644 --- a/src/log/log_test.go +++ b/src/log/log_test.go @@ -21,9 +21,10 @@ const ( Rdate = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]` Rtime = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]` Rmicroseconds = `\.[0-9][0-9][0-9][0-9][0-9][0-9]` - Rline = `(61|63):` // must update if the calls to l.Printf / l.Print below move + Rline = `(63|65):` // must update if the calls to l.Printf / l.Print below move Rlongfile = `.*/[A-Za-z0-9_\-]+\.go:` + Rline Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline + Rlevel = `(PANIC|FATAL|ERROR|WARN |INFO |DEBUG|TRACE)` ) type tester struct { @@ -44,11 +45,12 @@ var tests = []tester{ {Llongfile, "", Rlongfile + " "}, {Lshortfile, "", Rshortfile + " "}, {Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile + {Llevel, "", Rlevel + " "}, // everything at once: - {Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "}, - {Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "}, - {Ldate | Ltime | Lmicroseconds | Llongfile | Lmsgprefix, "XXX", Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " XXX"}, - {Ldate | Ltime | Lmicroseconds | Lshortfile | Lmsgprefix, "XXX", Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " XXX"}, + {Ldate | Ltime | Lmicroseconds | Llongfile | Llevel, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlevel + " " + Rlongfile + " "}, + {Ldate | Ltime | Lmicroseconds | Lshortfile | Llevel, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlevel + " " + Rshortfile + " "}, + {Ldate | Ltime | Lmicroseconds | Llongfile | Lmsgprefix | Llevel, "XXX", Rdate + " " + Rtime + Rmicroseconds + " " + Rlevel + " " + Rlongfile + " XXX"}, + {Ldate | Ltime | Lmicroseconds | Lshortfile | Lmsgprefix | Llevel, "XXX", Rdate + " " + Rtime + Rmicroseconds + " " + Rlevel + " " + Rshortfile + " XXX"}, } // Test using Println("hello", 23, "world") or using Printf("hello %d world", 23) @@ -224,3 +226,55 @@ func BenchmarkPrintlnNoFlags(b *testing.B) { l.Println(testString) } } + +func TestLevelFlag(t *testing.T) { + var b bytes.Buffer + l := New(&b, "", LstdFlags) + l.SetFlags(Llevel) + l.SetLoggerLevel(DebugLevel) + + l.Debug("hello") + want := fmt.Sprintf("DEBUG hello\n") + got := b.String() + if got == want { + return + } + t.Errorf("got %q; want %q", got, want) + b.Reset() + + // trace log will not be printed because the logger level is set to Debug + l.Trace("hello") + want = fmt.Sprintf("") + got = b.String() + if got == want { + return + } + t.Errorf("got %q; want %q", got, want) + b.Reset() + + l.Info("hello") + want = fmt.Sprintf("INFO hello\n") + got = b.String() + if got == want { + return + } + t.Errorf("got %q; want %q", got, want) + b.Reset() + + l.Error("hello") + want = fmt.Sprintf("ERROR hello\n") + got = b.String() + if got == want { + return + } + t.Errorf("got %q; want %q", got, want) + b.Reset() + + l.Warn("hello") + want = fmt.Sprintf("WARN hello\n") + got = b.String() + if got == want { + return + } + t.Errorf("got %q; want %q", got, want) +}