Skip to content

Commit

Permalink
Make Local really immutable and rename to Logger
Browse files Browse the repository at this point in the history
Make logger.Local really immutable and rename to logger.Logger.

Right now the public Adapter field may be misleading, and someone would want to update the field.

The Local name is also not very good, because logger can be used anywhere (not only locally inside the function, struct etc.). It's concurrency safe and multiple goroutines might use it.
  • Loading branch information
elgopher committed Jan 31, 2022
1 parent 808a5c2 commit 80e4a2a
Show file tree
Hide file tree
Showing 17 changed files with 76 additions and 69 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Please note that at least Go `1.17` is required.

## How to use

### Choose logger - global or local?
### Choose logger - global or normal?

Global logger can be accessed from everywhere in your library and can be reconfigured anytime. Local logger is a logger
initialized only once and used locally, for example inside the function.
Global logger can be accessed from everywhere in your package and can be reconfigured anytime. Normal logger is an
immutable logger, initialized only once.

### Use global logger

Expand Down Expand Up @@ -91,26 +91,26 @@ func main() {
`context.Context` can very useful in transiting request-scoped tags or even entire logger. A `logger.Adapter` implementation might use them
making possible to log messages instrumented with tags. Thanks to that your library can trully participate in the incoming request.

### Use local logger
### Use normal logger

Logging is a special kind of dependency. It is used all over the place. Adding it as an explicit dependency to every
function, struct etc. can be cumbersome. Still though, you have an option to use **local** logger by injecting
function, struct etc. can be cumbersome. Still though, you have an option to use **normal** logger by injecting
logger.Adapter into your library:

```go
// your library code:
func NewLibrary(adapter logger.Adapter) YourLib {
// create a new local logger which provides similar API to the global logger
localLogger := logger.Local{Adapter: adapter}
return YourLib{localLogger: localLogger}
// create a new normal logger which provides similar API to the global logger
l := logger.WithAdapter(adapter)
return YourLib{log: l}
}

type YourLib struct {
localLogger logger.Local
log logger.Logger
}

func (l YourLib) Method(ctx context.Context) {
l.localLogger.Debug(ctx, "message from local logger")
l.log.Debug(ctx, "message from normal logger")
}


Expand Down
2 changes: 1 addition & 1 deletion adapter/console/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func main() {
ctx := context.Background()

// log to console, stdout
log := logger.Local{Adapter: console.StdoutAdapter()}
log := logger.WithAdapter(console.StdoutAdapter())

log.Debug(ctx, "Hello fmt")
log.With("field_name", "field_value").Info(ctx, "Some info")
Expand Down
4 changes: 2 additions & 2 deletions adapter/glogadapter/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ func main() {
ctx := context.Background()

flag.Parse() // glog will pick command line options like -stderrthreshold=[INFO|WARNING|ERROR]
// create local logger
log := logger.Local{Adapter: glogadapter.Adapter{}}
// create yala logger
log := logger.WithAdapter(glogadapter.Adapter{})

log.Debug(ctx, "Hello glog ") // Debug will be logged as Info
log.With("field_name", "field_value").Info(ctx, "Some info")
Expand Down
12 changes: 6 additions & 6 deletions adapter/internal/benchmark/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ func Adapter(b *testing.B, adapter logger.Adapter) {
}
})

b.Run("local logger info", func(b *testing.B) {
localLogger := logger.Local{Adapter: adapter}
b.Run("normal logger info", func(b *testing.B) {
log := logger.WithAdapter(adapter)

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
localLogger.Info(ctx, "msg")
log.Info(ctx, "msg")
}
})

Expand All @@ -51,14 +51,14 @@ func Adapter(b *testing.B, adapter logger.Adapter) {

for fieldType, fieldValue := range fields {
b.Run(fieldType, func(b *testing.B) {
b.Run("local logger with field", func(b *testing.B) {
localLogger := logger.Local{Adapter: adapter}
b.Run("normal logger with field", func(b *testing.B) {
log := logger.WithAdapter(adapter)

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
localLogger.With("a", fieldValue).Info(ctx, "msg")
log.With("a", fieldValue).Info(ctx, "msg")
}
})
})
Expand Down
2 changes: 1 addition & 1 deletion adapter/log15adapter/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {

l := log15.New() // create log15 logger
adapter := log15adapter.Adapter{Logger: l} // create logger.Adapter for log15
log := logger.Local{Adapter: adapter} // create yala logger
log := logger.WithAdapter(adapter) // create yala logger

log.Debug(ctx, "Hello log15")
log.With("field_name", "field_value").Info(ctx, "Some info")
Expand Down
2 changes: 1 addition & 1 deletion adapter/logadapter/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func main() {
// log using standard log package
standardLog := log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)
adapter := logadapter.Adapter(standardLog)
yalaLogger := logger.Local{Adapter: adapter}
yalaLogger := logger.WithAdapter(adapter)

yalaLogger.Debug(ctx, "Hello standard log")
yalaLogger.With("f1", "v1").With("f2", "f2").Info(ctx, "Some info")
Expand Down
2 changes: 1 addition & 1 deletion adapter/logrusadapter/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func main() {
Entry: entry, // inject logrus
}
// Create yala logger
log := logger.Local{Adapter: adapter}
log := logger.WithAdapter(adapter)

log.Debug(ctx, "Hello logrus ")
log.With("field_name", "field_value").With("another", "ccc").Info(ctx, "Some info")
Expand Down
2 changes: 1 addition & 1 deletion adapter/zapadapter/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func main() {

zapLogger := newZapLogger()
adapter := zapadapter.Adapter{Logger: zapLogger} // create logger.Adapter for zap
log := logger.Local{Adapter: adapter} // Create yala logger
log := logger.WithAdapter(adapter) // Create yala logger

log.Debug(ctx, "Hello zap")
log.With("field_name", "field_value").Info(ctx, "Some info")
Expand Down
2 changes: 1 addition & 1 deletion adapter/zerologadapter/_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func main() {

l := zerolog.New(os.Stdout) // create zerolog logger
adapter := zerologadapter.Adapter{Logger: l} // create logger.Adapter for zerolog
log := logger.Local{Adapter: adapter} // Create yala logger
log := logger.WithAdapter(adapter) // Create yala logger

log.Debug(ctx, "Hello zerolog")
log.With("field_name", "field_value").Info(ctx, "Some info")
Expand Down
4 changes: 2 additions & 2 deletions logger/_examples/contextlogger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func main() {
defaultZapLogger := newZapLogger()
// this adapter will look for zap logger in the context and will wrap it with zapadapter.Adapter
adapter := ZapContextAdapter{DefaultZapLogger: defaultZapLogger}
// create local logger
log := logger.Local{Adapter: adapter}
// create logger
log := logger.WithAdapter(adapter)

contextLogger := defaultZapLogger.With(zap.String("tag", "value"))
// bind zap logger to ctx, so all messages will be logged with tag
Expand Down
2 changes: 1 addition & 1 deletion logger/_examples/filter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {
Prefix: "example:",
NextAdapter: adapter,
}
l := logger.Local{Adapter: filterAdapter}
l := logger.WithAdapter(filterAdapter)

ctx := context.Background()

Expand Down
2 changes: 1 addition & 1 deletion logger/_examples/levelfilter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {
MinLevel: logger.WarnLevel,
NextAdapter: adapter,
}
l := logger.Local{Adapter: filterAdapter}
l := logger.WithAdapter(filterAdapter)

ctx := context.Background()

Expand Down
2 changes: 1 addition & 1 deletion logger/_examples/reuse/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
func main() {
ctx := context.Background()

log := logger.Local{Adapter: console.StdoutAdapter()}
log := logger.WithAdapter(console.StdoutAdapter())

// requestLogger will log all messages with at least two fields: request_id and user
requestLogger := log.With("request_id", "123").With("user", "elgopher")
Expand Down
2 changes: 1 addition & 1 deletion logger/_examples/tags/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func main() {

// creates an adapter which adds field from context to each logged message.
addFieldAdapter := AddFieldFromContextAdapter{NextAdapter: adapter}
l := logger.Local{Adapter: addFieldAdapter}
l := logger.WithAdapter(addFieldAdapter)

ctx := context.Background()
// add tag to context
Expand Down
33 changes: 20 additions & 13 deletions logger/local.go → logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,28 @@ import (
"context"
)

// Local is an immutable struct to log messages or create new loggers with fields or error.
// Logger is an immutable logger to log messages or create new loggers with fields or error.
//
// You can't update the adapter once created.
//
// It is safe to use it concurrently.
type Local struct {
Adapter Adapter
type Logger struct {
adapter Adapter
entry Entry
}

// WithAdapter creates a new Logger.
func WithAdapter(adapter Adapter) Logger {
return Logger{adapter: adapter}
}

// Debug logs a message at DebugLevel.
func (l Local) Debug(ctx context.Context, msg string) {
func (l Logger) Debug(ctx context.Context, msg string) {
l.log(ctx, DebugLevel, msg)
}

func (l Local) log(ctx context.Context, lvl Level, msg string) {
if l.Adapter == nil {
func (l Logger) log(ctx context.Context, lvl Level, msg string) {
if l.adapter == nil {
return
}

Expand All @@ -30,41 +37,41 @@ func (l Local) log(ctx context.Context, lvl Level, msg string) {
e.Message = msg
e.SkippedCallerFrames += 2

l.Adapter.Log(ctx, e)
l.adapter.Log(ctx, e)
}

// Info logs a message at InfoLevel.
func (l Local) Info(ctx context.Context, msg string) {
func (l Logger) Info(ctx context.Context, msg string) {
l.log(ctx, InfoLevel, msg)
}

// Warn logs a message at WarnLevel.
func (l Local) Warn(ctx context.Context, msg string) {
func (l Logger) Warn(ctx context.Context, msg string) {
l.log(ctx, WarnLevel, msg)
}

// Error logs a message at ErrorLevel.
func (l Local) Error(ctx context.Context, msg string) {
func (l Logger) Error(ctx context.Context, msg string) {
l.log(ctx, ErrorLevel, msg)
}

// With creates a new logger with field.
func (l Local) With(key string, value interface{}) Local {
func (l Logger) With(key string, value interface{}) Logger {
l.entry = l.entry.With(Field{key, value})

return l
}

// WithError creates a new logger with error.
func (l Local) WithError(err error) Local {
func (l Logger) WithError(err error) Logger {
l.entry.Error = err

return l
}

// WithSkippedCallerFrame creates a new logger with one more skipped caller frame. This function is handy when you
// want to write your own logging helpers.
func (l Local) WithSkippedCallerFrame() Local {
func (l Logger) WithSkippedCallerFrame() Logger {
l.entry.SkippedCallerFrames++

return l
Expand Down
16 changes: 8 additions & 8 deletions logger/logger_concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ func TestConcurrency(t *testing.T) {
assert.Equal(t, adapter.Count(), 6000)
})

t.Run("local log functions", func(t *testing.T) {
t.Run("normal log functions", func(t *testing.T) {
adapter := &concurrencySafeAdapter{}
localLogger := logger.Local{Adapter: adapter}
log := logger.WithAdapter(adapter)

var waitGroup sync.WaitGroup

Expand All @@ -51,12 +51,12 @@ func TestConcurrency(t *testing.T) {

go func() {
// when
localLogger.Debug(ctx, message)
localLogger.Info(ctx, message)
localLogger.Warn(ctx, message)
localLogger.Error(ctx, message)
localLogger.With("k", "v").Info(ctx, message)
localLogger.WithError(ErrSome).Error(ctx, message)
log.Debug(ctx, message)
log.Info(ctx, message)
log.Warn(ctx, message)
log.Error(ctx, message)
log.With("k", "v").Info(ctx, message)
log.WithError(ErrSome).Error(ctx, message)
waitGroup.Done()
}()
}
Expand Down
36 changes: 18 additions & 18 deletions logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,36 +92,36 @@ func TestGlobalLogging(t *testing.T) {
})
}

func TestLocalLogger(t *testing.T) {
func TestNormalLogger(t *testing.T) {
t.Run("passing nil adapter should disable logger", func(t *testing.T) {
localLogger := logger.Local{Adapter: nil}
localLogger.Info(ctx, message)
log := logger.WithAdapter(nil)
log.Info(ctx, message)
})

t.Run("using zero value should not panic", func(t *testing.T) {
var localLogger logger.Local
var log logger.Logger
assert.NotPanics(t, func() {
localLogger.Info(ctx, message)
log.Info(ctx, message)
})
})

t.Run("should log message using adapter", func(t *testing.T) {
type functionUnderTest func(l logger.Local, ctx context.Context, msg string)
type functionUnderTest func(l logger.Logger, ctx context.Context, msg string)
tests := map[logger.Level]functionUnderTest{
logger.DebugLevel: logger.Local.Debug,
logger.InfoLevel: logger.Local.Info,
logger.WarnLevel: logger.Local.Warn,
logger.ErrorLevel: logger.Local.Error,
logger.DebugLevel: logger.Logger.Debug,
logger.InfoLevel: logger.Logger.Info,
logger.WarnLevel: logger.Logger.Warn,
logger.ErrorLevel: logger.Logger.Error,
}

for lvl, log := range tests {
testName := lvl.String()

t.Run(testName, func(t *testing.T) {
adapter := &adapterMock{}
localLogger := logger.Local{Adapter: adapter}
normalLogger := logger.WithAdapter(adapter)
// when
log(localLogger, context.Background(), message)
log(normalLogger, context.Background(), message)
// then
adapter.HasExactlyOneEntry(t,
logger.Entry{
Expand Down Expand Up @@ -175,12 +175,12 @@ func TestGlobal_With(t *testing.T) {
},
With: globalWith,
},
"local": {
"normal": {
NewLoggerWithField: func(adapter logger.Adapter, field logger.Field) anyLogger {
return logger.Local{Adapter: adapter}.With(field.Key, field.Value)
return logger.WithAdapter(adapter).With(field.Key, field.Value)
},
With: func(l anyLogger, k string, v interface{}) anyLogger {
return l.(logger.Local).With(k, v) // nolint:forcetypeassert // no generics still in Go
return l.(logger.Logger).With(k, v) // nolint:forcetypeassert // no generics still in Go
},
},
}
Expand Down Expand Up @@ -270,12 +270,12 @@ func TestGlobal_WithError(t *testing.T) {
},
WithError: globalWithError,
},
"local": {
"normal": {
NewLoggerWithError: func(adapter logger.Adapter, err error) anyLogger {
return logger.Local{Adapter: adapter}.WithError(err)
return logger.WithAdapter(adapter).WithError(err)
},
WithError: func(l anyLogger, err error) anyLogger {
return l.(logger.Local).WithError(err) // nolint:forcetypeassert // no generics still in Go
return l.(logger.Logger).WithError(err) // nolint:forcetypeassert // no generics still in Go
},
},
}
Expand Down

0 comments on commit 80e4a2a

Please sign in to comment.