diff --git a/go.mod b/go.mod index b93f529..144c7ab 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,13 @@ require ( github.com/rabbitmq/amqp091-go v1.4.0 github.com/stretchr/testify v1.8.0 github.com/wagslane/go-rabbitmq v0.10.0 + go.uber.org/zap v1.21.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4035ce1..8460637 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -10,12 +12,15 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rabbitmq/amqp091-go v1.4.0 h1:T2G+J9W9OY4p64Di23J6yH7tOkMocgnESvYeBjuG9cY= github.com/rabbitmq/amqp091-go v1.4.0/go.mod h1:JsV0ofX5f1nwOGafb8L5rBItt9GyhfQfcJj+oyz0dGg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= @@ -23,8 +28,15 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/wagslane/go-rabbitmq v0.10.0 h1:y9Bw8Q/9gOvsHfjMOGQjCW3033aYTKabxDm8eyjUGjs= github.com/wagslane/go-rabbitmq v0.10.0/go.mod h1:u6xM1V7OO4D0szUy/F6Bya/9r0lLae/2FXBijkAQmn0= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +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.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -54,6 +66,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/internal/app.go b/internal/app.go index 48dcb99..ce600ef 100644 --- a/internal/app.go +++ b/internal/app.go @@ -2,50 +2,55 @@ package internal import ( "context" + "fmt" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue/handler" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue/publisher" - "log" ) func Start() int { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - conf, err := config.ReadConfig() //read configuration from file & env + conf, err := config.ReadConfig() //read configuration from env if err != nil { - log.Println("error while reading configuration") + fmt.Println("error while reading configuration") + return 1 + } + log, err := logger.NewLogger(conf) + if err != nil { + fmt.Println("error creating logger: ", err) return 1 } - //initialize publisher connection to the queue //this library assumes using one publisher and one consumer per application //https://github.com/wagslane/go-rabbitmq/issues/79 - pub, err := publisher.NewPublisher(conf.Queue) //TODO pass logger here and add it to publisher options + pub, err := publisher.NewPublisher(conf.Queue, log) if err != nil { - log.Println("error while starting publisher: ", err) + log.Error("error while starting publisher: ", err) return 1 } - defer publisher.ClosePublisher(pub) + defer publisher.ClosePublisher(pub, log) //initialize consumer connection to the queue - consumer, err := queue.NewConsumer(conf.Queue) //TODO pass logger here and add it to consumer options + consumer, err := queue.NewConsumer(conf.Queue, log) if err != nil { - log.Println("error while connecting to the queue: ", err) + log.Error("error while connecting to the queue: ", err) return 1 } - defer queue.CloseConsumer(consumer) + defer queue.CloseConsumer(consumer, log) - handl := handler.NewApiSpecDocHandler(pub, conf.Queue) + handl := handler.NewApiSpecDocHandler(pub, conf.Queue, log) listener := queue.NewListener() err = listener.Start(consumer, &conf.Queue, handl) if err != nil { - log.Println("error while listening queue ", err) + log.Error("error while listening queue ", err) return 1 } <-ctx.Done() - log.Println("application stopped gracefully (not)") + log.Info("application stopped gracefully (not)") return 0 } diff --git a/internal/config/application.go b/internal/config/application.go index f11ee6f..8ced3d1 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -6,6 +6,8 @@ import ( ) type ApplicationConfig struct { + Env Environment `default:"dev"` + Logger LoggerConfig Queue QueueConfig } diff --git a/internal/config/logger.go b/internal/config/logger.go new file mode 100644 index 0000000..55c0687 --- /dev/null +++ b/internal/config/logger.go @@ -0,0 +1,6 @@ +package config + +//LoggerConfig represents configuration of the logger +type LoggerConfig struct { + Level LoggerLevel `default:"info"` //Level of minimum logging +} diff --git a/internal/config/types.go b/internal/config/types.go new file mode 100644 index 0000000..4d71ed4 --- /dev/null +++ b/internal/config/types.go @@ -0,0 +1,40 @@ +package config + +import "go.uber.org/zap/zapcore" + +//Environment defines the application environment to adjust settings to it. +type Environment string + +const ( + Dev Environment = "dev" + Prod Environment = "prod" +) + +//LoggerLevel defines the minimum logging level to process +type LoggerLevel string + +const ( + TraceLevel LoggerLevel = "trace" + DebugLevel LoggerLevel = "debug" + InfoLevel LoggerLevel = "info" + WarnLevel LoggerLevel = "warn" + ErrorLevel LoggerLevel = "error" + PanicLevel LoggerLevel = "panic" +) + +func (ll LoggerLevel) ToZapLevel() zapcore.Level { + switch ll { + case TraceLevel, DebugLevel: + return zapcore.DebugLevel + case InfoLevel: + return zapcore.InfoLevel + case WarnLevel: + return zapcore.WarnLevel + case ErrorLevel: + return zapcore.ErrorLevel + case PanicLevel: + return zapcore.PanicLevel + default: + return zapcore.InfoLevel + } +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..0718844 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,31 @@ +package logger + +import ( + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" +) + +//Logger represents common logger interface +//go:generate mockgen -source=logger.go -destination=./mocks/logger.go +type Logger interface { + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + + Warn(args ...interface{}) + Warnf(format string, args ...interface{}) + + Info(args ...interface{}) + Infof(format string, args ...interface{}) + + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + + Trace(args ...interface{}) + Tracef(format string, args ...interface{}) +} + +func NewLogger(conf *config.ApplicationConfig) (Logger, error) { + return newZapLogger(conf) +} diff --git a/internal/logger/mocks/logger.go b/internal/logger/mocks/logger.go new file mode 100644 index 0000000..0cf44a3 --- /dev/null +++ b/internal/logger/mocks/logger.go @@ -0,0 +1,232 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: logger.go + +// Package mock_logger is a generated GoMock package. +package mock_logger + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockLogger is a mock of Logger interface. +type MockLogger struct { + ctrl *gomock.Controller + recorder *MockLoggerMockRecorder +} + +// MockLoggerMockRecorder is the mock recorder for MockLogger. +type MockLoggerMockRecorder struct { + mock *MockLogger +} + +// NewMockLogger creates a new mock instance. +func NewMockLogger(ctrl *gomock.Controller) *MockLogger { + mock := &MockLogger{ctrl: ctrl} + mock.recorder = &MockLoggerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { + return m.recorder +} + +// Debug mocks base method. +func (m *MockLogger) Debug(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debug", varargs...) +} + +// Debug indicates an expected call of Debug. +func (mr *MockLoggerMockRecorder) Debug(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), args...) +} + +// Debugf mocks base method. +func (m *MockLogger) Debugf(format string, args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debugf", varargs...) +} + +// Debugf indicates an expected call of Debugf. +func (mr *MockLoggerMockRecorder) Debugf(format interface{}, args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...) +} + +// Error mocks base method. +func (m *MockLogger) Error(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Error", varargs...) +} + +// Error indicates an expected call of Error. +func (mr *MockLoggerMockRecorder) Error(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), args...) +} + +// Errorf mocks base method. +func (m *MockLogger) Errorf(format string, args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Errorf", varargs...) +} + +// Errorf indicates an expected call of Errorf. +func (mr *MockLoggerMockRecorder) Errorf(format interface{}, args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...) +} + +// Fatal mocks base method. +func (m *MockLogger) Fatal(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Fatal", varargs...) +} + +// Fatal indicates an expected call of Fatal. +func (mr *MockLoggerMockRecorder) Fatal(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatal", reflect.TypeOf((*MockLogger)(nil).Fatal), args...) +} + +// Fatalf mocks base method. +func (m *MockLogger) Fatalf(format string, args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Fatalf", varargs...) +} + +// Fatalf indicates an expected call of Fatalf. +func (mr *MockLoggerMockRecorder) Fatalf(format interface{}, args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatalf", reflect.TypeOf((*MockLogger)(nil).Fatalf), varargs...) +} + +// Info mocks base method. +func (m *MockLogger) Info(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Info", varargs...) +} + +// Info indicates an expected call of Info. +func (mr *MockLoggerMockRecorder) Info(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), args...) +} + +// Infof mocks base method. +func (m *MockLogger) Infof(format string, args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Infof", varargs...) +} + +// Infof indicates an expected call of Infof. +func (mr *MockLoggerMockRecorder) Infof(format interface{}, args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...) +} + +// Trace mocks base method. +func (m *MockLogger) Trace(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Trace", varargs...) +} + +// Trace indicates an expected call of Trace. +func (mr *MockLoggerMockRecorder) Trace(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trace", reflect.TypeOf((*MockLogger)(nil).Trace), args...) +} + +// Tracef mocks base method. +func (m *MockLogger) Tracef(format string, args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Tracef", varargs...) +} + +// Tracef indicates an expected call of Tracef. +func (mr *MockLoggerMockRecorder) Tracef(format interface{}, args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tracef", reflect.TypeOf((*MockLogger)(nil).Tracef), varargs...) +} + +// Warn mocks base method. +func (m *MockLogger) Warn(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Warn", varargs...) +} + +// Warn indicates an expected call of Warn. +func (mr *MockLoggerMockRecorder) Warn(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), args...) +} + +// Warnf mocks base method. +func (m *MockLogger) Warnf(format string, args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Warnf", varargs...) +} + +// Warnf indicates an expected call of Warnf. +func (mr *MockLoggerMockRecorder) Warnf(format interface{}, args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLogger)(nil).Warnf), varargs...) +} diff --git a/internal/logger/zap.go b/internal/logger/zap.go new file mode 100644 index 0000000..024ecd7 --- /dev/null +++ b/internal/logger/zap.go @@ -0,0 +1,101 @@ +package logger + +import ( + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" + "go.uber.org/zap" +) + +//ZapLogger represents zap implementation of the Logger interface +type ZapLogger struct { + log *zap.SugaredLogger + level config.LoggerLevel +} + +func (l *ZapLogger) Fatal(args ...interface{}) { + l.log.Fatal(args) +} + +func (l *ZapLogger) Fatalf(format string, args ...interface{}) { + l.log.Fatalf(format, args) +} + +func (l *ZapLogger) Error(args ...interface{}) { + l.log.Error(args) +} + +func (l *ZapLogger) Errorf(format string, args ...interface{}) { + l.log.Errorf(format, args) +} + +func (l *ZapLogger) Warn(args ...interface{}) { + l.log.Warn(args) +} + +func (l *ZapLogger) Warnf(format string, args ...interface{}) { + l.log.Warnf(format, args) +} + +func (l *ZapLogger) Info(args ...interface{}) { + l.log.Info(args) +} + +func (l *ZapLogger) Infof(format string, args ...interface{}) { + l.log.Infof(format, args) +} + +func (l *ZapLogger) Debug(args ...interface{}) { + l.log.Debug(args) +} + +func (l *ZapLogger) Debugf(format string, args ...interface{}) { + l.log.Debugf(format, args) +} + +func (l *ZapLogger) Trace(args ...interface{}) { + if l.level != config.TraceLevel { + l.log.Debug(args) + } +} + +func (l *ZapLogger) Tracef(format string, args ...interface{}) { + if l.level != config.TraceLevel { + l.log.Debugf(format, args) + } +} + +//createZapSugaredLogger creates sugared logger from the application configuration +func createZapSugaredLogger(conf *config.ApplicationConfig) (*zap.SugaredLogger, error) { + var zConf zap.Config + switch conf.Env { + case config.Prod: + zConf = zap.NewProductionConfig() + case config.Dev: + zConf = zap.NewDevelopmentConfig() + default: + zConf = zap.NewDevelopmentConfig() + } + zConf.Level.SetLevel(conf.Logger.Level.ToZapLevel()) + + //We are using the wrapper, so to remove the wrapper from the call trace, we need to add AddCallerSkip option + logger, err := zConf.Build(zap.AddCallerSkip(1)) + if err != nil { + return nil, err + } + + return logger.Sugar(), nil +} + +//newZapLogger creates new ZapLogger instance using application configuration +func newZapLogger(conf *config.ApplicationConfig) (*ZapLogger, error) { + logger, err := createZapSugaredLogger(conf) + if err != nil { + return nil, err + } + + zapLogger := &ZapLogger{ + log: logger, + level: conf.Logger.Level, + } + + return zapLogger, nil +} diff --git a/internal/logger/zap_test.go b/internal/logger/zap_test.go new file mode 100644 index 0000000..4af4f87 --- /dev/null +++ b/internal/logger/zap_test.go @@ -0,0 +1,36 @@ +package logger + +import ( + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" + "github.com/stretchr/testify/assert" + "go.uber.org/zap/zapcore" + "testing" +) + +func TestNewZapLogger_checkConfiguration(t *testing.T) { + conf := config.ApplicationConfig{ + Env: config.Prod, + Logger: config.LoggerConfig{ + Level: config.ErrorLevel, + }, + } + logger, err := newZapLogger(&conf) + assert.Nil(t, err) + assert.Equal(t, config.ErrorLevel, logger.level) + checkRes := logger.log.Desugar().Check(zapcore.InfoLevel, "test") + assert.Nil(t, checkRes) //Logger won't write to log + checkRes = logger.log.Desugar().Check(zapcore.ErrorLevel, "test") + assert.NotNil(t, checkRes) //Logger will write to log +} + +func TestNewZapLogger_notFailFromDefaultConfig(t *testing.T) { + conf := config.ApplicationConfig{} + logger, err := newZapLogger(&conf) + assert.Nil(t, err) + assert.NotNil(t, logger) + //Check default info level + checkRes := logger.log.Desugar().Check(zapcore.DebugLevel, "test") + assert.Nil(t, checkRes) //Logger won't write to log + checkRes = logger.log.Desugar().Check(zapcore.InfoLevel, "test") + assert.NotNil(t, checkRes) //Logger will write to log +} diff --git a/internal/queue/consumer.go b/internal/queue/consumer.go index 43f2f52..5159842 100644 --- a/internal/queue/consumer.go +++ b/internal/queue/consumer.go @@ -2,9 +2,9 @@ package queue import ( "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger" "github.com/wagslane/go-rabbitmq" "io" - "log" ) //Consumer is just an interface for the library consumer which doesn't have one. @@ -19,11 +19,12 @@ type Consumer interface { ) error } -func NewConsumer(conf config.QueueConfig) (Consumer, error) { +func NewConsumer(conf config.QueueConfig, log logger.Logger) (Consumer, error) { consumer, err := rabbitmq.NewConsumer( conf.Url, rabbitmq.Config{}, rabbitmq.WithConsumerOptionsLogging, + rabbitmq.WithConsumerOptionsLogger(log), ) if err != nil { return nil, err @@ -31,10 +32,10 @@ func NewConsumer(conf config.QueueConfig) (Consumer, error) { return &consumer, nil } -func CloseConsumer(consumer Consumer) { - log.Println("closing consumer") +func CloseConsumer(consumer Consumer, log logger.Logger) { + log.Info("closing consumer") err := consumer.Close() if err != nil { - log.Println("error while closing consumer: ", err) + log.Error("error while closing consumer: ", err) } } diff --git a/internal/queue/consumer_test.go b/internal/queue/consumer_test.go index 08198a4..3937137 100644 --- a/internal/queue/consumer_test.go +++ b/internal/queue/consumer_test.go @@ -2,6 +2,7 @@ package queue_test import ( "github.com/golang/mock/gomock" + mock_logger "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger/mocks" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue" mock_queue "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue/mocks" "testing" @@ -11,5 +12,7 @@ func TestClosePublisher(t *testing.T) { ctrl := gomock.NewController(t) consumer := mock_queue.NewMockConsumer(ctrl) consumer.EXPECT().Close().Return(nil) - queue.CloseConsumer(consumer) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Info(gomock.Any()) + queue.CloseConsumer(consumer, log) } diff --git a/internal/queue/handler/apispec.go b/internal/queue/handler/apispec.go index 81a2ca3..fccc898 100644 --- a/internal/queue/handler/apispec.go +++ b/internal/queue/handler/apispec.go @@ -5,23 +5,24 @@ import ( "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/dto" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/dto/apiSpecDoc" + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue/publisher" "github.com/wagslane/go-rabbitmq" - "log" ) type ApiSpecDocHandler struct { publisher publisher.Publisher config config.QueueConfig + log logger.Logger } func (asdh *ApiSpecDocHandler) Handle(delivery rabbitmq.Delivery) rabbitmq.Action { - log.Printf("consumed: %v", string(delivery.Body)) + asdh.log.Infof("consumed: %v", string(delivery.Body)) //call process here var req dto.UrlRequest err := json.Unmarshal(delivery.Body, &req) if err != nil { - log.Printf("error unmarshalling message: '%v', err: %s\n", string(delivery.Body), err) + asdh.log.Errorf("error unmarshalling message: '%v', err: %s", string(delivery.Body), err) return rabbitmq.NackDiscard } //here processing of the request happens... @@ -31,25 +32,25 @@ func (asdh *ApiSpecDocHandler) Handle(delivery rabbitmq.Delivery) rabbitmq.Actio result := dto.ScrapingResult{IsNotifyUser: req.IsNotifyUser, ApiSpecDoc: asd} err = asdh.publish(&delivery, result, asdh.config.ScrapingResultQueue) if err != nil { - log.Println("error while publishing: ", err) + asdh.log.Error("error while publishing: ", err) //Here is some error while publishing happened - probably something wrong with the queue return rabbitmq.NackDiscard } if req.IsNotifyUser { err = asdh.publish(&delivery, dto.NewUserNotification(nil), asdh.config.NotificationQueue) if err != nil { - log.Println("error while notifying user") + asdh.log.Error("error while notifying user") //don't discard this message because it was published to the storage service successfully } } - log.Println("Url scraped successfully") + asdh.log.Info("url scraped successfully") return rabbitmq.Ack } func (asdh *ApiSpecDocHandler) publish(delivery *rabbitmq.Delivery, message any, queue string) error { content, err := json.Marshal(message) if err != nil { - log.Println("error while marshalling: ", err) + asdh.log.Info("error while marshalling: ", err) return err } return asdh.publisher.Publish(content, @@ -60,9 +61,10 @@ func (asdh *ApiSpecDocHandler) publish(delivery *rabbitmq.Delivery, message any, ) } -func NewApiSpecDocHandler(publisher publisher.Publisher, config config.QueueConfig) Handler { +func NewApiSpecDocHandler(publisher publisher.Publisher, config config.QueueConfig, log logger.Logger) Handler { return &ApiSpecDocHandler{ publisher: publisher, config: config, + log: log, } } diff --git a/internal/queue/handler/apispec_test.go b/internal/queue/handler/apispec_test.go index 991a74f..07edd78 100644 --- a/internal/queue/handler/apispec_test.go +++ b/internal/queue/handler/apispec_test.go @@ -5,6 +5,7 @@ import ( "github.com/golang/mock/gomock" "github.com/rabbitmq/amqp091-go" "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" + mock_logger "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger/mocks" publisher "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue/publisher/mocks" "github.com/stretchr/testify/assert" "github.com/wagslane/go-rabbitmq" @@ -14,9 +15,12 @@ import ( func TestApiSpecDocHandler_Handle_wrongBody_NackDiscard(t *testing.T) { ctrl := gomock.NewController(t) pub := publisher.NewMockPublisher(ctrl) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Infof(gomock.Any(), gomock.Any()) + log.EXPECT().Errorf(gomock.Any(), gomock.Any()) conf := config.QueueConfig{} - handl := NewApiSpecDocHandler(pub, conf) + handl := NewApiSpecDocHandler(pub, conf, log) wrongBody := "wrong body" delivery := rabbitmq.Delivery{ Delivery: amqp091.Delivery{Body: []byte(wrongBody)}, @@ -28,6 +32,9 @@ func TestApiSpecDocHandler_Handle_wrongBody_NackDiscard(t *testing.T) { func TestApiSpecDocHandler_Handle_publishError_NackDiscard(t *testing.T) { ctrl := gomock.NewController(t) pub := publisher.NewMockPublisher(ctrl) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Infof(gomock.Any(), gomock.Any()) + log.EXPECT().Error(gomock.Any()) queueName := "test queue" conf := config.QueueConfig{ ScrapingResultQueue: queueName, @@ -35,7 +42,7 @@ func TestApiSpecDocHandler_Handle_publishError_NackDiscard(t *testing.T) { pub.EXPECT().Publish(gomock.Any(), gomock.Eq([]string{queueName}), gomock.Any()).Times(1). Return(errors.New("publish error")) - handl := NewApiSpecDocHandler(pub, conf) + handl := NewApiSpecDocHandler(pub, conf, log) body := `{"FileUrl":"test url","IsNotifyUser":false}` delivery := rabbitmq.Delivery{ Delivery: amqp091.Delivery{Body: []byte(body)}, @@ -47,13 +54,16 @@ func TestApiSpecDocHandler_Handle_publishError_NackDiscard(t *testing.T) { func TestApiSpecDocHandler_Handle_allCorrectNotificationFalse_called1TimeAck(t *testing.T) { ctrl := gomock.NewController(t) pub := publisher.NewMockPublisher(ctrl) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Infof(gomock.Any(), gomock.Any()) + log.EXPECT().Info(gomock.Any()) queueName := "test queue" conf := config.QueueConfig{ ScrapingResultQueue: queueName, } pub.EXPECT().Publish(gomock.Any(), gomock.Eq([]string{queueName}), gomock.Any()).Times(1).Return(nil) - handl := NewApiSpecDocHandler(pub, conf) + handl := NewApiSpecDocHandler(pub, conf, log) body := `{"FileUrl":"test url","IsNotifyUser":false}` delivery := rabbitmq.Delivery{ Delivery: amqp091.Delivery{Body: []byte(body)}, @@ -65,6 +75,9 @@ func TestApiSpecDocHandler_Handle_allCorrectNotificationFalse_called1TimeAck(t * func TestApiSpecDocHandler_Handle_allCorrectNotificationFalse_called2TimesAck(t *testing.T) { ctrl := gomock.NewController(t) pub := publisher.NewMockPublisher(ctrl) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Infof(gomock.Any(), gomock.Any()) + log.EXPECT().Info(gomock.Any()) resQName := "test queue" notQName := "test notification queue" conf := config.QueueConfig{ @@ -74,7 +87,7 @@ func TestApiSpecDocHandler_Handle_allCorrectNotificationFalse_called2TimesAck(t firstCall := pub.EXPECT().Publish(gomock.Any(), gomock.Eq([]string{resQName}), gomock.Any()).Times(1).Return(nil) pub.EXPECT().Publish(gomock.Any(), gomock.Eq([]string{notQName}), gomock.Any()).Times(1).Return(nil).After(firstCall) - handl := NewApiSpecDocHandler(pub, conf) + handl := NewApiSpecDocHandler(pub, conf, log) body := `{"FileUrl":"test url","IsNotifyUser":true}` delivery := rabbitmq.Delivery{ Delivery: amqp091.Delivery{Body: []byte(body)}, @@ -86,6 +99,10 @@ func TestApiSpecDocHandler_Handle_allCorrectNotificationFalse_called2TimesAck(t func TestApiSpecDocHandler_Handle_notificationError_called2TimesAck(t *testing.T) { ctrl := gomock.NewController(t) pub := publisher.NewMockPublisher(ctrl) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Infof(gomock.Any(), gomock.Any()) + log.EXPECT().Info(gomock.Any()) + log.EXPECT().Error(gomock.Any()) resQName := "test queue" notQName := "test notification queue" conf := config.QueueConfig{ @@ -99,7 +116,7 @@ func TestApiSpecDocHandler_Handle_notificationError_called2TimesAck(t *testing.T Return(errors.New("unexpected notification error")). After(firstCall) - handl := NewApiSpecDocHandler(pub, conf) + handl := NewApiSpecDocHandler(pub, conf, log) body := `{"FileUrl":"test url","IsNotifyUser":true}` delivery := rabbitmq.Delivery{ Delivery: amqp091.Delivery{Body: []byte(body)}, diff --git a/internal/queue/publisher/publisher.go b/internal/queue/publisher/publisher.go index e22ac77..971e98e 100644 --- a/internal/queue/publisher/publisher.go +++ b/internal/queue/publisher/publisher.go @@ -2,9 +2,9 @@ package publisher import ( "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/config" + "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger" "github.com/wagslane/go-rabbitmq" "io" - "log" ) //Publisher is just an interface for the library publisher which doesn't have one. @@ -21,17 +21,18 @@ type Publisher interface { //NewPublisher creates a publisher and connects to the rabbit under the hood. //This method appears to be not testable cause it combines 2 responsibilities: create an instance and connect to a queue. //I think we may rely on NewPublisher has been already tested in the library. -func NewPublisher(conf config.QueueConfig) (Publisher, error) { +func NewPublisher(conf config.QueueConfig, log logger.Logger) (Publisher, error) { return rabbitmq.NewPublisher( conf.Url, rabbitmq.Config{}, + rabbitmq.WithPublisherOptionsLogger(log), ) } -func ClosePublisher(publisher Publisher) { - log.Println("closing publisher") +func ClosePublisher(publisher Publisher, log logger.Logger) { + log.Info("closing publisher") err := publisher.Close() if err != nil { - log.Println("error while closing publisher: ", err) + log.Error("error while closing publisher: ", err) } } diff --git a/internal/queue/publisher/publisher_test.go b/internal/queue/publisher/publisher_test.go index 2d5cab6..1ed940a 100644 --- a/internal/queue/publisher/publisher_test.go +++ b/internal/queue/publisher/publisher_test.go @@ -2,6 +2,7 @@ package publisher import ( "github.com/golang/mock/gomock" + mock_logger "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/logger/mocks" publisher "github.com/rog-golang-buddies/api-hub_data-scraping-service/internal/queue/publisher/mocks" "testing" ) @@ -9,6 +10,9 @@ import ( func TestClosePublisher(t *testing.T) { ctrl := gomock.NewController(t) pub := publisher.NewMockPublisher(ctrl) + log := mock_logger.NewMockLogger(ctrl) + log.EXPECT().Info(gomock.Any()) + pub.EXPECT().Close().Return(nil) - ClosePublisher(pub) + ClosePublisher(pub, log) }