diff --git a/README.md b/README.md index 0571827..2e3e269 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,23 @@ package main import ( - "os" "time" "github.com/zerodha/logf" ) func main() { - logger := logf.New(os.Stderr) - - // Basic log. + logger := logf.New(logf.Opts{ + EnableColor: true, + Level: logf.DebugLevel, + CallerSkipFrameCount: 3, + EnableCaller: true, + TimestampFormat: time.RFC3339Nano, + DefaultFields: []any{"scope", "example"}, + }) + + // Basic logs. logger.Info("starting app") - - // Enable colored output. - logger.SetColorOutput(true) - - // Change verbosity on the fly. - logger.SetLevel(logf.DebugLevel) logger.Debug("meant for debugging app") // Add extra keys to the log. @@ -41,11 +41,6 @@ func main() { // Log with error key. logger.Error("error fetching details", "error", "this is a dummy error") - // Enable `caller` field in the log and specify the number of frames to skip to get the caller. - logger.SetCallerFrame(true, 3) - // Change the default timestamp format. - logger.SetTimestampFormat(time.RFC3339Nano) - // Log the error and set exit code as 1. logger.Fatal("goodbye world") } diff --git a/benchmark_test.go b/benchmark_test.go index 4a61394..c9bc555 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -30,6 +30,17 @@ func BenchmarkOneField(b *testing.B) { }) } +func BenchmarkOneFieldWithDefaultFields(b *testing.B) { + logger := logf.New(logf.Opts{Writer: io.Discard, DefaultFields: []any{"component", "logf"}}) + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(p *testing.PB) { + for p.Next() { + logger.Info("hello world", "stack", "testing") + } + }) +} + func BenchmarkThreeFields(b *testing.B) { logger := logf.New(logf.Opts{Writer: io.Discard}) b.ReportAllocs() diff --git a/examples/main.go b/examples/main.go index 15a3140..74792f0 100644 --- a/examples/main.go +++ b/examples/main.go @@ -13,6 +13,7 @@ func main() { CallerSkipFrameCount: 3, EnableCaller: true, TimestampFormat: time.RFC3339Nano, + DefaultFields: []any{"scope", "example"}, }) // Basic logs. diff --git a/log.go b/log.go index 1376c8e..ff01f62 100644 --- a/log.go +++ b/log.go @@ -29,6 +29,9 @@ type Opts struct { EnableColor bool EnableCaller bool CallerSkipFrameCount int + + // These fields will be printed with every log. + DefaultFields []any } // Logger is the interface for all log operations @@ -180,19 +183,37 @@ func (l Logger) handleLog(msg string, lvl Level, fields ...any) { // Format the line as logfmt. var ( // count is find out if this is the last key in while itering fields. - count int - key string - val any + count int + fieldCount = len(l.DefaultFields) + len(fields) + key string + val any ) // If there are odd number of fields, ignore the last. - if len(fields)%2 != 0 { - fields = fields[0 : len(fields)-1] + if fieldCount%2 != 0 { + fields = fields[0 : fieldCount-1] + } + + for i := range l.DefaultFields { + space := false + if count != fieldCount-1 { + space = true + } + + if i%2 == 0 { + key = l.DefaultFields[i].(string) + continue + } else { + val = l.DefaultFields[i] + } + + writeToBuf(buf, key, val, lvl, l.Opts.EnableColor, space) + count++ } for i := range fields { space := false - if count != len(fields)-1 { + if count != fieldCount-1 { space = true } diff --git a/log_test.go b/log_test.go index c9a3964..aeaf389 100644 --- a/log_test.go +++ b/log_test.go @@ -66,6 +66,19 @@ func TestLogFormat(t *testing.T) { buf.Reset() } +func TestLogFormatWithDefaultFields(t *testing.T) { + buf := &bytes.Buffer{} + l := New(Opts{Writer: buf, DefaultFields: []any{"defaultkey", "defaultvalue"}}) + + l.Info("hello world") + assert.Contains(t, buf.String(), `level=info message="hello world" defaultkey=defaultvalue`) + buf.Reset() + + l.Info("hello world", "component", "logf") + assert.Contains(t, buf.String(), `level=info message="hello world" defaultkey=defaultvalue component=logf`) + buf.Reset() +} + func TestOddNumberedFields(t *testing.T) { buf := &bytes.Buffer{} l := New(Opts{Writer: buf})