From 616dd629ad9086328f9f7cdc261c0d14d2bee661 Mon Sep 17 00:00:00 2001 From: Rohan Date: Tue, 5 Jul 2022 15:03:40 +0530 Subject: [PATCH] feat: remove pointer receiver --- benchmark_test.go | 33 +++++---------- examples/main.go | 3 +- field_logger.go | 13 +++--- log.go | 102 +++++++++++++++++++++++++++------------------- log_test.go | 11 +++-- 5 files changed, 84 insertions(+), 78 deletions(-) diff --git a/benchmark_test.go b/benchmark_test.go index acf611c..be88bfd 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -9,8 +9,7 @@ import ( ) func BenchmarkNoField(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(p *testing.PB) { @@ -21,8 +20,7 @@ func BenchmarkNoField(b *testing.B) { } func BenchmarkOneField(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) b.ReportAllocs() b.ResetTimer() b.RunParallel(func(p *testing.PB) { @@ -33,8 +31,7 @@ func BenchmarkOneField(b *testing.B) { } func BenchmarkThreeFields(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) b.ReportAllocs() b.ResetTimer() @@ -50,8 +47,7 @@ func BenchmarkThreeFields(b *testing.B) { } func BenchmarkErrorField(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) b.ReportAllocs() b.ResetTimer() @@ -65,8 +61,7 @@ func BenchmarkErrorField(b *testing.B) { } func BenchmarkHugePayload(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) b.ReportAllocs() b.ResetTimer() @@ -89,8 +84,7 @@ func BenchmarkHugePayload(b *testing.B) { } func BenchmarkThreeFields_WithCaller(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) logger.SetCallerFrame(true, 3) b.ReportAllocs() b.ResetTimer() @@ -107,8 +101,7 @@ func BenchmarkThreeFields_WithCaller(b *testing.B) { } func BenchmarkNoField_WithColor(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) logger.SetColorOutput(true) b.ReportAllocs() b.ResetTimer() @@ -121,8 +114,7 @@ func BenchmarkNoField_WithColor(b *testing.B) { } func BenchmarkOneField_WithColor(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) logger.SetColorOutput(true) b.ReportAllocs() b.ResetTimer() @@ -134,8 +126,7 @@ func BenchmarkOneField_WithColor(b *testing.B) { } func BenchmarkThreeFields_WithColor(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) logger.SetColorOutput(true) b.ReportAllocs() b.ResetTimer() @@ -152,8 +143,7 @@ func BenchmarkThreeFields_WithColor(b *testing.B) { } func BenchmarkErrorField_WithColor(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) logger.SetColorOutput(true) b.ReportAllocs() b.ResetTimer() @@ -168,8 +158,7 @@ func BenchmarkErrorField_WithColor(b *testing.B) { } func BenchmarkHugePayload_WithColor(b *testing.B) { - logger := logf.New() - logger.SetWriter(io.Discard) + logger := logf.New(io.Discard) logger.SetColorOutput(true) b.ReportAllocs() b.ResetTimer() diff --git a/examples/main.go b/examples/main.go index bf7c2fe..9575fe2 100644 --- a/examples/main.go +++ b/examples/main.go @@ -2,13 +2,14 @@ package main import ( "errors" + "os" "time" "github.com/zerodha/logf" ) func main() { - logger := logf.New() + logger := logf.New(os.Stderr) // Basic log. logger.Info("starting app") diff --git a/field_logger.go b/field_logger.go index 7a3fcb6..30b08d3 100644 --- a/field_logger.go +++ b/field_logger.go @@ -4,31 +4,32 @@ import "os" type FieldLogger struct { fields Fields - logger *Logger + logger Logger } -func (l *FieldLogger) Debug(msg string) { +// Debug emits a debug log line. +func (l FieldLogger) Debug(msg string) { l.logger.handleLog(msg, DebugLevel, l.fields) } // Info emits a info log line. -func (l *FieldLogger) Info(msg string) { +func (l FieldLogger) Info(msg string) { l.logger.handleLog(msg, InfoLevel, l.fields) } // Warn emits a warning log line. -func (l *FieldLogger) Warn(msg string) { +func (l FieldLogger) Warn(msg string) { l.logger.handleLog(msg, WarnLevel, l.fields) } // Error emits an error log line. -func (l *FieldLogger) Error(msg string) { +func (l FieldLogger) Error(msg string) { l.logger.handleLog(msg, ErrorLevel, l.fields) } // Fatal emits a fatal level log line. // It aborts the current program with an exit code of 1. -func (l *FieldLogger) Fatal(msg string) { +func (l FieldLogger) Fatal(msg string) { l.logger.handleLog(msg, ErrorLevel, l.fields) os.Exit(1) } diff --git a/log.go b/log.go index e749535..6235e21 100644 --- a/log.go +++ b/log.go @@ -26,25 +26,12 @@ var ( // Logger is the interface for all log operations // related to emitting logs. type Logger struct { - mu sync.Mutex // Atomic writes. - out io.Writer // Output destination. - level Level // Verbosity of logs. - tsFormat string // Timestamp format. - enableColor bool // Colored output. - enableCaller bool // Print caller information. - callerSkipFrameCount int // Number of frames to skip when detecting caller -} - -// Opts represents various properties -// to configure logger. -type Opts struct { - Writer io.Writer - Lvl Level - TimestampFormat string - EnableColor bool - EnableCaller bool - // CallerSkipFrameCount is the count of the number of frames to skip when computing the file name and line number - CallerSkipFrameCount int + out io.Writer // Output destination. + level Level // Verbosity of logs. + tsFormat string // Timestamp format. + enableColor bool // Colored output. + enableCaller bool // Print caller information. + callerSkipFrameCount int // Number of frames to skip when detecting caller } // Fields is a map of arbitrary KV pairs @@ -82,10 +69,14 @@ var colorLvlMap = [...]string{ // New instantiates a logger object. // It writes to `stderr` as the default and it's non configurable. -func New() *Logger { +func New(out io.Writer) Logger { // Initialise logger with sane defaults. - return &Logger{ - out: os.Stderr, + if out == nil { + out = os.Stderr + } + + return Logger{ + out: newSyncWriter(out), level: InfoLevel, tsFormat: defaultTSFormat, enableColor: false, @@ -94,6 +85,31 @@ func New() *Logger { } } +// syncWriter is a wrapper around io.Writer that +// synchronizes writes using a mutex. +type syncWriter struct { + sync.Mutex + w io.Writer +} + +// Write synchronously to the underlying io.Writer. +func (w *syncWriter) Write(p []byte) (int, error) { + w.Lock() + n, err := w.w.Write(p) + w.Unlock() + return n, err +} + +// newSyncWriter wraps an io.Writer with syncWriter. It can +// be used as an io.Writer as syncWriter satisfies the io.Writer interface. +func newSyncWriter(in io.Writer) *syncWriter { + if in == nil { + return &syncWriter{w: os.Stderr} + } + + return &syncWriter{w: in} +} + // String representation of the log severity. func (l Level) String() string { switch l { @@ -114,73 +130,75 @@ func (l Level) String() string { // SetLevel sets the verbosity for logger. // Verbosity can be dynamically changed by the caller. -func (l *Logger) SetLevel(lvl Level) { +func (l Logger) SetLevel(lvl Level) Logger { l.level = lvl + return l } // SetWriter sets the output writer for the logger -func (l *Logger) SetWriter(w io.Writer) { - l.mu.Lock() - l.out = w - l.mu.Unlock() +func (l Logger) SetWriter(w io.Writer) Logger { + l.out = &syncWriter{w: w} + return l } // SetTimestampFormat sets the timestamp format for the `timestamp` key. -func (l *Logger) SetTimestampFormat(f string) { +func (l Logger) SetTimestampFormat(f string) Logger { l.tsFormat = f + return l } // SetColorOutput enables/disables colored output. -func (l *Logger) SetColorOutput(color bool) { +func (l Logger) SetColorOutput(color bool) Logger { l.enableColor = color + return l } // SetCallerFrame enables/disables the caller source in the log line. -func (l *Logger) SetCallerFrame(caller bool, depth int) { +func (l Logger) SetCallerFrame(caller bool, depth int) Logger { l.enableCaller = caller l.callerSkipFrameCount = depth + return l } // Debug emits a debug log line. -func (l *Logger) Debug(msg string) { +func (l Logger) Debug(msg string) { l.handleLog(msg, DebugLevel, nil) } // Info emits a info log line. -func (l *Logger) Info(msg string) { +func (l Logger) Info(msg string) { l.handleLog(msg, InfoLevel, nil) } // Warn emits a warning log line. -func (l *Logger) Warn(msg string) { +func (l Logger) Warn(msg string) { l.handleLog(msg, WarnLevel, nil) } // Error emits an error log line. -func (l *Logger) Error(msg string) { +func (l Logger) Error(msg string) { l.handleLog(msg, ErrorLevel, nil) } // Fatal emits a fatal level log line. // It aborts the current program with an exit code of 1. -func (l *Logger) Fatal(msg string) { +func (l Logger) Fatal(msg string) { l.handleLog(msg, FatalLevel, nil) os.Exit(1) } // WithFields returns a new entry with `fields` set. -func (l *Logger) WithFields(fields Fields) *FieldLogger { - fl := &FieldLogger{ +func (l Logger) WithFields(fields Fields) FieldLogger { + return FieldLogger{ fields: fields, logger: l, } - return fl } // WithError returns a Logger with the "error" key set to `err`. -func (l *Logger) WithError(err error) *FieldLogger { +func (l Logger) WithError(err error) FieldLogger { if err == nil { - return &FieldLogger{logger: l} + return FieldLogger{logger: l} } return l.WithFields(Fields{ @@ -190,7 +208,7 @@ func (l *Logger) WithError(err error) *FieldLogger { // handleLog emits the log after filtering log level // and applying formatting of the fields. -func (l *Logger) handleLog(msg string, lvl Level, fields Fields) { +func (l Logger) handleLog(msg string, lvl Level, fields Fields) { // Discard the log if the verbosity is higher. // For eg, if the lvl is `3` (error), but the incoming message is `0` (debug), skip it. if lvl < l.level { @@ -221,13 +239,11 @@ func (l *Logger) handleLog(msg string, lvl Level, fields Fields) { } buf.AppendString("\n") - l.mu.Lock() _, err := l.out.Write(buf.Bytes()) if err != nil { // Should ideally never happen. stdlog.Printf("error logging: %v", err) } - l.mu.Unlock() buf.Reset() diff --git a/log_test.go b/log_test.go index ea3999d..29c8979 100644 --- a/log_test.go +++ b/log_test.go @@ -3,6 +3,7 @@ package logf import ( "bytes" "errors" + "os" "strconv" "sync" "testing" @@ -37,7 +38,7 @@ func TestLevelParsing(t *testing.T) { } func TestNewLoggerDefault(t *testing.T) { - l := New() + l := New(os.Stderr) assert.Equal(t, l.level, InfoLevel, "level is info") assert.Equal(t, l.enableColor, false, "color output is disabled") assert.Equal(t, l.enableCaller, false, "caller is disabled") @@ -47,8 +48,7 @@ func TestNewLoggerDefault(t *testing.T) { func TestLogFormat(t *testing.T) { buf := &bytes.Buffer{} - l := New() - l.SetWriter(buf) + l := New(buf) l.SetColorOutput(false) // Info log. @@ -79,8 +79,7 @@ func TestLogFormat(t *testing.T) { // These test are typically meant to be run with the data race detector. func TestLoggerConcurrency(t *testing.T) { buf := &bytes.Buffer{} - l := New() - l.SetWriter(buf) + l := New(buf) l.SetColorOutput(false) for _, n := range []int{10, 100, 1000} { @@ -93,7 +92,7 @@ func TestLoggerConcurrency(t *testing.T) { } } -func genLogs(l *Logger) { +func genLogs(l Logger) { for i := 0; i < 100; i++ { l.WithFields(Fields{"index": strconv.FormatInt(int64(i), 10)}).Info("random log") }