diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d34822b..04894c7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -41,10 +41,18 @@ jobs: go vet ./... go build -o /dev/null ./... + - name: Test zap logger module + if: matrix.go-version == 'stable' + working-directory: logger/zaplogger + run: | + go vet ./... + go test -v -race -coverprofile=../../coverage-zaplogger.txt -covermode=atomic ./... + - uses: codecov/codecov-action@v4 if: matrix.go-version == 'stable' with: token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.txt,./coverage-zaplogger.txt # end-to-end tests with the real external systems in Docker, see e2e/README.md e2e: diff --git a/Readme.md b/Readme.md index df6728d..61745e9 100644 --- a/Readme.md +++ b/Readme.md @@ -18,6 +18,9 @@ tinyconf fills config structs with values from drivers. When initializing `tinyc as well as drivers. Drivers are applied in the order of addition, the next driver overrides values of the previous one, so the last driver has the highest priority. You can also add your own driver. +Loggers: `logger.NewFmtLogger`, `logger.NewSlogLogger` (Go 1.21+), and the adapter of zap in the separate module +`github.com/insei/tinyconf/logger/zaplogger`, so zap is not a dependency of the core. + `tinyconf.Manager` has methods: | Method | Description | diff --git a/docs/features/logging.md b/docs/features/logging.md index a942802..758e457 100644 --- a/docs/features/logging.md +++ b/docs/features/logging.md @@ -69,10 +69,26 @@ import "github.com/insei/tinyconf/logger" // prints to stdout, levels: FATAL, ERROR, WARN, INFO, DEBUG, TRACE tinyconf.WithLogger(logger.NewFmtLogger(logger.INFO)) +// wraps *slog.Logger, Go 1.21 or newer +tinyconf.WithLogger(logger.NewSlogLogger(slog.Default())) +``` + +The adapter of [zap](https://github.com/uber-go/zap) is a separate module, so zap is not a dependency of tinyconf for the applications with other loggers: + +```bash +go get github.com/insei/tinyconf/logger/zaplogger +``` + +```go +import "github.com/insei/tinyconf/logger/zaplogger" + // wraps *zap.Logger -tinyconf.WithLogger(logger.NewZapLogger(zapLogger)) +tinyconf.WithLogger(zaplogger.New(zapLogger)) ``` +!!! note "Migration" + `logger.NewZapLogger(l)` of the previous versions is `zaplogger.New(l)` from the `github.com/insei/tinyconf/logger/zaplogger` module. + ## Custom logger Implement the `tinyconf.Logger` interface: diff --git a/e2e/go.sum b/e2e/go.sum index 238f664..eaed01e 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -180,10 +180,12 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= diff --git a/example/go.mod b/example/go.mod index 4f0951e..f19f937 100644 --- a/example/go.mod +++ b/example/go.mod @@ -9,8 +9,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/insei/cast v1.1.1 // indirect github.com/insei/fmap/v3 v3.3.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/sys v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/example/go.sum b/example/go.sum index ceb4e70..cf665c9 100644 --- a/example/go.sum +++ b/example/go.sum @@ -12,12 +12,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/go.mod b/go.mod index a00a719..f7685a8 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/insei/cast v1.1.1 github.com/insei/fmap/v3 v3.3.0 github.com/stretchr/testify v1.9.0 - go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -16,6 +15,5 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.uber.org/multierr v1.10.0 // indirect golang.org/x/sys v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index f980463..3105816 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/logger/fields_test.go b/logger/fields_test.go index 73efcb4..6c2f08b 100644 --- a/logger/fields_test.go +++ b/logger/fields_test.go @@ -4,10 +4,6 @@ import ( "strings" "testing" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "go.uber.org/zap/zaptest/observer" - "github.com/insei/tinyconf" ) @@ -22,18 +18,3 @@ func TestFmtLogger_Fields(t *testing.T) { } } } - -func TestZapLogger_With(t *testing.T) { - core, logs := observer.New(zapcore.DebugLevel) - logger := NewZapLogger(zap.New(core)).With(tinyconf.Field{Key: "driver", Value: "env"}) - logger.Info("message", tinyconf.Field{Key: "field", Value: "Name"}) - - entries := logs.All() - if len(entries) != 1 { - t.Fatalf("Expected 1 log entry, but got %d", len(entries)) - } - fields := entries[0].ContextMap() - if entries[0].Message != "message" || fields["driver"] != "env" || fields["field"] != "Name" { - t.Errorf("Unexpected log entry: %s %v", entries[0].Message, fields) - } -} diff --git a/logger/slog.go b/logger/slog.go new file mode 100644 index 0000000..550acea --- /dev/null +++ b/logger/slog.go @@ -0,0 +1,56 @@ +//go:build go1.21 + +package logger + +import ( + "context" + "log/slog" + + "github.com/insei/tinyconf" +) + +type slogLogger struct { + l *slog.Logger +} + +func attrs(fields []tinyconf.Field) []any { + args := make([]any, 0, len(fields)) + for _, field := range fields { + args = append(args, slog.Any(field.Key, field.Value)) + } + return args +} + +func (s *slogLogger) log(level slog.Level, msg string, fields []tinyconf.Field) { + s.l.Log(context.Background(), level, msg, attrs(fields)...) +} + +// Debug logs a message at the DEBUG level. +func (s *slogLogger) Debug(msg string, fields ...tinyconf.Field) { + s.log(slog.LevelDebug, msg, fields) +} + +// Error logs a message at the ERROR level. +func (s *slogLogger) Error(msg string, fields ...tinyconf.Field) { + s.log(slog.LevelError, msg, fields) +} + +// Warn logs a message at the WARN level. +func (s *slogLogger) Warn(msg string, fields ...tinyconf.Field) { + s.log(slog.LevelWarn, msg, fields) +} + +// Info logs a message at the INFO level. +func (s *slogLogger) Info(msg string, fields ...tinyconf.Field) { + s.log(slog.LevelInfo, msg, fields) +} + +// With returns the logger with the fields. +func (s *slogLogger) With(fields ...tinyconf.Field) tinyconf.Logger { + return &slogLogger{l: s.l.With(attrs(fields)...)} +} + +// NewSlogLogger returns the logger of tinyconf that writes to the slog logger, it needs Go 1.21 or newer. +func NewSlogLogger(l *slog.Logger) tinyconf.Logger { + return &slogLogger{l: l} +} diff --git a/logger/slog_test.go b/logger/slog_test.go new file mode 100644 index 0000000..90e2751 --- /dev/null +++ b/logger/slog_test.go @@ -0,0 +1,37 @@ +//go:build go1.21 + +package logger + +import ( + "bytes" + "log/slog" + "testing" + + "github.com/insei/tinyconf" +) + +func TestSlogLogger(t *testing.T) { + var out bytes.Buffer + handler := slog.NewTextHandler(&out, &slog.HandlerOptions{ + Level: slog.LevelDebug, + // the time is removed for the stable output + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + return slog.Attr{} + } + return a + }, + }) + logger := NewSlogLogger(slog.New(handler)).With(tinyconf.Field{Key: "driver", Value: "env"}) + logger.Debug("debug", tinyconf.Field{Key: "field", Value: "Name"}) + logger.Info("info") + logger.Warn("warn") + logger.Error("error", tinyconf.Field{Key: "details", Value: "failed"}) + want := "level=DEBUG msg=debug driver=env field=Name\n" + + "level=INFO msg=info driver=env\n" + + "level=WARN msg=warn driver=env\n" + + "level=ERROR msg=error driver=env details=failed\n" + if out.String() != want { + t.Errorf("unexpected output:\n%s\nwant:\n%s", out.String(), want) + } +} diff --git a/logger/zap.go b/logger/zap.go deleted file mode 100644 index a09d21c..0000000 --- a/logger/zap.go +++ /dev/null @@ -1,49 +0,0 @@ -package logger - -import ( - "go.uber.org/zap" - - "github.com/insei/tinyconf" -) - -// zapLogger is a simple l that uses zap.Logger -type zapLogger struct { - l *zap.Logger -} - -func (t *zapLogger) castToZapFields(flds ...tinyconf.Field) *zapLogger { - l := t.l - for _, fld := range flds { - l = l.With(zap.Any(fld.Key, fld.Value)) - } - return &zapLogger{l: l} -} - -// Debug logs a message at the DEBUG level -func (t *zapLogger) Debug(msg string, fld ...tinyconf.Field) { - t.castToZapFields(fld...).l.Debug(msg) -} - -// Error logs a message at the ERROR level -func (t *zapLogger) Error(msg string, fld ...tinyconf.Field) { - t.castToZapFields(fld...).l.Error(msg) -} - -// Warn logs a message at the WARN level -func (t *zapLogger) Warn(msg string, fld ...tinyconf.Field) { - t.castToZapFields(fld...).l.Warn(msg) -} - -// Info logs a message at the INFO level -func (t *zapLogger) Info(msg string, fld ...tinyconf.Field) { - t.castToZapFields(fld...).l.Info(msg) -} - -func (t *zapLogger) With(fld ...tinyconf.Field) tinyconf.Logger { - return t.castToZapFields(fld...) -} - -// NewZapLogger creates a new zapLogger -func NewZapLogger(l *zap.Logger) tinyconf.Logger { - return &zapLogger{l: l} -} diff --git a/logger/zap_test.go b/logger/zap_test.go deleted file mode 100644 index 5b662f7..0000000 --- a/logger/zap_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package logger - -import ( - "os" - "testing" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/insei/tinyconf" -) - -func TestZapDebug(t *testing.T) { - var config zapcore.EncoderConfig - config = zap.NewDevelopmentEncoderConfig() - - zapLg := zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(config), - zapcore.Lock(os.Stdout), - zapcore.DebugLevel, - )) - lg := NewZapLogger(zapLg) - logger := lg.(*zapLogger) - - logger.Debug("Debug message", tinyconf.Field{Key: "Login", Value: "world"}) -} - -func TestZapError(t *testing.T) { - var config zapcore.EncoderConfig - config = zap.NewDevelopmentEncoderConfig() - - zapLg := zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(config), - zapcore.Lock(os.Stdout), - zapcore.ErrorLevel, - )) - lg := NewZapLogger(zapLg) - logger := lg.(*zapLogger) - - logger.Error("Error message", tinyconf.Field{Key: "Name", Value: "Eddy"}) -} - -func TestZapWarning(t *testing.T) { - var config zapcore.EncoderConfig - config = zap.NewDevelopmentEncoderConfig() - - zapLg := zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(config), - zapcore.Lock(os.Stdout), - zapcore.WarnLevel, - )) - lg := NewZapLogger(zapLg) - logger := lg.(*zapLogger) - - logger.Warn("Warn message", tinyconf.Field{Key: "Table", Value: "admins"}) -} - -func TestZapInfo(t *testing.T) { - var config zapcore.EncoderConfig - config = zap.NewDevelopmentEncoderConfig() - - zapLg := zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(config), - zapcore.Lock(os.Stdout), - zapcore.InfoLevel, - )) - lg := NewZapLogger(zapLg) - logger := lg.(*zapLogger) - - logger.Info("Info message", tinyconf.Field{Key: "Success", Value: "true"}) -} - -func TestNewZapLogger(t *testing.T) { - var config zapcore.EncoderConfig - config = zap.NewDevelopmentEncoderConfig() - - zapLg := zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(config), - zapcore.Lock(os.Stdout), - zapcore.InfoLevel, - )) - logger := NewZapLogger(zapLg) - _, ok := logger.(*zapLogger) - if !ok { - t.Error("The type of l is not '*zapLogger'") - } -} diff --git a/logger/zaplogger/go.mod b/logger/zaplogger/go.mod new file mode 100644 index 0000000..0353c59 --- /dev/null +++ b/logger/zaplogger/go.mod @@ -0,0 +1,21 @@ +module github.com/insei/tinyconf/logger/zaplogger + +go 1.19 + +replace github.com/insei/tinyconf => ../../ + +require ( + github.com/insei/tinyconf v1.3.2 + github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/insei/cast v1.1.1 // indirect + github.com/insei/fmap/v3 v3.3.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/logger/zaplogger/go.sum b/logger/zaplogger/go.sum new file mode 100644 index 0000000..a361eb3 --- /dev/null +++ b/logger/zaplogger/go.sum @@ -0,0 +1,23 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/insei/cast v1.1.1 h1:Elrnc3yo5FvaN8B6KWP/+UWblgKG8uj0/UbE/olcLYM= +github.com/insei/cast v1.1.1/go.mod h1:WyueAs28LJPpteJTcUZbkt3eLffnNczOR7B4ODQ8lrE= +github.com/insei/fmap/v3 v3.3.0 h1:VdTwCpJxQWyjUqomfNDwRHkWA5XeHnb2UNHB7J2k8UA= +github.com/insei/fmap/v3 v3.3.0/go.mod h1:Kk0gs7nKb4E/JycKJFnrsX5hlyBBe0yetGKFCJG0vzk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logger/zaplogger/zaplogger.go b/logger/zaplogger/zaplogger.go new file mode 100644 index 0000000..5aefbd8 --- /dev/null +++ b/logger/zaplogger/zaplogger.go @@ -0,0 +1,53 @@ +// Package zaplogger is the adapter of zap for the logger of tinyconf. It is a separate module, +// so zap is not a dependency of tinyconf for the applications that use other loggers. +// +// config, err := tinyconf.New(tinyconf.WithLogger(zaplogger.New(zapLogger))) +package zaplogger + +import ( + "go.uber.org/zap" + + "github.com/insei/tinyconf" +) + +type zapLogger struct { + l *zap.Logger +} + +func (t *zapLogger) with(fields ...tinyconf.Field) *zap.Logger { + l := t.l + for _, field := range fields { + l = l.With(zap.Any(field.Key, field.Value)) + } + return l +} + +// Debug logs a message at the DEBUG level. +func (t *zapLogger) Debug(msg string, fields ...tinyconf.Field) { + t.with(fields...).Debug(msg) +} + +// Error logs a message at the ERROR level. +func (t *zapLogger) Error(msg string, fields ...tinyconf.Field) { + t.with(fields...).Error(msg) +} + +// Warn logs a message at the WARN level. +func (t *zapLogger) Warn(msg string, fields ...tinyconf.Field) { + t.with(fields...).Warn(msg) +} + +// Info logs a message at the INFO level. +func (t *zapLogger) Info(msg string, fields ...tinyconf.Field) { + t.with(fields...).Info(msg) +} + +// With returns the logger with the fields. +func (t *zapLogger) With(fields ...tinyconf.Field) tinyconf.Logger { + return &zapLogger{l: t.with(fields...)} +} + +// New returns the logger of tinyconf that writes to the zap logger. +func New(l *zap.Logger) tinyconf.Logger { + return &zapLogger{l: l} +} diff --git a/logger/zaplogger/zaplogger_test.go b/logger/zaplogger/zaplogger_test.go new file mode 100644 index 0000000..3ef89cb --- /dev/null +++ b/logger/zaplogger/zaplogger_test.go @@ -0,0 +1,31 @@ +package zaplogger + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/insei/tinyconf" +) + +func TestLogger(t *testing.T) { + core, logs := observer.New(zapcore.DebugLevel) + logger := New(zap.New(core)).With(tinyconf.Field{Key: "driver", Value: "env"}) + logger.Debug("debug", tinyconf.Field{Key: "field", Value: "Name"}) + logger.Info("info") + logger.Warn("warn") + logger.Error("error", tinyconf.Field{Key: "details", Value: "failed"}) + + entries := logs.All() + assert.Len(t, entries, 4) + for i, level := range []zapcore.Level{zapcore.DebugLevel, zapcore.InfoLevel, zapcore.WarnLevel, zapcore.ErrorLevel} { + assert.Equal(t, level, entries[i].Level) + assert.Equal(t, level.String(), entries[i].Message) + assert.Equal(t, "env", entries[i].ContextMap()["driver"]) + } + assert.Equal(t, "Name", entries[0].ContextMap()["field"]) + assert.Equal(t, "failed", entries[3].ContextMap()["details"]) +}