diff --git a/.env b/.env index 4df529f..0d9554d 100644 --- a/.env +++ b/.env @@ -10,6 +10,8 @@ MIGRATION_DSN="host=pg port=5432 dbname=chat user=chat-user password=chat-passwo GRPC_HOST=localhost GRPC_PORT=50052 +LOGGER_LEVEL=info + HTTP_HOST=localhost HTTP_PORT=8081 diff --git a/.gitignore b/.gitignore index b3e416d..7d43f7e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /bin /coverage.out /tls/ -/vendor.protogen \ No newline at end of file +/vendor.protogen +/logs/ diff --git a/go.mod b/go.mod index 9be5a54..7ca541d 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,10 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 ) -require gopkg.in/yaml.v3 v3.0.1 // indirect +require ( + go.uber.org/multierr v1.10.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) require ( github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -42,8 +45,10 @@ require ( require ( github.com/brianvoe/gofakeit/v7 v7.1.2 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/natefinch/lumberjack v2.0.0+incompatible github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 + go.uber.org/zap v1.27.0 golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.19.0 // indirect diff --git a/go.sum b/go.sum index 89701c2..e59b8c3 100644 --- a/go.sum +++ b/go.sum @@ -156,6 +156,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -213,11 +215,15 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/internal/api/chat/create_chat.go b/internal/api/chat/create_chat.go index 644a487..331ca45 100644 --- a/internal/api/chat/create_chat.go +++ b/internal/api/chat/create_chat.go @@ -2,11 +2,13 @@ package chat import ( "context" - "log" "github.com/solumD/chat-server/internal/api/chat/errors" "github.com/solumD/chat-server/internal/converter" + "github.com/solumD/chat-server/internal/logger" desc "github.com/solumD/chat-server/pkg/chat_v1" + + "go.uber.org/zap" ) // CreateChat отправляет запрос в сервисный слой на создание чата @@ -21,7 +23,7 @@ func (i *API) CreateChat(ctx context.Context, req *desc.CreateChatRequest) (*des return nil, err } - log.Printf("inserted chat with id %d", chatID) + logger.Info("inserted chat", zap.Int64("chatID", chatID)) return &desc.CreateChatResponse{ Id: chatID, diff --git a/internal/api/chat/delete_chat.go b/internal/api/chat/delete_chat.go index d22306b..9d17a62 100644 --- a/internal/api/chat/delete_chat.go +++ b/internal/api/chat/delete_chat.go @@ -5,8 +5,10 @@ import ( "fmt" "log" + "github.com/solumD/chat-server/internal/logger" desc "github.com/solumD/chat-server/pkg/chat_v1" + "go.uber.org/zap" "google.golang.org/protobuf/types/known/emptypb" ) @@ -21,7 +23,7 @@ func (i *API) DeleteChat(ctx context.Context, req *desc.DeleteChatRequest) (*emp return nil, err } - log.Printf("deleted chat with id %d", req.GetId()) + logger.Info("deleted chat", zap.Int64("chatID", req.GetId())) return &emptypb.Empty{}, nil } diff --git a/internal/api/chat/send_message.go b/internal/api/chat/send_message.go index 5d791ad..27efd8c 100644 --- a/internal/api/chat/send_message.go +++ b/internal/api/chat/send_message.go @@ -2,12 +2,13 @@ package chat import ( "context" - "log" "github.com/solumD/chat-server/internal/api/chat/errors" "github.com/solumD/chat-server/internal/converter" + "github.com/solumD/chat-server/internal/logger" desc "github.com/solumD/chat-server/pkg/chat_v1" + "go.uber.org/zap" "google.golang.org/protobuf/types/known/emptypb" ) @@ -22,7 +23,7 @@ func (i *API) SendMessage(ctx context.Context, req *desc.SendMessageRequest) (*e return nil, err } - log.Printf("sent message in chat %d", req.GetId()) + logger.Info("sent message in chat", zap.Int64("chatID", req.GetId())) return &emptypb.Empty{}, nil } diff --git a/internal/api/chat/tests/create_chat_test.go b/internal/api/chat/tests/create_chat_test.go index 469ad5e..3b79578 100644 --- a/internal/api/chat/tests/create_chat_test.go +++ b/internal/api/chat/tests/create_chat_test.go @@ -7,6 +7,7 @@ import ( "github.com/solumD/chat-server/internal/api/chat" "github.com/solumD/chat-server/internal/api/chat/errors" + "github.com/solumD/chat-server/internal/logger" "github.com/solumD/chat-server/internal/model" "github.com/solumD/chat-server/internal/service" serviceMocks "github.com/solumD/chat-server/internal/service/mocks" @@ -103,6 +104,8 @@ func TestCreateChat(t *testing.T) { }, } + logger.MockInit() + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/internal/api/chat/tests/delete_chat_test.go b/internal/api/chat/tests/delete_chat_test.go index 5134280..b75e07c 100644 --- a/internal/api/chat/tests/delete_chat_test.go +++ b/internal/api/chat/tests/delete_chat_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/solumD/chat-server/internal/api/chat" + "github.com/solumD/chat-server/internal/logger" "github.com/solumD/chat-server/internal/service" serviceMocks "github.com/solumD/chat-server/internal/service/mocks" desc "github.com/solumD/chat-server/pkg/chat_v1" @@ -93,6 +94,8 @@ func TestDeleteChat(t *testing.T) { }, } + logger.MockInit() + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/internal/api/chat/tests/send_message_test.go b/internal/api/chat/tests/send_message_test.go index 79f92f7..c74d39a 100644 --- a/internal/api/chat/tests/send_message_test.go +++ b/internal/api/chat/tests/send_message_test.go @@ -7,6 +7,7 @@ import ( "github.com/solumD/chat-server/internal/api/chat" "github.com/solumD/chat-server/internal/api/chat/errors" + "github.com/solumD/chat-server/internal/logger" "github.com/solumD/chat-server/internal/model" "github.com/solumD/chat-server/internal/service" serviceMocks "github.com/solumD/chat-server/internal/service/mocks" @@ -104,6 +105,8 @@ func TestSendMessage(t *testing.T) { }, } + logger.MockInit() + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/internal/app/app.go b/internal/app/app.go index c4d3d21..351856c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,6 +11,7 @@ import ( "github.com/solumD/chat-server/internal/closer" "github.com/solumD/chat-server/internal/config" "github.com/solumD/chat-server/internal/interceptor" + "github.com/solumD/chat-server/internal/logger" desc "github.com/solumD/chat-server/pkg/chat_v1" _ "github.com/solumD/chat-server/statik" // @@ -136,9 +137,12 @@ func (a *App) initGRPCServer(ctx context.Context) { log.Fatalf("failed to load TLS keys: %v", err) } + logger.Init(logger.GetCore(logger.GetAtomicLevel(a.serviceProvider.LoggerConfig().Level()))) + a.grpcServer = grpc.NewServer( grpc.UnaryInterceptor( grpcMW.ChainUnaryServer( + interceptor.LogInterceptor, interceptor.ValidateInterceptor, interceptor.NewAuthInterceptor(a.serviceProvider.AuthClient(ctx)).Get()), ), diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index 9c87a36..ff3c7fc 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -16,6 +16,7 @@ import ( chatRepo "github.com/solumD/chat-server/internal/repository/chat" "github.com/solumD/chat-server/internal/service" chatSrv "github.com/solumD/chat-server/internal/service/chat" + "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -27,6 +28,7 @@ type serviceProvider struct { httpConfig config.HTTPConfig swaggerConfig config.SwaggerConfig authConfig config.AuthConfig + loggerConfig config.LoggerConfig dbClient db.Client txManager db.TxManager @@ -70,6 +72,20 @@ func (s *serviceProvider) GRPCConfig() config.GRPCConfig { return s.grpcConfig } +// LoggerConfig инициализирует конфиг логгера +func (s *serviceProvider) LoggerConfig() config.LoggerConfig { + if s.loggerConfig == nil { + cfg, err := config.NewLoggerConfig() + if err != nil { + log.Fatalf("failed to get logger config:%v", err) + } + + s.loggerConfig = cfg + } + + return s.loggerConfig +} + // HTTPConfig ининициализирует конфиг http сервера func (s *serviceProvider) HTTPConfig() config.HTTPConfig { if s.httpConfig == nil { diff --git a/internal/client/db/pg/pg.go b/internal/client/db/pg/pg.go index ddf80a4..892a514 100644 --- a/internal/client/db/pg/pg.go +++ b/internal/client/db/pg/pg.go @@ -2,11 +2,11 @@ package pg import ( "context" - "fmt" - "log" "github.com/solumD/chat-server/internal/client/db" "github.com/solumD/chat-server/internal/client/db/prettier" + "github.com/solumD/chat-server/internal/logger" + "go.uber.org/zap" "github.com/georgysavva/scany/pgxscan" "github.com/jackc/pgconn" @@ -114,9 +114,6 @@ func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { func logQuery(ctx context.Context, q db.Query, args ...interface{}) { prettyQuery := prettier.Pretty(q.QueryRaw, prettier.PlaceholderDollar, args...) - log.Println( - ctx, - fmt.Sprintf("sql: %s", q.Name), - fmt.Sprintf("query: %s", prettyQuery), - ) + logger.Debug( + "", zap.Any("ctx", ctx), zap.String("sql", q.Name), zap.String("query", prettyQuery)) } diff --git a/internal/config/config.go b/internal/config/config.go index 21e08d7..a2e825d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,6 +9,11 @@ type GRPCConfig interface { Address() string } +// LoggerConfig интерфейс конфига логгера +type LoggerConfig interface { + Level() string +} + // PGConfig is interface of a postgres config type PGConfig interface { DSN() string diff --git a/internal/config/logger.go b/internal/config/logger.go new file mode 100644 index 0000000..127cdd4 --- /dev/null +++ b/internal/config/logger.go @@ -0,0 +1,31 @@ +package config + +import ( + "errors" + "os" +) + +const ( + loggerLevelEnvName = "LOGGER_LEVEL" +) + +type loggerConfig struct { + level string +} + +// NewLoggerConfig returns new grpc config +func NewLoggerConfig() (LoggerConfig, error) { + level := os.Getenv(loggerLevelEnvName) + if len(level) == 0 { + return nil, errors.New("logger level not found") + } + + return &loggerConfig{ + level: level, + }, nil +} + +// Level returns level of logger +func (cfg *loggerConfig) Level() string { + return cfg.level +} diff --git a/internal/interceptor/logger.go b/internal/interceptor/logger.go new file mode 100644 index 0000000..b5d5ae2 --- /dev/null +++ b/internal/interceptor/logger.go @@ -0,0 +1,25 @@ +package interceptor + +import ( + "context" + "time" + + "github.com/solumD/chat-server/internal/logger" + + "go.uber.org/zap" + "google.golang.org/grpc" +) + +// LogInterceptor логирует содержание запроса +func LogInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + now := time.Now() + + res, err := handler(ctx, req) + if err != nil { + logger.Error(err.Error(), zap.String("method", info.FullMethod), zap.Any("req", req)) + } + + logger.Info("request", zap.String("method", info.FullMethod), zap.Any("req", req), zap.Any("res", res), zap.Duration("duration", time.Since(now))) + + return res, err +} diff --git a/internal/logger/funcs.go b/internal/logger/funcs.go new file mode 100644 index 0000000..50f64b5 --- /dev/null +++ b/internal/logger/funcs.go @@ -0,0 +1,59 @@ +package logger + +import ( + "log" + "os" + + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + logsFilePath = "logs/app.log" + fileMegabytesMaxSize = 10 + fileMaxBackupsCount = 3 + fileMaxAge = 7 // days +) + +// GetCore возвращает настройки логгера +func GetCore(level zap.AtomicLevel) zapcore.Core { + stdout := zapcore.AddSync(os.Stdout) + + file := zapcore.AddSync(&lumberjack.Logger{ + Filename: logsFilePath, + MaxSize: fileMegabytesMaxSize, + MaxBackups: fileMaxBackupsCount, + MaxAge: fileMaxAge, + }) + + productionCfg := zap.NewProductionEncoderConfig() + productionCfg.TimeKey = "timestamp" + productionCfg.EncodeTime = zapcore.ISO8601TimeEncoder + + developmentCfg := zap.NewDevelopmentEncoderConfig() + developmentCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder + + consoleEncoder := zapcore.NewConsoleEncoder(developmentCfg) + fileEncoder := zapcore.NewJSONEncoder(productionCfg) + + return zapcore.NewTee( + zapcore.NewCore(consoleEncoder, stdout, level), + zapcore.NewCore(fileEncoder, file, level), + ) +} + +// GetAtomicLevel возвращает уровень логгирования +func GetAtomicLevel(logLevel string) zap.AtomicLevel { + var level zapcore.Level + if err := level.Set(logLevel); err != nil { + log.Fatalf("failed to set log level: %v", err) + } + + return zap.NewAtomicLevelAt(level) +} + +// MockInit инициализирует логгер для моков +func MockInit() { + globalLogger = zap.NewNop() +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..d230bcf --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,43 @@ +package logger + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var globalLogger *zap.Logger + +// Init инициализирует глобальный логгер +func Init(core zapcore.Core, options ...zap.Option) { + globalLogger = zap.New(core, options...) +} + +// Debug обертка над методом +func Debug(msg string, fields ...zap.Field) { + globalLogger.Debug(msg, fields...) +} + +// Info обертка над методом +func Info(msg string, fields ...zap.Field) { + globalLogger.Info(msg, fields...) +} + +// Warn обертка над методом +func Warn(msg string, fields ...zap.Field) { + globalLogger.Warn(msg, fields...) +} + +// Error обертка над методом +func Error(msg string, fields ...zap.Field) { + globalLogger.Error(msg, fields...) +} + +// Fatal обертка над методом +func Fatal(msg string, fields ...zap.Field) { + globalLogger.Fatal(msg, fields...) +} + +// WithOptions обертка над методом +func WithOptions(opts ...zap.Option) *zap.Logger { + return globalLogger.WithOptions(opts...) +} diff --git a/internal/repository/chat/repo_funcs.go b/internal/repository/chat/funcs.go similarity index 100% rename from internal/repository/chat/repo_funcs.go rename to internal/repository/chat/funcs.go diff --git a/internal/repository/chat/repository.go b/internal/repository/chat/repository.go index 7b94591..55cb5ca 100644 --- a/internal/repository/chat/repository.go +++ b/internal/repository/chat/repository.go @@ -3,7 +3,6 @@ package chat import ( "context" "fmt" - "log" "github.com/solumD/chat-server/internal/client/db" "github.com/solumD/chat-server/internal/model" @@ -160,12 +159,10 @@ func (r *repo) SendMessage(ctx context.Context, message *model.Message) (*emptyp QueryRaw: query, } - res, err := r.db.DB().ExecContext(ctx, q, args...) + _, err = r.db.DB().ExecContext(ctx, q, args...) if err != nil { return nil, err } - log.Printf("inserted %d message", res.RowsAffected()) - return &emptypb.Empty{}, nil } diff --git a/internal/service/chat/tests/create_chat_test.go b/internal/service/chat/tests/create_chat_test.go index 7a2e7dc..6737237 100644 --- a/internal/service/chat/tests/create_chat_test.go +++ b/internal/service/chat/tests/create_chat_test.go @@ -7,6 +7,7 @@ import ( "github.com/solumD/chat-server/internal/client/db" "github.com/solumD/chat-server/internal/client/db/mocks" + "github.com/solumD/chat-server/internal/logger" "github.com/solumD/chat-server/internal/model" "github.com/solumD/chat-server/internal/repository" repoMocks "github.com/solumD/chat-server/internal/repository/mocks" @@ -120,6 +121,8 @@ func TestCreateChat(t *testing.T) { }, } + logger.MockInit() + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/internal/service/chat/tests/delete_chat_test.go b/internal/service/chat/tests/delete_chat_test.go index aba9eb3..8f75a0d 100644 --- a/internal/service/chat/tests/delete_chat_test.go +++ b/internal/service/chat/tests/delete_chat_test.go @@ -7,6 +7,7 @@ import ( "github.com/solumD/chat-server/internal/client/db" "github.com/solumD/chat-server/internal/client/db/mocks" + "github.com/solumD/chat-server/internal/logger" "github.com/solumD/chat-server/internal/repository" repoMocks "github.com/solumD/chat-server/internal/repository/mocks" "github.com/solumD/chat-server/internal/service/chat" @@ -92,6 +93,8 @@ func TestDeleteChat(t *testing.T) { }, } + logger.MockInit() + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/internal/service/chat/tests/send_message_test.go b/internal/service/chat/tests/send_message_test.go index 1c7c432..6dd9f4b 100644 --- a/internal/service/chat/tests/send_message_test.go +++ b/internal/service/chat/tests/send_message_test.go @@ -7,6 +7,7 @@ import ( "github.com/solumD/chat-server/internal/client/db" "github.com/solumD/chat-server/internal/client/db/mocks" + "github.com/solumD/chat-server/internal/logger" "github.com/solumD/chat-server/internal/model" "github.com/solumD/chat-server/internal/repository" repoMocks "github.com/solumD/chat-server/internal/repository/mocks" @@ -147,6 +148,8 @@ func TestSendMessage(t *testing.T) { }, } + logger.MockInit() + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) {