Skip to content

log: improve logger implementation #48464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions src/log/entry.go
Original file line number Diff line number Diff line change
@@ -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.
}
7 changes: 7 additions & 0 deletions src/log/formatter.go
Original file line number Diff line number Diff line change
@@ -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)
}
40 changes: 40 additions & 0 deletions src/log/formatter_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading