diff --git a/.gitignore b/.gitignore index 34bcbf1..efb2e89 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ cmd/agent/main cmd/agent/agent cmd/server/main cmd/server/server - +cmd/staticlint/staticlint # Dependency directories (remove the comment below to include it) vendor/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9d1598 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: build test clean + +build: + go clean -testcache + GOOS=linux GOARCH=amd64 go build -buildvcs=false -o=cmd/server ./cmd/server/... + GOOS=linux GOARCH=amd64 go build -buildvcs=false -ldflags "-X main.buildVersion=v1.0.1 -X main.buildDate=$(shell date +'%Y-%m-%d') -X main.buildCommit=$(shell git rev-parse HEAD)" -o=cmd/agent ./cmd/agent/... + +test: build + GOOS=linux GOARCH=amd64 go build -buildvcs=false -o=cmd/staticlint ./cmd/staticlint/... + cmd/staticlint/staticlint ./... + go mod tidy + go mod vendor + go test ./... diff --git a/Questions.md b/Questions.md index a49e7b0..0d46351 100644 --- a/Questions.md +++ b/Questions.md @@ -27,10 +27,28 @@ go tool pprof -http=":9090" -seconds=30 heap.out 3. godoc * Не могу добиться чтоб заработали ссылкы на структуры в другом файле например [domain.Metrics]. Хотя в документации например ioutil.ReadDir() ссылки работают. +* -play + +4. staticlint + +Не работает чтение файла конфигурации при вызове через +```go +go vet -vettool=cmd/staticlint/staticlint ./... +``` + +есть ли идеи? + + +5. go vet - Можно ли отключить конкретный чекер на конкретной строке? + +Прим. internal/exitcheck/testdata/pkg2/pkg2.go + + +6. (__ответ получен__ - Да) Цикл в b.Run * play -4. Цикл в b.Run (__ответ получен__ - Да) +7. Цикл в b.Run (__ответ получен__ - Да) Цикл в bench-тесте: до b.N или до фиксированного testN? @@ -40,21 +58,16 @@ go tool pprof -http=":9090" -seconds=30 heap.out *Если нужно делать профилирование(cpu/memory) - то нужно ставить функции в одинаковые условия запускать цикл фиксированное число раз; затем смотреть на результаты профилирования* -5. Статический анализатор для выявления не проверенных ошибок типа (отложить до следующего спринат? (там про go vet было?)) -```go - _, _ = io.ReadAll(req.Body) - ``` - и необрабываемого проброса ошибок - ```go - if _, err ;= io.ReadAll(req.Body); err!=nil { - return err - } - ``` - -6. Не понял комментарий на строку -```go -logger.Infow("GetAllMetrics", "status", "start") + +7. (__ответ получен__) Статический анализатор для выявления не проверенных ошибок типа (отложить до следующего спринат? (там про go vet было?)) + +[golangci](https://golangci-lint.run/) + +[errcheck](https://github.com/kisielk/errcheck) + +```bash +golangci-lint run ./... + ``` -"Не совсем понимаю зачем ты пишешь это через запятые". -Обсудить - практику формирования записей в лог (кроме requestID - так как requestID я уже научился добавлять ) -Возможно - запись о начале работы метода, запись о завершении, времени выполнения? \ No newline at end of file + + diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 4c2cfd6..e4dae66 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -2,22 +2,38 @@ package main import ( "context" + "fmt" "log" "os" "os/signal" "syscall" "github.com/StasMerzlyakov/go-metrics/internal/agent" - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable" + "github.com/StasMerzlyakov/go-metrics/internal/agent/retriable" "github.com/StasMerzlyakov/go-metrics/internal/config" ) +var ( + buildVersion string = "N/A" + buildDate string = "N/A" + buildCommit string = "N/A" +) + +func printVersion() { + fmt.Printf("Build version: %s\n", buildVersion) + fmt.Printf("Build date: %s\n", buildDate) + fmt.Printf("Build commit: %s\n", buildCommit) +} + type Agent interface { Start(ctx context.Context) Wait() } func main() { + + printVersion() + agentCfg, err := config.LoadAgentConfig() if err != nil { log.Fatal(err) diff --git a/cmd/server/server.go b/cmd/server/server.go index 8b15517..082285a 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -9,7 +9,6 @@ import ( "syscall" "time" - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable" "github.com/StasMerzlyakov/go-metrics/internal/config" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/fs/backup" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/handler" @@ -17,9 +16,9 @@ import ( "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/compress" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/digest" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/logging" + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/retry" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/memory" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/postgres" - "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/wrapper" "github.com/StasMerzlyakov/go-metrics/internal/server/app" "github.com/StasMerzlyakov/go-metrics/internal/server/domain" "github.com/go-chi/chi/v5" @@ -36,6 +35,7 @@ type Server interface { func createMiddleWareList(srvConf *config.ServerConfiguration) []func(http.Handler) http.Handler { var mwList []func(http.Handler) http.Handler + mwList = append(mwList, logging.EncrichWithRequestIDMW()) mwList = append(mwList, logging.NewLoggingResponseMW()) if srvConf.Key != "" { mwList = append(mwList, digest.NewWriteHashDigestResponseHeaderMW(srvConf.Key)) @@ -45,6 +45,30 @@ func createMiddleWareList(srvConf *config.ServerConfiguration) []func(http.Handl mwList = append(mwList, logging.NewLoggingRequestMW()) + // при работе с Postgres добавляем retriable-обертку + // функция, обрабатывающая ошибки; в нужных случаях выкидывает нужную ошибку(domain.ErrDBConnection) + // на которую реагирует retriableWrapper + pgErrPreProcFn := func(err error) error { + if err == nil { + return nil + } + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgerrcode.IsConnectionException(pgErr.Code) { + return domain.ErrDBConnection + } + } + + var conErr *pgconn.ConnectError + if errors.As(err, &conErr) { + return domain.ErrDBConnection + } + return err + } + mwList = append(mwList, retry.NewRetriableRequestMWConf( + time.Duration(time.Second), time.Duration(2*time.Second), 4, pgErrPreProcFn, + )) + return mwList } @@ -83,32 +107,6 @@ func main() { if srvConf.DatabaseDSN != "" { storage = postgres.NewStorage(srvConf.DatabaseDSN) - - // при работе с Postgres добавляем retriable-обертку - - // функция, обрабатывающая ошибки; в нужных случаях выкидывает нужную ошибку(domain.ErrDBConnection) - // на которую реагирует retriableWrapper - pgErrPreProcFn := func(err error) error { - if err == nil { - return nil - } - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { - if pgerrcode.IsConnectionException(pgErr.Code) { - return domain.ErrDBConnection - } - } - - var conErr *pgconn.ConnectError - if errors.As(err, &conErr) { - return domain.ErrDBConnection - } - return err - } - - retriableConf := retriable.DefaultConfFn(domain.ErrDBConnection, pgErrPreProcFn) - storage = wrapper.NewRetriable(retriableConf, sugarLog, storage) - } else { storage = memory.NewStorage() } diff --git a/cmd/staticlint/staticlint.go b/cmd/staticlint/staticlint.go new file mode 100644 index 0000000..548c446 --- /dev/null +++ b/cmd/staticlint/staticlint.go @@ -0,0 +1,172 @@ +// Package main contains project multicheck linter: +// - standart golang.org/x/tools/go/analysis +// - SAXXX + QF1004 staticcheck.io +// - github.com/jingyugao/rowserrcheck/passes/rowserr +// - github.com/kisielk/errcheck/errcheck +// +// If `config.json` exists in the current project directory it will be used for reading excludedchecks. +// +// content: { +// +// "excludedcheck": ["appends", "QF1004"] +// } +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/StasMerzlyakov/go-metrics/internal/osexit" + "github.com/jingyugao/rowserrcheck/passes/rowserr" + "github.com/kisielk/errcheck/errcheck" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/multichecker" + "golang.org/x/tools/go/analysis/passes/appends" + "golang.org/x/tools/go/analysis/passes/asmdecl" + "golang.org/x/tools/go/analysis/passes/assign" + "golang.org/x/tools/go/analysis/passes/atomic" + "golang.org/x/tools/go/analysis/passes/bools" + "golang.org/x/tools/go/analysis/passes/buildtag" + "golang.org/x/tools/go/analysis/passes/cgocall" + "golang.org/x/tools/go/analysis/passes/composite" + "golang.org/x/tools/go/analysis/passes/copylock" + "golang.org/x/tools/go/analysis/passes/defers" + "golang.org/x/tools/go/analysis/passes/directive" + "golang.org/x/tools/go/analysis/passes/errorsas" + "golang.org/x/tools/go/analysis/passes/framepointer" + "golang.org/x/tools/go/analysis/passes/httpresponse" + "golang.org/x/tools/go/analysis/passes/ifaceassert" + "golang.org/x/tools/go/analysis/passes/loopclosure" + "golang.org/x/tools/go/analysis/passes/lostcancel" + "golang.org/x/tools/go/analysis/passes/nilfunc" + "golang.org/x/tools/go/analysis/passes/printf" + "golang.org/x/tools/go/analysis/passes/shift" + "golang.org/x/tools/go/analysis/passes/sigchanyzer" + "golang.org/x/tools/go/analysis/passes/slog" + "golang.org/x/tools/go/analysis/passes/stdmethods" + "golang.org/x/tools/go/analysis/passes/stringintconv" + "golang.org/x/tools/go/analysis/passes/structtag" + "golang.org/x/tools/go/analysis/passes/testinggoroutine" + "golang.org/x/tools/go/analysis/passes/tests" + "golang.org/x/tools/go/analysis/passes/timeformat" + "golang.org/x/tools/go/analysis/passes/unmarshal" + "golang.org/x/tools/go/analysis/passes/unreachable" + "golang.org/x/tools/go/analysis/passes/unsafeptr" + "golang.org/x/tools/go/analysis/passes/unusedresult" + "honnef.co/go/tools/staticcheck" +) + +type ConfigData struct { + ExcludedChecks []string `json:"excludedChecks"` +} + +const ConfigX = "config.json" + +func main() { + + var analyzers []*analysis.Analyzer + var cfg ConfigData + + ex, err := os.Executable() + if err != nil { + panic(err) + } + + exPath := filepath.Dir(ex) + confFilePath := filepath.Join(exPath, ConfigX) + fmt.Println(confFilePath) + + data, err := os.ReadFile(ConfigX) + if err != nil { + fmt.Printf("can't find conf file: %s; used default conifguration\n", ConfigX) + } else { + if err = json.Unmarshal(data, &cfg); err != nil { + panic(err) + } + } + + excludedChecks := make(map[string]bool) + for _, v := range cfg.ExcludedChecks { + excludedChecks[v] = true + } + + standardAnalyzers := []*analysis.Analyzer{ + appends.Analyzer, // check for missing values after append + asmdecl.Analyzer, // report mismatches between assembly files and Go declarations + assign.Analyzer, // check for useless assignments + atomic.Analyzer, // check for common mistakes using the sync/atomic package + bools.Analyzer, // check for common mistakes involving boolean operators + buildtag.Analyzer, // check //go:build and // +build directives + cgocall.Analyzer, // detect some violations of the cgo pointer passing rules + composite.Analyzer, // check for unkeyed composite literals + copylock.Analyzer, // check for locks erroneously passed by value + defers.Analyzer, // report common mistakes in defer statements + directive.Analyzer, // check Go toolchain directives such as //go:debug + errorsas.Analyzer, // report passing non-pointer or non-error values to errors.As + framepointer.Analyzer, // report assembly that clobbers the frame pointer before saving it + httpresponse.Analyzer, // check for mistakes using HTTP responses + ifaceassert.Analyzer, // detect impossible interface-to-interface type assertions + loopclosure.Analyzer, // check references to loop variables from within nested functions + lostcancel.Analyzer, // check cancel func returned by context.WithCancel is called + nilfunc.Analyzer, // check for useless comparisons between functions and nil + printf.Analyzer, // check consistency of Printf format strings and arguments + shift.Analyzer, // check for shifts that equal or exceed the width of the integer + sigchanyzer.Analyzer, // check for unbuffered channel of os.Signal + slog.Analyzer, // check for invalid structured logging calls + stdmethods.Analyzer, // check signature of methods of well-known interfaces + stringintconv.Analyzer, // check for string(int) conversions + structtag.Analyzer, // check that struct field tags conform to reflect.StructTag.Get + testinggoroutine.Analyzer, // report calls to (*testing.T).Fatal from goroutines started by a test + tests.Analyzer, // check for common mistaken usages of tests and examples + timeformat.Analyzer, // check for calls of (time.Time).Format or time.Parse with 2006-02-01 + unmarshal.Analyzer, // report passing non-pointer or non-interface values to unmarshal + unreachable.Analyzer, // check for unreachable code + unsafeptr.Analyzer, // check for invalid conversions of uintptr to unsafe.Pointer + unusedresult.Analyzer, // check for unused results of calls to some functions + } + + // golang.org/x/tools/go/analysis/passes analazers + for _, v := range standardAnalyzers { + if !excludedChecks[v.Name] { + analyzers = append(analyzers, v) + } + } + + // SA + QF1004 staticcheck.io + for _, v := range staticcheck.Analyzers { + if !excludedChecks[v.Analyzer.Name] { + if strings.HasPrefix(v.Analyzer.Name, "SA") || + v.Analyzer.Name == "QF1004" { // use strings.ReplaceAll instead of strings.Replace with n == -1 + analyzers = append(analyzers, v.Analyzer) + } + } + } + + // errcheck + errCheck := errcheck.Analyzer + if !excludedChecks[errCheck.Name] { + analyzers = append(analyzers, errCheck) // check for unchecked errors in Go code. + } + + // rowserrcheck + rser := rowserr.NewAnalyzer( + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/postgres", + ) + + if !excludedChecks[rser.Name] { + analyzers = append(analyzers, rser) // rowserrcheck is a static analysis tool which checks whether sql.Rows.Err is correctly checked + } + + // osexit + ocheck := osexit.Analyzer + if !excludedChecks[ocheck.Name] { + analyzers = append(analyzers, ocheck) // prohibits the use of a direct os.Exit call in the main function of the main package. + } + + multichecker.Main( + analyzers..., + ) +} diff --git a/config.json b/config.json new file mode 100644 index 0000000..f40872c --- /dev/null +++ b/config.json @@ -0,0 +1,3 @@ +{ + "excludedChecks": ["errcheck"] +} diff --git a/cover.sh b/cover.sh old mode 100644 new mode 100755 diff --git a/go.mod b/go.mod index c8b602e..d2f548e 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,64 @@ module github.com/StasMerzlyakov/go-metrics -go 1.19 +go 1.21 + +toolchain go1.22.2 require ( github.com/caarlos0/env v3.5.0+incompatible github.com/go-chi/chi/v5 v5.0.11 github.com/go-resty/resty/v2 v2.11.0 github.com/golang/mock v1.6.0 + github.com/google/uuid v1.6.0 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgx/v5 v5.5.4 + github.com/jingyugao/rowserrcheck v1.1.1 + github.com/kisielk/errcheck v1.7.0 github.com/pkg/errors v0.9.1 github.com/shirou/gopsutil/v3 v3.24.2 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.31.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 github.com/ungerik/go-pool v0.0.0-20140720100922-d102a2c7872a go.uber.org/zap v1.27.0 + golang.org/x/tools v0.21.1-0.20240514024235-59d9797072e7 + honnef.co/go/tools v0.4.7 ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/docker v25.0.5+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/klauspost/compress v1.16.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect @@ -31,11 +66,20 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9e9b6d5..aea6a1c 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,65 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= +github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 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= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= +github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -25,12 +70,38 @@ github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= +github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -54,18 +125,41 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV 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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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= +github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= +github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= +github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E= +github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0/go.mod h1:ZNYY8vumNCEG9YI59A9d6/YaMY49uwRhmeU563EzFGw= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/ungerik/go-pool v0.0.0-20140720100922-d102a2c7872a h1:Lfu+d2ZpWyfH6HyuQrGI3om39+yNuvk2OWT0UptrFAM= github.com/ungerik/go-pool v0.0.0-20140720100922-d102a2c7872a/go.mod h1:DcjcKDwgMN/tbplnqJjxJNonhx4QvgPsv5W3N+h6sFU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= 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= @@ -74,37 +168,50 @@ 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -113,36 +220,54 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240514024235-59d9797072e7 h1:DnP3aRQn/r68glNGB8/7+3iE77jA+YZZCxpfIXx2MdA= +golang.org/x/tools v0.21.1-0.20240514024235-59d9797072e7/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 40817ea..76934ed 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -1,3 +1,4 @@ +// Package agent содежит конфигурацию и код агента package agent import ( diff --git a/internal/common/wrapper/mocks/invoker.go b/internal/agent/mocks/invoker.go similarity index 92% rename from internal/common/wrapper/mocks/invoker.go rename to internal/agent/mocks/invoker.go index 665d928..480a450 100644 --- a/internal/common/wrapper/mocks/invoker.go +++ b/internal/agent/mocks/invoker.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable (interfaces: Logger) +// Source: github.com/StasMerzlyakov/go-metrics/internal/agent/retriable (interfaces: Logger) // Package mocks is a generated GoMock package. package mocks diff --git a/internal/common/wrapper/retriable/invoker.go b/internal/agent/retriable/invoker.go similarity index 92% rename from internal/common/wrapper/retriable/invoker.go rename to internal/agent/retriable/invoker.go index af9116d..cf6eeed 100644 --- a/internal/common/wrapper/retriable/invoker.go +++ b/internal/agent/retriable/invoker.go @@ -1,3 +1,4 @@ +// Package retriable содержит wrapper для реализации многократного вызова функции, при возникновении ошибки. package retriable import ( diff --git a/internal/common/wrapper/retriable/invoker_test.go b/internal/agent/retriable/invoker_test.go similarity index 95% rename from internal/common/wrapper/retriable/invoker_test.go rename to internal/agent/retriable/invoker_test.go index c91eabf..d2808a1 100644 --- a/internal/common/wrapper/retriable/invoker_test.go +++ b/internal/agent/retriable/invoker_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/mocks" - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable" + "github.com/StasMerzlyakov/go-metrics/internal/agent/mocks" + "github.com/StasMerzlyakov/go-metrics/internal/agent/retriable" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) diff --git a/internal/agent/retriable_result_sender.go b/internal/agent/retriable_result_sender.go index 16e385e..8489b88 100644 --- a/internal/agent/retriable_result_sender.go +++ b/internal/agent/retriable_result_sender.go @@ -5,7 +5,7 @@ import ( "strings" "syscall" - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable" + "github.com/StasMerzlyakov/go-metrics/internal/agent/retriable" "github.com/sirupsen/logrus" ) diff --git a/internal/config/doc.go b/internal/config/doc.go new file mode 100644 index 0000000..b2fd86b --- /dev/null +++ b/internal/config/doc.go @@ -0,0 +1,2 @@ +// Package config содержит функции для загрузки конфигурации agent и server +package config diff --git a/internal/osexit/doc.go b/internal/osexit/doc.go new file mode 100644 index 0000000..6c6be4c --- /dev/null +++ b/internal/osexit/doc.go @@ -0,0 +1,2 @@ +// Package osexit prohibits the use of a direct os.Exit call in the main function of the main package. +package osexit diff --git a/internal/osexit/exitcheck.go b/internal/osexit/exitcheck.go new file mode 100644 index 0000000..09b58cf --- /dev/null +++ b/internal/osexit/exitcheck.go @@ -0,0 +1,67 @@ +package osexit + +import ( + "go/ast" + "strings" + + "golang.org/x/tools/go/analysis" +) + +var Analyzer = &analysis.Analyzer{ + Name: "osexit", + Doc: "prohibits the use of os.Exit in the main function of the main package", + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + expr := func(fnDecl *ast.FuncDecl) { + if fnDecl.Name.Name == "main" { + for _, stmt := range fnDecl.Body.List { + if exptStmt, ok := stmt.(*ast.ExprStmt); ok { + if call, ok := exptStmt.X.(*ast.CallExpr); ok { + if isOsExitCall(call) { + pass.Reportf(exptStmt.Pos(), "os.Exit in main function") + } + } + } + + } + } + } + + for _, file := range pass.Files { + if len(file.Comments) > 0 { + firstComment := file.Comments[0] + if strings.HasPrefix(firstComment.Text(), "Code generated by 'go test'. DO NOT EDIT") { + // skip generated files + continue + } + } + if file.Name.Name == "main" { + ast.Inspect(file, func(node ast.Node) bool { + if fnDecl, ok := node.(*ast.FuncDecl); ok { + expr(fnDecl) + } + return true + }) + } + } + return nil, nil + +} + +func isOsExitCall(call *ast.CallExpr) bool { + + if selExpr, ok := call.Fun.(*ast.SelectorExpr); ok { + if packIdent, isIdent := selExpr.X.(*ast.Ident); !isIdent || packIdent.Name != "os" { + return false + } + + if selExpr.Sel.Name != "Exit" { + return false + } + return true + } + + return false +} diff --git a/internal/osexit/exitcheck_test.go b/internal/osexit/exitcheck_test.go new file mode 100644 index 0000000..d39592c --- /dev/null +++ b/internal/osexit/exitcheck_test.go @@ -0,0 +1,14 @@ +package osexit_test + +import ( + "testing" + + "github.com/StasMerzlyakov/go-metrics/internal/osexit" + + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestMyAnalyzer(t *testing.T) { + analysistest.Run(t, analysistest.TestData(), osexit.Analyzer, "./...") + +} diff --git a/internal/osexit/testdata/pkg1/main.go b/internal/osexit/testdata/pkg1/main.go new file mode 100644 index 0000000..491ad95 --- /dev/null +++ b/internal/osexit/testdata/pkg1/main.go @@ -0,0 +1,11 @@ +package main + +import "os" + +func main() { + os.Exit(1) // want "os.Exit in main function" +} + +func Fun2() { + os.Exit(2) +} diff --git a/internal/osexit/testdata/pkg2/pkg2.go b/internal/osexit/testdata/pkg2/pkg2.go new file mode 100644 index 0000000..aefd43e --- /dev/null +++ b/internal/osexit/testdata/pkg2/pkg2.go @@ -0,0 +1,8 @@ +// Package pkg2 exitcheck testdata package. +package pkg2 + +import "os" + +func Fun2() { + os.Exit(2) +} diff --git a/internal/osexit/testdata/pkg3/main.go b/internal/osexit/testdata/pkg3/main.go new file mode 100644 index 0000000..08fab1f --- /dev/null +++ b/internal/osexit/testdata/pkg3/main.go @@ -0,0 +1,11 @@ +// Code generated by 'go test'. DO NOT EDIT. + +package main + +import "os" + +func main() { + + os.Exit(1) + +} diff --git a/internal/server/adapter/fs/backup/json.go b/internal/server/adapter/fs/backup/json.go index a549cc2..150024a 100644 --- a/internal/server/adapter/fs/backup/json.go +++ b/internal/server/adapter/fs/backup/json.go @@ -1,3 +1,4 @@ +// Package backup отвечает за бэкап данных приложения package backup import ( @@ -26,9 +27,11 @@ type jsonFormatter struct { } func (jf *jsonFormatter) Write(ctx context.Context, metricses []domain.Metrics) error { - logger := domain.GetMainLogger() + + logger := domain.GetCtxLogger(ctx) + action := domain.GetAction(1) if jf.fileStoragePath == "" { - logger.Errorw("Write", "status", "error", "msg", "fileStoragePath is not specified") + logger.Errorw(action, "error", "fileStoragePath is not specified") return os.ErrNotExist } @@ -44,7 +47,7 @@ func (jf *jsonFormatter) Write(ctx context.Context, metricses []domain.Metrics) file, err = os.CreateTemp(tmpDir, tempFileTemplate) if err != nil { - logger.Infow("Write", "status", "ok", "error", "can't create temp file") + logger.Errorw(action, "error", "can't create temp file") return err } @@ -52,26 +55,26 @@ func (jf *jsonFormatter) Write(ctx context.Context, metricses []domain.Metrics) err = json.NewEncoder(file).Encode(metricses) if err != nil { - logger.Errorw("Write", "status", "error", "msg", err.Error()) + logger.Errorw(action, "msg", err.Error()) return err } - logger.Infow("Write", "status", "ok", "msg", fmt.Sprintf("metrics stored into file %v", jf.fileStoragePath)) return nil } func (jf *jsonFormatter) Read(ctx context.Context) ([]domain.Metrics, error) { - logger := domain.GetMainLogger() + logger := domain.GetCtxLogger(ctx) + action := domain.GetAction(1) var result []domain.Metrics if jf.fileStoragePath == "" { - logger.Errorw("Read", "status", "error", "msg", "fileStoragePath is not specified") + logger.Errorw(action, "error", "fileStoragePath is not specified") return result, os.ErrNotExist } file, err := os.Open(jf.fileStoragePath) if err != nil { if errors.Is(err, os.ErrNotExist) { - logger.Infow("Read", "status", "ok", "msg", fmt.Sprintf("dump file %v not exists", jf.fileStoragePath)) + logger.Infow(action, "error", fmt.Sprintf("dump file %v not exists", jf.fileStoragePath)) return result, os.ErrNotExist } return nil, err @@ -81,7 +84,7 @@ func (jf *jsonFormatter) Read(ctx context.Context) ([]domain.Metrics, error) { err = json.NewDecoder(file).Decode(&result) if err != nil { - logger.Infow("Read", "status", "error", "msg", fmt.Sprintf("can't restore backup from file %v", jf.fileStoragePath)) + logger.Errorw(action, "error", fmt.Sprintf("can't restore backup from file %v", jf.fileStoragePath)) return nil, err } diff --git a/internal/server/adapter/http/handler/admin_operation_handler.go b/internal/server/adapter/http/handler/admin_operation_handler.go index 985aaf1..e59dc1f 100644 --- a/internal/server/adapter/http/handler/admin_operation_handler.go +++ b/internal/server/adapter/http/handler/admin_operation_handler.go @@ -32,11 +32,15 @@ type adminOperationAdpater struct { // Возвращает [http.StatusOK] в случае успешной проверки. func (h *adminOperationAdpater) Ping(w http.ResponseWriter, req *http.Request) { - _, _ = io.ReadAll(req.Body) + if _, err := io.ReadAll(req.Body); err != nil { + handleAppError(req.Context(), w, err) + return + } + defer req.Body.Close() if err := h.adminApp.Ping(req.Context()); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } diff --git a/internal/server/adapter/http/handler/doc.go b/internal/server/adapter/http/handler/doc.go new file mode 100644 index 0000000..77b41fa --- /dev/null +++ b/internal/server/adapter/http/handler/doc.go @@ -0,0 +1,2 @@ +// Package handler содержит rest-api приложения +package handler diff --git a/internal/server/adapter/http/handler/metric_operation_handler.go b/internal/server/adapter/http/handler/metric_operation_handler.go index 5e43aee..59e58e6 100644 --- a/internal/server/adapter/http/handler/metric_operation_handler.go +++ b/internal/server/adapter/http/handler/metric_operation_handler.go @@ -3,7 +3,6 @@ package handler import ( "context" "encoding/json" - "errors" "fmt" "html/template" "io" @@ -18,8 +17,6 @@ import ( _ "github.com/golang/mock/mockgen/model" // обязательно для корректного запуска mockgen ) -var ErrMediaType = errors.New("UnsupportedMediaTypeError") // ошибку определяю здесь - она специфична для - //go:generate mockgen -destination "../mocks/$GOFILE" -package mocks . MetricApp type MetricApp interface { @@ -77,19 +74,19 @@ func (h *metricOperationAdapter) PostMetrics(w http.ResponseWriter, req *http.Re defer req.Body.Close() if err := h.checkContentType(ApplicationJSON, req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } var metrics []domain.Metrics if err := json.NewDecoder(req.Body).Decode(&metrics); err != nil { fullErr := fmt.Errorf("%w: json decode error - %v", domain.ErrDataFormat, err.Error()) - handleAppError(w, fullErr) + handleAppError(req.Context(), w, fullErr) return } if err := h.metricApp.UpdateAll(req.Context(), metrics); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -108,7 +105,7 @@ func (h *metricOperationAdapter) PostMetric(w http.ResponseWriter, req *http.Req defer req.Body.Close() if err := h.checkContentType(ApplicationJSON, req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -116,18 +113,18 @@ func (h *metricOperationAdapter) PostMetric(w http.ResponseWriter, req *http.Req if err := json.NewDecoder(req.Body).Decode(&metrics); err != nil { fullErr := fmt.Errorf("%w: json decode error - %v", domain.ErrDataFormat, err.Error()) - handleAppError(w, fullErr) + handleAppError(req.Context(), w, fullErr) return } updatedMetric, err := h.metricApp.Update(req.Context(), metrics) if err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if err := h.sendMetrics(w, updatedMetric); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } } @@ -147,31 +144,31 @@ func (h *metricOperationAdapter) ValueMetric(w http.ResponseWriter, req *http.Re defer req.Body.Close() if err := h.checkContentType(ApplicationJSON, req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } var metrics *domain.Metrics if err := json.NewDecoder(req.Body).Decode(&metrics); err != nil { fullErr := fmt.Errorf("%w: json decode error - %v", domain.ErrDataFormat, err.Error()) - handleAppError(w, fullErr) + handleAppError(req.Context(), w, fullErr) return } value, err := h.metricApp.Get(req.Context(), metrics.MType, metrics.ID) if err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if value == nil { err := fmt.Errorf("%w: unknown metric '%v'", domain.ErrNotFound, metrics.ID) - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if err := h.sendMetrics(w, value); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } } @@ -181,14 +178,14 @@ func (h *metricOperationAdapter) ValueMetric(w http.ResponseWriter, req *http.Re // ContentType: "text/plain" func (h *metricOperationAdapter) PostGauge(w http.ResponseWriter, req *http.Request) { if _, err := io.ReadAll(req.Body); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } defer req.Body.Close() if err := h.checkContentType(TextPlain, req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -197,12 +194,12 @@ func (h *metricOperationAdapter) PostGauge(w http.ResponseWriter, req *http.Requ var err error if name, err = h.extractName(req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if value, err = h.extractFloat64(req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -213,7 +210,7 @@ func (h *metricOperationAdapter) PostGauge(w http.ResponseWriter, req *http.Requ } if _, err := h.metricApp.Update(req.Context(), metrics); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } } @@ -223,13 +220,13 @@ func (h *metricOperationAdapter) PostGauge(w http.ResponseWriter, req *http.Requ // ContentType: "text/plain" func (h *metricOperationAdapter) PostCounter(w http.ResponseWriter, req *http.Request) { if _, err := io.ReadAll(req.Body); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } defer req.Body.Close() if err := h.checkContentType(TextPlain, req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -238,12 +235,12 @@ func (h *metricOperationAdapter) PostCounter(w http.ResponseWriter, req *http.Re var err error if name, err = h.extractName(req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if value, err = h.extractInt64(req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -254,7 +251,7 @@ func (h *metricOperationAdapter) PostCounter(w http.ResponseWriter, req *http.Re } if _, err := h.metricApp.Update(req.Context(), metrics); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } } @@ -264,7 +261,7 @@ func (h *metricOperationAdapter) PostCounter(w http.ResponseWriter, req *http.Re // ContentType: "text/plain" func (h *metricOperationAdapter) GetCounter(w http.ResponseWriter, req *http.Request) { if _, err := io.ReadAll(req.Body); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } defer req.Body.Close() @@ -275,24 +272,24 @@ func (h *metricOperationAdapter) GetCounter(w http.ResponseWriter, req *http.Req var err error if name, err = h.extractName(req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } value, err := h.metricApp.Get(req.Context(), domain.CounterType, name) if err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if value == nil { err := fmt.Errorf("%w: unknown metric '%v'", domain.ErrNotFound, name) - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if _, err := w.Write([]byte(fmt.Sprintf("%v", *value.Delta))); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } } @@ -302,7 +299,7 @@ func (h *metricOperationAdapter) GetCounter(w http.ResponseWriter, req *http.Req // ContentType: "text/plain" func (h *metricOperationAdapter) GetGauge(w http.ResponseWriter, req *http.Request) { if _, err := io.ReadAll(req.Body); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } defer req.Body.Close() @@ -313,24 +310,24 @@ func (h *metricOperationAdapter) GetGauge(w http.ResponseWriter, req *http.Reque var err error if name, err = h.extractName(req); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } value, err := h.metricApp.Get(req.Context(), domain.GaugeType, name) if err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if value == nil { err := fmt.Errorf("%w: unknown metric '%v'", domain.ErrNotFound, name) - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } if _, err := w.Write([]byte(fmt.Sprintf("%v", *value.Value))); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } } @@ -339,14 +336,14 @@ func (h *metricOperationAdapter) GetGauge(w http.ResponseWriter, req *http.Reque // GET / func (h *metricOperationAdapter) AllMetrics(w http.ResponseWriter, req *http.Request) { if _, err := io.ReadAll(req.Body); err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } defer req.Body.Close() metricses, err := h.metricApp.GetAllMetrics(req.Context()) if err != nil { - handleAppError(w, err) + handleAppError(req.Context(), w, err) return } @@ -354,7 +351,7 @@ func (h *metricOperationAdapter) AllMetrics(w http.ResponseWriter, req *http.Req if err := allMetricsViewTmplate.Execute(w, metricses); err != nil { fullErr := fmt.Errorf("%w: generate result error - %v", domain.ErrServerInternal, err.Error()) - handleAppError(w, fullErr) + handleAppError(req.Context(), w, fullErr) return } } @@ -391,7 +388,7 @@ func (h *metricOperationAdapter) sendMetrics(w http.ResponseWriter, metrics *dom func (h *metricOperationAdapter) checkContentType(expectedType string, req *http.Request) error { contentType := req.Header.Get("Content-Type") if contentType != "" && !strings.HasPrefix(contentType, expectedType) { - return fmt.Errorf("%w: only '%v' supported", ErrMediaType, expectedType) + return fmt.Errorf("%w: only '%v' supported", domain.ErrMediaType, expectedType) } return nil } diff --git a/internal/server/adapter/http/handler/useful.go b/internal/server/adapter/http/handler/useful.go index d475540..4aa668d 100644 --- a/internal/server/adapter/http/handler/useful.go +++ b/internal/server/adapter/http/handler/useful.go @@ -1,7 +1,7 @@ package handler import ( - "errors" + "context" "fmt" "net/http" @@ -42,34 +42,10 @@ func TodoResponse(res http.ResponseWriter, message string) { `, message) } -func handleAppError(w http.ResponseWriter, err error) { - logger := domain.GetMainLogger() +func handleAppError(ctx context.Context, w http.ResponseWriter, err error) { + logger := domain.GetCtxLogger(ctx) action := domain.GetAction(2) // интересует имя метода, из которого взывался handleAppError - - if errors.Is(err, ErrMediaType) { - logger.Infow(action, "status", "error", "msg", err.Error()) - http.Error(w, err.Error(), http.StatusUnsupportedMediaType) - return - } - - if errors.Is(err, domain.ErrDataFormat) || - errors.Is(err, domain.ErrDataDigestMismath) { - logger.Infow(action, "status", "error", "msg", err.Error()) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if errors.Is(err, domain.ErrServerInternal) || - errors.Is(err, domain.ErrDBConnection) { - logger.Infow(action, "status", "error", "msg", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if errors.Is(err, domain.ErrNotFound) { - logger.Infow(action, "status", "error", "msg", err.Error()) - http.Error(w, err.Error(), http.StatusNotFound) - return - } + logger.Infow(action, "error", err.Error()) + http.Error(w, err.Error(), domain.MapDomainErrorToHTTPStatusErr(err)) } diff --git a/internal/server/adapter/http/middleware/common.go b/internal/server/adapter/http/middleware/common.go index 13107ca..8bae86d 100644 --- a/internal/server/adapter/http/middleware/common.go +++ b/internal/server/adapter/http/middleware/common.go @@ -1,3 +1,4 @@ +// Package middleware содержит мидлы приложения package middleware import "net/http" @@ -10,3 +11,13 @@ func Conveyor(h http.Handler, middlewares ...Middleware) http.Handler { } return h } + +func ConveyorFunc(h http.Handler, middlewares ...Middleware) http.HandlerFunc { + handler := Conveyor(h, middlewares...) + return handler.ServeHTTP +} + +//go:generate mockgen -destination "./mocks/$GOFILE" -package mocks . Handler +type Handler interface { + ServeHTTP(http.ResponseWriter, *http.Request) +} diff --git a/internal/server/adapter/http/middleware/compress/compress_gzip_buffer_response_mw.go b/internal/server/adapter/http/middleware/compress/compress_gzip_buffer_response_mw.go index c5d3f3a..ee06cc2 100644 --- a/internal/server/adapter/http/middleware/compress/compress_gzip_buffer_response_mw.go +++ b/internal/server/adapter/http/middleware/compress/compress_gzip_buffer_response_mw.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" - "github.com/StasMerzlyakov/go-metrics/internal/server/domain" ) type bufferWriter struct { @@ -21,11 +20,10 @@ func (w bufferWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } -// Вариант мидлы через буфер. Можно оценить ответ. +// NewCompressGZIPBufferResponseMW Вариант мидлы через буфер. Можно оценить ответ. func NewCompressGZIPBufferResponseMW() middleware.Middleware { return func(next http.Handler) http.Handler { cmprFn := func(w http.ResponseWriter, r *http.Request) { - log := domain.GetMainLogger() acceptEncodingReqHeader := r.Header.Get("Accept-Encoding") if !strings.Contains(acceptEncodingReqHeader, "gzip") { next.ServeHTTP(w, r) @@ -39,19 +37,15 @@ func NewCompressGZIPBufferResponseMW() middleware.Middleware { w.Header().Add("Content-Encoding", "gzip") // добавляем заголовок gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) if err != nil { - log.Errorln("writeGZIP", "desc", "can't initialize gzip", "err", err.Error()) - http.Error(w, "can't initialize gzip", http.StatusInternalServerError) + http.Error(w, fmt.Errorf("can't initialize gzip - %w", err).Error(), http.StatusInternalServerError) return } defer gz.Close() _, err = gz.Write(buff.Bytes()) if err != nil { - log.Errorln("writeGZIP", "status", "err", "msg", fmt.Sprintf("can't initialize gzip: %v", err.Error())) - http.Error(w, "can't write gzip", http.StatusInternalServerError) + http.Error(w, fmt.Errorf("can't initialize gzip - %w", err).Error(), http.StatusInternalServerError) return } - - log.Infow("writeGZIP", "header", "Content-Type", "value", contentTypeRespHeader, "msg", "response will be zipped") } else { w.Write(buff.Bytes()) } diff --git a/internal/server/adapter/http/middleware/compress/compress_gzip_response_mw.go b/internal/server/adapter/http/middleware/compress/compress_gzip_response_mw.go index a8539df..3a8ad86 100644 --- a/internal/server/adapter/http/middleware/compress/compress_gzip_response_mw.go +++ b/internal/server/adapter/http/middleware/compress/compress_gzip_response_mw.go @@ -6,15 +6,12 @@ import ( "strings" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" - "github.com/StasMerzlyakov/go-metrics/internal/server/domain" gPool "github.com/ungerik/go-pool" - "go.uber.org/zap" ) -// Вариант мидлы без дополнительного буфера при обработке ответа. +// NewCompressGZIPResponseMW Вариант мидлы без дополнительного буфера при обработке ответа. func NewCompressGZIPResponseMW() middleware.Middleware { return func(next http.Handler) http.Handler { - log := domain.GetMainLogger() cmprFn := func(w http.ResponseWriter, r *http.Request) { acceptEncodingReqHeader := r.Header.Get("Accept-Encoding") if !strings.Contains(acceptEncodingReqHeader, "gzip") { @@ -22,8 +19,7 @@ func NewCompressGZIPResponseMW() middleware.Middleware { } else { gz := gPool.Gzip.GetWriter(w) defer gPool.Gzip.PutWriter(gz) - log.Infow("testAcceptEncoding", "AcceptEncoding", "exists", "value", acceptEncodingReqHeader) - next.ServeHTTP(gzipWriter{log: log, ResponseWriter: w, Writer: gz, IsGZIPHeaderRecorded: false, IsContentTypeVerified: false}, r) + next.ServeHTTP(gzipWriter{ResponseWriter: w, Writer: gz, IsGZIPHeaderRecorded: false, IsContentTypeVerified: false}, r) } } return http.HandlerFunc(cmprFn) @@ -31,7 +27,6 @@ func NewCompressGZIPResponseMW() middleware.Middleware { } type gzipWriter struct { - log *zap.SugaredLogger http.ResponseWriter Writer io.Writer IsGZIPHeaderRecorded bool @@ -47,7 +42,6 @@ func (w gzipWriter) Write(b []byte) (int, error) { strings.Contains(contentTypeRespHeader, "text/html") { w.IsGZIPHeaderRecorded = true w.Header().Add("Content-Encoding", "gzip") // добавляем заголовок - w.log.Infow("gzipResponse", "contentType", contentTypeRespHeader) } } diff --git a/internal/server/adapter/http/middleware/compress/doc.go b/internal/server/adapter/http/middleware/compress/doc.go new file mode 100644 index 0000000..5399263 --- /dev/null +++ b/internal/server/adapter/http/middleware/compress/doc.go @@ -0,0 +1,2 @@ +// Package compress содержит мидлы отвечающие за компрессию/декомпресию данных +package compress diff --git a/internal/server/adapter/http/middleware/compress/uncompress_gzip_request_mw.go b/internal/server/adapter/http/middleware/compress/uncompress_gzip_request_mw.go index bb0565a..c2ea1cc 100644 --- a/internal/server/adapter/http/middleware/compress/uncompress_gzip_request_mw.go +++ b/internal/server/adapter/http/middleware/compress/uncompress_gzip_request_mw.go @@ -2,12 +2,12 @@ package compress import ( "compress/gzip" + "fmt" "io" "net/http" "strings" "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" - "github.com/StasMerzlyakov/go-metrics/internal/server/domain" ) type gzipreadCloser struct { @@ -23,17 +23,14 @@ func NewUncompressGZIPRequestMW() middleware.Middleware { return func(next http.Handler) http.Handler { uncmprFn := func(w http.ResponseWriter, r *http.Request) { - log := domain.GetMainLogger() contentEncodingHeader := r.Header.Get("Content-Encoding") if strings.Contains(contentEncodingHeader, "gzip") { zr, err := gzip.NewReader(r.Body) if err != nil { - log.Infow("uncompress request error:", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("uncompress request error - %s", err.Error()), http.StatusInternalServerError) return } r.Body = gzipreadCloser{zr, r.Body} - log.Infow("readGZIP", "Content-Encoding", r.Header.Get("Content-Encoding"), "msq", "the request will be unzip") } next.ServeHTTP(w, r) } diff --git a/internal/server/adapter/http/middleware/digest/check_hash_digest_request_buffered_mw.go b/internal/server/adapter/http/middleware/digest/check_hash_digest_request_buffered_mw.go index 3d21086..d6bbda8 100644 --- a/internal/server/adapter/http/middleware/digest/check_hash_digest_request_buffered_mw.go +++ b/internal/server/adapter/http/middleware/digest/check_hash_digest_request_buffered_mw.go @@ -13,15 +13,16 @@ import ( "github.com/StasMerzlyakov/go-metrics/internal/server/domain" ) -// Реализация с буфером. Хэш проверяется прямо в мидле. Для этого читается req.Body +// NewCheckHashDigestRequestBufferedMW Реализация с буфером. Хэш проверяется прямо в мидле. Для этого читается req.Body func NewCheckHashDigestRequestBufferedMW(key string) middleware.Middleware { return func(next http.Handler) http.Handler { - log := domain.GetMainLogger() cmprFn := func(w http.ResponseWriter, req *http.Request) { + log := domain.GetCtxLogger(req.Context()) + action := domain.GetAction(1) hashSHA256Hex := req.Header.Get("HashSHA256") if hashSHA256Hex == "" { errMsg := fmt.Errorf("%w: HashSHA256 header is not specified", domain.ErrDataFormat) - log.Infow("check_hash_digest_request_mw", "err", errMsg.Error()) + log.Errorw(action, "error", errMsg.Error()) http.Error(w, errMsg.Error(), http.StatusNotFound) return } @@ -29,7 +30,7 @@ func NewCheckHashDigestRequestBufferedMW(key string) middleware.Middleware { hashSHA256, err := hex.DecodeString(hashSHA256Hex) if err != nil { errMsg := fmt.Errorf("%w: decode HashSHA256 header err: %v", domain.ErrDataDigestMismath, err.Error()) - log.Infow("check_hash_digest_request_mw", "err", errMsg.Error()) + log.Infow(action, "error", errMsg.Error()) http.Error(w, errMsg.Error(), http.StatusBadRequest) return } @@ -45,7 +46,7 @@ func NewCheckHashDigestRequestBufferedMW(key string) middleware.Middleware { if !bytes.Equal(hashSHA256, hashValue) { err := fmt.Errorf("%w: expected %v, actual %v", domain.ErrDataDigestMismath, hex.EncodeToString(hashSHA256), hex.EncodeToString(hashValue)) - log.Infow("check_hash_digest_request_mw", "err", err.Error()) + log.Infow(action, "error", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return diff --git a/internal/server/adapter/http/middleware/digest/check_hash_digest_request_mw.go b/internal/server/adapter/http/middleware/digest/check_hash_digest_request_mw.go index 320c384..97fab8f 100644 --- a/internal/server/adapter/http/middleware/digest/check_hash_digest_request_mw.go +++ b/internal/server/adapter/http/middleware/digest/check_hash_digest_request_mw.go @@ -12,10 +12,9 @@ import ( "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" "github.com/StasMerzlyakov/go-metrics/internal/server/domain" - "go.uber.org/zap" ) -// Реализация без буфера. Хэш проверяется при возникновении EOF при чтении req.Body +// NewCheckHashDigestRequestMW Реализация без буфера. Хэш проверяется при возникновении EOF при чтении req.Body // Требуется обязательное вычитывание req.Body в http.Handler и обработка ответа // // _, err := io.ReadAll(req.Body) @@ -25,7 +24,7 @@ import ( func NewCheckHashDigestRequestMW(key string) middleware.Middleware { return func(next http.Handler) http.Handler { cmprFn := func(w http.ResponseWriter, r *http.Request) { - log := domain.GetMainLogger() + log := domain.GetCtxLogger(r.Context()) if r.Method == http.MethodGet { next.ServeHTTP(w, r) @@ -34,7 +33,11 @@ func NewCheckHashDigestRequestMW(key string) middleware.Middleware { hashSHA256Hex := r.Header.Get("HashSHA256") if hashSHA256Hex == "" { - _, _ = io.ReadAll(r.Body) + if _, err := io.ReadAll(r.Body); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer r.Body.Close() errMsg := fmt.Errorf("%w: HashSHA256 header is not specified", domain.ErrDataFormat) @@ -45,7 +48,10 @@ func NewCheckHashDigestRequestMW(key string) middleware.Middleware { hashSHA256, err := hex.DecodeString(hashSHA256Hex) if err != nil { - _, _ = io.ReadAll(r.Body) + if _, err := io.ReadAll(r.Body); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } defer r.Body.Close() errMsg := fmt.Errorf("%w: decode HashSHA256 header err: %v", domain.ErrDataDigestMismath, err.Error()) @@ -59,7 +65,6 @@ func NewCheckHashDigestRequestMW(key string) middleware.Middleware { hasher: hasher, expected: hashSHA256, ReadCloser: r.Body, - log: log, } r.Body = reader next.ServeHTTP(w, r) @@ -73,7 +78,6 @@ type hasherReader struct { hasher hash.Hash expected []byte io.ReadCloser - log *zap.SugaredLogger } func (hr *hasherReader) Read(p []byte) (n int, err error) { @@ -94,10 +98,7 @@ func (hr *hasherReader) Read(p []byte) (n int, err error) { domain.ErrDataDigestMismath, hex.EncodeToString(hr.expected), hex.EncodeToString(value)) - hr.log.Infow("CheckHashDigestRequestMW", "status", "ERROR", "msg", fullErr.Error()) return 0, fullErr - } else { - hr.log.Infow("CheckHashDigestRequestMW", "status", "OK") } } return diff --git a/internal/server/adapter/http/middleware/digest/doc.go b/internal/server/adapter/http/middleware/digest/doc.go new file mode 100644 index 0000000..6ff9402 --- /dev/null +++ b/internal/server/adapter/http/middleware/digest/doc.go @@ -0,0 +1,2 @@ +// Package digest содержит мидлы, отвечающие за проверку и добавление sha256 заголовка +package digest diff --git a/internal/server/adapter/http/middleware/logging/doc.go b/internal/server/adapter/http/middleware/logging/doc.go new file mode 100644 index 0000000..c242f5f --- /dev/null +++ b/internal/server/adapter/http/middleware/logging/doc.go @@ -0,0 +1,2 @@ +// Package logging contains responding for logging request information middleware +package logging diff --git a/internal/server/adapter/http/middleware/logging/enrich_with_request_id_mw.go b/internal/server/adapter/http/middleware/logging/enrich_with_request_id_mw.go new file mode 100644 index 0000000..0935799 --- /dev/null +++ b/internal/server/adapter/http/middleware/logging/enrich_with_request_id_mw.go @@ -0,0 +1,23 @@ +package logging + +import ( + "net/http" + + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" + "github.com/StasMerzlyakov/go-metrics/internal/server/domain" + "github.com/google/uuid" +) + +// EncrichWithRequestIDMW Добавляет к запросу RequestID и устанавливает в контекст логгер +func EncrichWithRequestIDMW() middleware.Middleware { + return func(next http.Handler) http.Handler { + logReqFn := func(w http.ResponseWriter, req *http.Request) { + logger := domain.GetMainLogger() + requestUUID := uuid.New() + ctx := domain.EnrichWithRequestIDLogger(req.Context(), requestUUID, logger) + req = req.WithContext(ctx) + next.ServeHTTP(w, req) + } + return http.HandlerFunc(logReqFn) + } +} diff --git a/internal/server/adapter/http/middleware/logging/logging_request_info_mw.go b/internal/server/adapter/http/middleware/logging/logging_request_info_mw.go index b4955c9..6b9fff1 100644 --- a/internal/server/adapter/http/middleware/logging/logging_request_info_mw.go +++ b/internal/server/adapter/http/middleware/logging/logging_request_info_mw.go @@ -11,7 +11,7 @@ import ( func NewLoggingRequestMW() middleware.Middleware { return func(next http.Handler) http.Handler { logReqFn := func(w http.ResponseWriter, req *http.Request) { - log := domain.GetMainLogger() + log := domain.GetCtxLogger(req.Context()) start := time.Now() uri := req.RequestURI method := req.Method diff --git a/internal/server/adapter/http/middleware/logging/logging_response_info_mw.go b/internal/server/adapter/http/middleware/logging/logging_response_info_mw.go index bd21553..5ec830e 100644 --- a/internal/server/adapter/http/middleware/logging/logging_response_info_mw.go +++ b/internal/server/adapter/http/middleware/logging/logging_response_info_mw.go @@ -43,8 +43,8 @@ func (lw *loggingResponseWriter) WriteHeader(statusCode int) { func NewLoggingResponseMW() middleware.Middleware { return func(next http.Handler) http.Handler { - log := domain.GetMainLogger() lrw := func(w http.ResponseWriter, r *http.Request) { + log := domain.GetCtxLogger(r.Context()) lw := &loggingResponseWriter{ responseData: &responseData{ status: http.StatusOK, diff --git a/internal/server/adapter/http/middleware/mocks/common.go b/internal/server/adapter/http/middleware/mocks/common.go new file mode 100644 index 0000000..e832f74 --- /dev/null +++ b/internal/server/adapter/http/middleware/mocks/common.go @@ -0,0 +1,47 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware (interfaces: Handler) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + http "net/http" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockHandler is a mock of Handler interface. +type MockHandler struct { + ctrl *gomock.Controller + recorder *MockHandlerMockRecorder +} + +// MockHandlerMockRecorder is the mock recorder for MockHandler. +type MockHandlerMockRecorder struct { + mock *MockHandler +} + +// NewMockHandler creates a new mock instance. +func NewMockHandler(ctrl *gomock.Controller) *MockHandler { + mock := &MockHandler{ctrl: ctrl} + mock.recorder = &MockHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHandler) EXPECT() *MockHandlerMockRecorder { + return m.recorder +} + +// ServeHTTP mocks base method. +func (m *MockHandler) ServeHTTP(arg0 http.ResponseWriter, arg1 *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ServeHTTP", arg0, arg1) +} + +// ServeHTTP indicates an expected call of ServeHTTP. +func (mr *MockHandlerMockRecorder) ServeHTTP(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServeHTTP", reflect.TypeOf((*MockHandler)(nil).ServeHTTP), arg0, arg1) +} diff --git a/internal/server/adapter/http/middleware/retry/retriable_mw.go b/internal/server/adapter/http/middleware/retry/retriable_mw.go new file mode 100644 index 0000000..9cfabf3 --- /dev/null +++ b/internal/server/adapter/http/middleware/retry/retriable_mw.go @@ -0,0 +1,117 @@ +// Package retry client request retry middleware +package retry + +import ( + "bytes" + "context" + "io" + "net/http" + "time" + + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" + "github.com/StasMerzlyakov/go-metrics/internal/server/domain" + "go.uber.org/zap/buffer" +) + +func NewRetriableRequestMW() middleware.Middleware { + return NewRetriableRequestMWConf(time.Duration(time.Second), time.Duration(2*time.Second), 4, nil) +} + +func NewRetriableRequestMWConf(firstRetryDelay time.Duration, delayIncrement time.Duration, retryCount int, preProccFn domain.ErrPreProcessFn) middleware.Middleware { + rConf := &domain.RetriableInvokerConf{ + RetriableErr: domain.ErrServerInternal, + FirstRetryDelay: firstRetryDelay, + DelayIncrement: delayIncrement, + RetryCount: retryCount, + PreProccFn: nil, + } + + invoker := domain.CreateRetriableInvokerByConf(rConf) + + return func(next http.Handler) http.Handler { + infokeFn := func(w http.ResponseWriter, req *http.Request) { + + log := domain.GetCtxLogger(req.Context()) + + // Кешируем данные запроса + body, err := io.ReadAll(req.Body) + if err != nil { + errMsg := "can't read request content" + log.Errorw("RetriableRequestMW", "err", errMsg) + http.Error(w, errMsg, http.StatusBadRequest) + return + } + defer req.Body.Close() + + respWriter := &responseWriter{ + header: make(map[string][]string), + } + + invokableFn := func(ctx context.Context) error { + respWriter.Clear() // Нужно очистить данные перед каждым вызовом + req.Body = io.NopCloser(bytes.NewReader(body)) // Устанавливаем тело запроса + next.ServeHTTP(respWriter, req) + if respWriter.status == http.StatusInternalServerError { + // По конфигурации rConf на ошибку domain.ErrServerInternal invoker будет повторять операцию + return domain.ErrServerInternal + } + return nil + } + + err = invoker.Invoke(req.Context(), invokableFn) + if err != nil { + log.Errorw("RetriableRequestMW", "error", err.Error()) + http.Error(w, err.Error(), domain.MapDomainErrorToHTTPStatusErr(err)) + return + } + + // Заполняем данные для ответа в порядке Header, StatusCode, Body + for k, vs := range respWriter.Header() { + for _, v := range vs { + w.Header().Add(k, v) + } + } + + if respWriter.status != 0 { + w.WriteHeader(respWriter.status) + } + + _, err = w.Write(respWriter.buf.Bytes()) + if err != nil { + log.Errorw("RetriableRequestMW", "err", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + return http.HandlerFunc(infokeFn) + } +} + +type responseWriter struct { + buf buffer.Buffer + status int + header http.Header +} + +var _ http.ResponseWriter = (*responseWriter)(nil) + +func (rw *responseWriter) Clear() { + rw.buf.Reset() + rw.status = 0 + for k := range rw.header { + delete(rw.header, k) + } +} + +func (rw *responseWriter) Header() http.Header { + return rw.header +} + +func (rw *responseWriter) Write(data []byte) (int, error) { + size, err := rw.buf.Write(data) + return size, err +} + +func (rw *responseWriter) WriteHeader(statusCode int) { + rw.status = statusCode +} diff --git a/internal/server/adapter/http/middleware/retry/retriable_mw_test.go b/internal/server/adapter/http/middleware/retry/retriable_mw_test.go new file mode 100644 index 0000000..4182c18 --- /dev/null +++ b/internal/server/adapter/http/middleware/retry/retriable_mw_test.go @@ -0,0 +1,195 @@ +package retry_test + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/StasMerzlyakov/go-metrics/internal/server/domain" + "github.com/go-resty/resty/v2" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware" + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/logging" + mmocks "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/mocks" + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/retry" +) + +func TestRetriableMW1(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rMW := retry.NewRetriableRequestMWConf(time.Duration(time.Second), time.Duration(2*time.Second), 4, nil) + + logger, err := zap.NewDevelopment() + require.NoError(t, err) + defer logger.Sync() + + suga := logger.Sugar() + domain.SetMainLogger(suga) + erMW := logging.EncrichWithRequestIDMW() + + mux := http.NewServeMux() + mux.Handle("/json", middleware.Conveyor(createOkMockHandler(ctrl), rMW, erMW)) + + srv := httptest.NewServer(mux) + defer srv.Close() + + req := resty.New().R() + req.Method = http.MethodPost + + req.URL = srv.URL + "/json" + + resp, err := req.Send() + require.Nil(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode()) +} + +func TestRetriableMW2(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rMW := retry.NewRetriableRequestMWConf(time.Duration(time.Second), time.Duration(2*time.Second), 4, nil) + + logger, err := zap.NewDevelopment() + require.NoError(t, err) + defer logger.Sync() + + suga := logger.Sugar() + domain.SetMainLogger(suga) + + erMW := logging.EncrichWithRequestIDMW() + + mux := http.NewServeMux() + mux.Handle("/json", middleware.Conveyor(createAnyErrHandler(ctrl), rMW, erMW)) + + srv := httptest.NewServer(mux) + defer srv.Close() + + req := resty.New().R() + req.Method = http.MethodPost + + req.URL = srv.URL + "/json" + + resp, err := req.Send() + require.Nil(t, err) + require.Equal(t, http.StatusAccepted, resp.StatusCode()) +} + +func TestRetriableMW3(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rMW := retry.NewRetriableRequestMWConf(time.Duration(time.Second), time.Duration(2*time.Second), 4, nil) + + logger, err := zap.NewDevelopment() + require.NoError(t, err) + defer logger.Sync() + + suga := logger.Sugar() + domain.SetMainLogger(suga) + + erMW := logging.EncrichWithRequestIDMW() + + mux := http.NewServeMux() + mux.Handle("/json", middleware.Conveyor(createAnyErrHandler(ctrl), rMW, erMW)) + + srv := httptest.NewServer(mux) + defer srv.Close() + + req := resty.New().R() + req.Method = http.MethodPost + + req.URL = srv.URL + "/json" + + resp, err := req.Send() + require.Nil(t, err) + require.Equal(t, http.StatusAccepted, resp.StatusCode()) +} + +func TestRetriableMW4(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rMW := retry.NewRetriableRequestMWConf(time.Duration(time.Second), time.Duration(2*time.Second), 4, nil) + logger, err := zap.NewDevelopment() + require.NoError(t, err) + defer logger.Sync() + + suga := logger.Sugar() + domain.SetMainLogger(suga) + + erMW := logging.EncrichWithRequestIDMW() + + mux := http.NewServeMux() + mux.Handle("/json", middleware.Conveyor(createInernalErrExceptLastHandler(ctrl), rMW, erMW)) + + srv := httptest.NewServer(mux) + defer srv.Close() + + req := resty.New().R() + req.Method = http.MethodPost + + req.URL = srv.URL + "/json" + + resp, err := req.Send() + require.Nil(t, err) + require.Equal(t, http.StatusCreated, resp.StatusCode()) + + // Проверим что в ответе только данные от посленего вызова + errHeader := resp.Header().Get("ERR") + assert.Equal(t, "ERROR4", errHeader) + + respContent := string(resp.Body()) + assert.Equal(t, "test error 4", respContent) +} + +func createOkMockHandler(ctrl *gomock.Controller) http.Handler { + mockHandler := mmocks.NewMockHandler(ctrl) + + mockHandler.EXPECT().ServeHTTP(gomock.Any(), gomock.Any()).DoAndReturn( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + io.WriteString(w, "{ "+strings.Repeat(`"msg":"Hello, world",`, 19)+`"msg":"Hello, world"`+"}") + }).Times(1) + return mockHandler +} + +func createAnyErrHandler(ctrl *gomock.Controller) http.Handler { + mockHandler := mmocks.NewMockHandler(ctrl) + + mockHandler.EXPECT().ServeHTTP(gomock.Any(), gomock.Any()).DoAndReturn( + func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("test err")) + http.Error(w, "test err", http.StatusAccepted) + }).Times(1) + return mockHandler +} + +func createInernalErrExceptLastHandler(ctrl *gomock.Controller) http.Handler { + mockHandler := mmocks.NewMockHandler(ctrl) + + counter := atomic.Int32{} + mockHandler.EXPECT().ServeHTTP(gomock.Any(), gomock.Any()).DoAndReturn( + func(w http.ResponseWriter, r *http.Request) { + new := counter.Add(1) + if new == 4 { + w.WriteHeader(http.StatusCreated) + w.Header().Add("ERR", fmt.Sprintf("ERROR%d", new)) + w.Write([]byte(fmt.Sprintf("test error %d", new))) + } else { + w.WriteHeader(http.StatusInternalServerError) + w.Header().Add("ERR", fmt.Sprintf("ERROR%d", new)) + w.Write([]byte(fmt.Sprintf("test error %d", new))) + } + }).Times(4) // 4 попытки + return mockHandler +} diff --git a/internal/server/adapter/http/testutils/http_handler.go b/internal/server/adapter/http/testutils/http_handler.go index 496f8ef..a374cc2 100644 --- a/internal/server/adapter/http/testutils/http_handler.go +++ b/internal/server/adapter/http/testutils/http_handler.go @@ -1,3 +1,4 @@ +// Package middleware contains go-metrics middleware package middleware import ( diff --git a/internal/server/adapter/storage/memory/storage.go b/internal/server/adapter/storage/memory/storage.go index 9de38cd..86d8b69 100644 --- a/internal/server/adapter/storage/memory/storage.go +++ b/internal/server/adapter/storage/memory/storage.go @@ -1,3 +1,4 @@ +// Package memory contains go-metrics memory storage implementation package memory import ( diff --git a/internal/server/adapter/storage/postgres/main_test.go b/internal/server/adapter/storage/postgres/main_test.go new file mode 100644 index 0000000..d727915 --- /dev/null +++ b/internal/server/adapter/storage/postgres/main_test.go @@ -0,0 +1,83 @@ +package postgres_test + +import ( + "context" + "log" + "os" + "sync" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +var postgresContainer *postgres.PostgresContainer + +func TestMain(m *testing.M) { + ctx, cancelFN := context.WithCancel(context.Background()) + defer func() { + cancelFN() + }() + + setup(ctx) + code := m.Run() + shutdown(ctx) + os.Exit(code) +} + +func shutdown(ctx context.Context) { + if postgresContainer != nil { + if err := postgresContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + } +} + +const dbName = "users" +const dbUser = "user" +const dbPassword = "password" + +var pPool *pgxpool.Pool + +func setup(ctx context.Context) { + + var err error + postgresContainer, err = postgres.RunContainer(ctx, + testcontainers.WithImage("docker.io/postgres:15.2-alpine"), + postgres.WithDatabase(dbName), + postgres.WithUsername(dbUser), + postgres.WithPassword(dbPassword), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(5*time.Second)), + ) + + if err != nil { + log.Fatalf(err.Error()) + } +} + +var once sync.Once + +func clear(ctx context.Context) error { + + once.Do(func() { + connString, _ := postgresContainer.ConnectionString(ctx) + pConf, _ := pgxpool.ParseConfig(connString) + pPool, _ = pgxpool.NewWithConfig(ctx, pConf) + }) + + tx, err := pPool.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + tx.Exec(ctx, `DELETE FROM gauge`) + tx.Exec(ctx, `DELETE FROM counter`) + return tx.Commit(ctx) +} diff --git a/internal/server/adapter/storage/postgres/storage.go b/internal/server/adapter/storage/postgres/storage.go index 109fa14..8319d04 100644 --- a/internal/server/adapter/storage/postgres/storage.go +++ b/internal/server/adapter/storage/postgres/storage.go @@ -1,3 +1,4 @@ +// Package postgres contains go-metrics postgres storage implementation package postgres import ( @@ -21,21 +22,7 @@ type storage struct { databaseURL string } -var createCounterTableSQL = `CREATE TABLE IF NOT EXISTS counter( - name text not null, - value bigint, - PRIMARY KEY(name) -);` - -var createGaugeTableSQL = `CREATE TABLE IF NOT EXISTS gauge( - name text not null, - value double precision, - PRIMARY KEY(name) -);` - func (st *storage) SetAllMetrics(ctx context.Context, in []domain.Metrics) error { - logger := domain.GetMainLogger() - logger.Infow("SetAllMetrics", "status", "start") _, err := st.db.ExecContext(ctx, "TRUNCATE counter,gauge") if err != nil { @@ -76,8 +63,6 @@ func (st *storage) SetAllMetrics(ctx context.Context, in []domain.Metrics) error } func (st *storage) GetAllMetrics(ctx context.Context) ([]domain.Metrics, error) { - logger := domain.GetMainLogger() - logger.Infow("GetAllMetrics", "status", "start") var metricsList []domain.Metrics gaugeList, err := st.getAllGauge(ctx) if err != nil { @@ -111,8 +96,6 @@ func (st *storage) GetAllMetrics(ctx context.Context) ([]domain.Metrics, error) } func (st *storage) Set(ctx context.Context, m *domain.Metrics) error { - logger := domain.GetMainLogger() - logger.Infow("Set", "status", "start") switch m.MType { case domain.CounterType: delta := *m.Delta @@ -128,8 +111,6 @@ func (st *storage) Set(ctx context.Context, m *domain.Metrics) error { } func (st *storage) Add(ctx context.Context, m *domain.Metrics) error { - logger := domain.GetMainLogger() - logger.Infow("Add", "status", "start") switch m.MType { case domain.CounterType: delta := *m.Delta @@ -157,10 +138,9 @@ func (st *storage) Get(ctx context.Context, id string, mType domain.MetricType) } func (st *storage) Bootstrap(ctx context.Context) error { - logger := domain.GetMainLogger() - logger.Infow("Bootstrap", "status", "start") + logger := domain.GetCtxLogger(ctx) if db, err := sql.Open("pgx", st.databaseURL); err != nil { - logger.Infow("Bootstrap", "status", "error", "msg", err.Error()) + logger.Errorw("Bootstrap", "status", "error", "msg", err.Error()) return err } else { st.db = db @@ -190,32 +170,24 @@ func (st *storage) Bootstrap(ctx context.Context) error { } func (st *storage) Ping(ctx context.Context) error { - logger := domain.GetMainLogger() - logger.Infow("Ping", "status", "start") + logger := domain.GetCtxLogger(ctx) if err := st.db.PingContext(ctx); err != nil { - logger.Infow("Ping", "status", "error", "msg", err.Error()) + logger.Errorw("Ping", "error", err.Error()) return fmt.Errorf("Ping error: %w", err) - } else { - logger.Infow("Ping", "status", "ok") - return nil } + return nil } func (st *storage) Close(ctx context.Context) error { - logger := domain.GetMainLogger() - logger.Infow("Close", "status", "start") + logger := domain.GetCtxLogger(ctx) if err := st.db.Close(); err != nil { - logger.Infow("Stop", "status", "error", "msg", err.Error()) + logger.Errorw("Stop", "error", err.Error()) return err - } else { - logger.Infow("Stop", "status", "ok") - return nil } + return nil } func (st *storage) SetMetrics(ctx context.Context, metric []domain.Metrics) error { - logger := domain.GetMainLogger() - logger.Infow("SetMetrics", "status", "start") tx, err := st.db.BeginTx(ctx, nil) if err != nil { return err @@ -255,8 +227,6 @@ func (st *storage) SetMetrics(ctx context.Context, metric []domain.Metrics) erro } func (st *storage) AddMetrics(ctx context.Context, metric []domain.Metrics) error { - logger := domain.GetMainLogger() - logger.Infow("AddMetrics", "status", "start") tx, err := st.db.BeginTx(ctx, nil) if err != nil { return err @@ -393,11 +363,12 @@ func (st *storage) getAllGauge(ctx context.Context) ([]gauge, error) { } func (st *storage) getCounter(ctx context.Context, id string) (*domain.Metrics, error) { - logger := domain.GetMainLogger() + logger := domain.GetCtxLogger(ctx) + action := domain.GetAction(1) rows, err := st.db.QueryContext(ctx, "SELECT name, value from counter WHERE name = $1", id) if err != nil { - logger.Infow("getCounter", "status", "error", "msg", err.Error()) + logger.Errorw(action, "status", "error", "msg", err.Error()) return nil, err } defer rows.Close() @@ -407,7 +378,7 @@ func (st *storage) getCounter(ctx context.Context, id string) (*domain.Metrics, var delta int64 err = rows.Scan(&name, &delta) if err != nil { - logger.Infow("getCounter", "status", "error", "msg", err.Error()) + logger.Errorw(action, "status", "error", "msg", err.Error()) return nil, err } @@ -427,10 +398,12 @@ func (st *storage) getCounter(ctx context.Context, id string) (*domain.Metrics, } func (st *storage) getGauge(ctx context.Context, id string) (*domain.Metrics, error) { - logger := domain.GetMainLogger() + logger := domain.GetCtxLogger(ctx) + action := domain.GetAction(1) + rows, err := st.db.QueryContext(ctx, "SELECT name, value from gauge WHERE name = $1", id) if err != nil { - logger.Infow("getGauge", "status", "error", "msg", err.Error()) + logger.Errorw(action, "status", "error", "msg", err.Error()) return nil, err } defer rows.Close() @@ -440,7 +413,7 @@ func (st *storage) getGauge(ctx context.Context, id string) (*domain.Metrics, er var value float64 err = rows.Scan(&name, &value) if err != nil { - logger.Infow("getGauge", "status", "error", "msg", err.Error()) + logger.Errorw(action, "status", "error", "msg", err.Error()) return nil, err } diff --git a/internal/server/adapter/storage/postgres/storage_test.go b/internal/server/adapter/storage/postgres/storage_test.go index d055899..bdd2e8c 100644 --- a/internal/server/adapter/storage/postgres/storage_test.go +++ b/internal/server/adapter/storage/postgres/storage_test.go @@ -1,3 +1,374 @@ package postgres_test -// TODO - тест на docker-контейнерах +import ( + "context" + "testing" + + "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/postgres" + "github.com/StasMerzlyakov/go-metrics/internal/server/domain" + "github.com/stretchr/testify/require" +) + +func TestPostgresStorageStoreAndLoad(t *testing.T) { + + toLoad := []domain.Metrics{ + {MType: domain.CounterType, ID: "PollCount", Delta: domain.DeltaPtr(1)}, + {MType: domain.GaugeType, ID: "RandomValue", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "Alloc", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "BuckHashSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "Frees", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "GCCPUFraction", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "GCSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "HeapAlloc", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "HeapIdle", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "HeapInuse", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "HeapObjects", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "HeapReleased", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "HeapSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "LastGC", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "Lookups", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "MCacheInuse", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "MCacheSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "MSpanInuse", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "MSpanSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "Mallocs", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "NextGC", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "NumForcedGC", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "NumGC", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "OtherSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "PauseTotalNs", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "StackInuse", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "StackSys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "Sys", Value: domain.ValuePtr(1.123)}, + {MType: domain.GaugeType, ID: "TotalAlloc", Value: domain.ValuePtr(1.123)}, + } + + ctx, cancelFN := context.WithCancel(context.Background()) + + defer cancelFN() + + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + out, err := storage.GetAllMetrics(ctx) + require.NoError(t, err) + require.True(t, len(out) == 0) + + err = storage.SetAllMetrics(ctx, toLoad) + require.NoError(t, err) + + out, err = storage.GetAllMetrics(ctx) + require.NoError(t, err) + require.Equal(t, len(toLoad), len(out)) +} + +func TestPostgresStorageGaugeOperations(t *testing.T) { + + ctx, cancelFN := context.WithCancel(context.Background()) + defer cancelFN() + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + GagueID := "NumGC" + + mConst := &domain.Metrics{ + ID: GagueID, + MType: domain.GaugeType, + Value: domain.ValuePtr(2), + } + + ms, err := storage.Get(ctx, GagueID, domain.CounterType) + require.NoError(t, err) + require.Nil(t, ms) + + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.Set(ctx, mConst) + require.NoError(t, err) + + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.NotNil(t, ms) + require.Equal(t, ms.ID, GagueID) + require.Equal(t, ms.MType, domain.GaugeType) + require.NotNil(t, ms.Value) + require.Equal(t, float64(2), *ms.Value) + require.Nil(t, ms.Delta) + + ms, err = storage.Get(ctx, GagueID, domain.CounterType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.Set(ctx, mConst) + require.NoError(t, err) + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.NotNil(t, ms) + require.Equal(t, ms.ID, GagueID) + require.Equal(t, ms.MType, domain.GaugeType) + require.NotNil(t, ms.Value) + require.Equal(t, float64(2), *ms.Value) + require.Nil(t, ms.Delta) + + err = storage.Add(ctx, mConst) + require.NoError(t, err) + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.NotNil(t, ms) + require.Equal(t, ms.ID, GagueID) + require.Equal(t, ms.MType, domain.GaugeType) + require.NotNil(t, ms.Value) + require.Equal(t, float64(4), *ms.Value) + require.Nil(t, ms.Delta) +} + +func TestMemoryStorageCounterOperations(t *testing.T) { + + ctx, cancelFN := context.WithCancel(context.Background()) + defer cancelFN() + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + CounterID := "PollCount" + + mConst := &domain.Metrics{ + ID: CounterID, + MType: domain.CounterType, + Delta: domain.DeltaPtr(2), + } + + ms, err := storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Nil(t, ms) + + ms, err = storage.Get(ctx, CounterID, domain.GaugeType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.Set(ctx, mConst) + require.NoError(t, err) + + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.NotNil(t, ms) + require.Equal(t, ms.ID, CounterID) + require.Equal(t, ms.MType, domain.CounterType) + require.NotNil(t, ms.Delta) + require.Equal(t, int64(2), *ms.Delta) + require.Nil(t, ms.Value) + + ms, err = storage.Get(ctx, CounterID, domain.GaugeType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.Set(ctx, mConst) + require.NoError(t, err) + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.NotNil(t, ms) + require.Equal(t, ms.ID, CounterID) + require.Equal(t, ms.MType, domain.CounterType) + require.NotNil(t, ms.Delta) + require.Equal(t, int64(2), *ms.Delta) + require.Nil(t, ms.Value) + + err = storage.Add(ctx, mConst) + require.NoError(t, err) + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.NotNil(t, ms) + require.Equal(t, ms.ID, CounterID) + require.Equal(t, ms.MType, domain.CounterType) + require.NotNil(t, ms.Delta) + require.Equal(t, int64(4), *ms.Delta) + require.Nil(t, ms.Value) +} + +func TestAddMetrics(t *testing.T) { + + t.Run("gague", func(t *testing.T) { + ctx, cancelFN := context.WithCancel(context.Background()) + defer cancelFN() + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + GagueID := "NumGC" + + mConst := &domain.Metrics{ + ID: GagueID, + MType: domain.GaugeType, + Value: domain.ValuePtr(2), + } + + ms, err := storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Equal(t, *mConst.Value, *ms.Value) + + err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Equal(t, 2**mConst.Value, *ms.Value) + }) + + t.Run("counter", func(t *testing.T) { + ctx, cancelFN := context.WithCancel(context.Background()) + defer cancelFN() + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + CounterID := "NumGC" + + mConst := &domain.Metrics{ + ID: CounterID, + MType: domain.CounterType, + Delta: domain.DeltaPtr(2), + } + + ms, err := storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Equal(t, *mConst.Delta, *ms.Delta) + + err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Equal(t, 2**mConst.Delta, *ms.Delta) + }) +} + +func TestSetMetrics(t *testing.T) { + + t.Run("gague", func(t *testing.T) { + ctx, cancelFN := context.WithCancel(context.Background()) + defer cancelFN() + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + GagueID := "NumGC" + + mConst := &domain.Metrics{ + ID: GagueID, + MType: domain.GaugeType, + Value: domain.ValuePtr(2), + } + + ms, err := storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Equal(t, *mConst.Value, *ms.Value) + + err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, GagueID, domain.GaugeType) + require.NoError(t, err) + require.Equal(t, *mConst.Value, *ms.Value) + }) + + t.Run("counter", func(t *testing.T) { + ctx, cancelFN := context.WithCancel(context.Background()) + defer cancelFN() + connString, err := postgresContainer.ConnectionString(ctx) + require.NoError(t, err) + + storage := postgres.NewStorage(connString) + err = storage.Bootstrap(ctx) + require.NoError(t, err) + + err = clear(ctx) + require.NoError(t, err) + + CounterID := "NumGC" + + mConst := &domain.Metrics{ + ID: CounterID, + MType: domain.CounterType, + Delta: domain.DeltaPtr(2), + } + + ms, err := storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Nil(t, ms) + + err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Equal(t, *mConst.Delta, *ms.Delta) + + err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) + require.NoError(t, err) + + ms, err = storage.Get(ctx, CounterID, domain.CounterType) + require.NoError(t, err) + require.Equal(t, *mConst.Delta, *ms.Delta) + }) +} diff --git a/internal/server/adapter/storage/wrapper/retriable.go b/internal/server/adapter/storage/wrapper/retriable.go deleted file mode 100644 index ff0714c..0000000 --- a/internal/server/adapter/storage/wrapper/retriable.go +++ /dev/null @@ -1,123 +0,0 @@ -package wrapper - -import ( - "context" - - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable" - "github.com/StasMerzlyakov/go-metrics/internal/server/domain" - "go.uber.org/zap" -) - -type Invoker interface { - Invoke(fn retriable.InvokableFn, ctx context.Context) error -} - -type Storage interface { - SetAllMetrics(ctx context.Context, marr []domain.Metrics) error - GetAllMetrics(ctx context.Context) ([]domain.Metrics, error) - Set(ctx context.Context, m *domain.Metrics) error - Add(ctx context.Context, m *domain.Metrics) error - SetMetrics(ctx context.Context, metric []domain.Metrics) error - AddMetrics(ctx context.Context, metric []domain.Metrics) error - Get(ctx context.Context, id string, mType domain.MetricType) (*domain.Metrics, error) - Ping(ctx context.Context) error - Bootstrap(ctx context.Context) error - Close(ctx context.Context) error -} - -func NewRetriable(rConf *retriable.RetriableInvokerConf, logger *zap.SugaredLogger, internalStorage Storage) *storage { - invoker := retriable.CreateRetriableInvokerConf(rConf, &zapLoggerWrapper{ - logger: logger, - }) - return &storage{ - invoker: invoker, - internalStorage: internalStorage, - } -} - -var _ Storage = (*storage)(nil) - -type storage struct { - invoker Invoker - internalStorage Storage -} - -func (s *storage) SetAllMetrics(ctx context.Context, metricses []domain.Metrics) error { - fn := func(ctx context.Context) error { - return s.internalStorage.SetAllMetrics(ctx, metricses) - } - return s.invoker.Invoke(fn, ctx) -} - -func (s *storage) GetAllMetrics(ctx context.Context) ([]domain.Metrics, error) { - var result []domain.Metrics - fn := func(ctx context.Context) error { - res, err := s.internalStorage.GetAllMetrics(ctx) - if err != nil { - return err - } - result = append(result, res...) - return nil - } - err := s.invoker.Invoke(fn, ctx) - return result, err -} - -func (s *storage) Set(ctx context.Context, m *domain.Metrics) error { - fn := func(ctx context.Context) error { - return s.internalStorage.Set(ctx, m) - } - return s.invoker.Invoke(fn, ctx) -} - -func (s *storage) Add(ctx context.Context, m *domain.Metrics) error { - fn := func(ctx context.Context) error { - return s.internalStorage.Add(ctx, m) - } - return s.invoker.Invoke(fn, ctx) -} - -func (s *storage) SetMetrics(ctx context.Context, metricses []domain.Metrics) error { - fn := func(ctx context.Context) error { - return s.internalStorage.SetMetrics(ctx, metricses) - } - return s.invoker.Invoke(fn, ctx) -} - -func (s *storage) AddMetrics(ctx context.Context, metricses []domain.Metrics) error { - fn := func(ctx context.Context) error { - return s.internalStorage.AddMetrics(ctx, metricses) - } - return s.invoker.Invoke(fn, ctx) -} - -func (s *storage) Get(ctx context.Context, id string, mType domain.MetricType) (*domain.Metrics, error) { - var m *domain.Metrics - var err error - fn := func(ctx context.Context) error { - m, err = s.internalStorage.Get(ctx, id, mType) - return err - } - err = s.invoker.Invoke(fn, ctx) - if err != nil { - return nil, err - } - return m, nil -} -func (s *storage) Ping(ctx context.Context) error { - return s.invoker.Invoke(s.internalStorage.Ping, ctx) -} -func (s *storage) Bootstrap(ctx context.Context) error { - return s.invoker.Invoke(s.internalStorage.Bootstrap, ctx) -} -func (s *storage) Close(ctx context.Context) error { - return s.invoker.Invoke(s.internalStorage.Close, ctx) -} - -type zapLoggerWrapper struct { - logger *zap.SugaredLogger -} - -func (z *zapLoggerWrapper) Infow(msg string, keysAndValues ...any) { - z.logger.Infow(msg, keysAndValues...) -} diff --git a/internal/server/adapter/storage/wrapper/retriable_test.go b/internal/server/adapter/storage/wrapper/retriable_test.go deleted file mode 100644 index a4ffb20..0000000 --- a/internal/server/adapter/storage/wrapper/retriable_test.go +++ /dev/null @@ -1,346 +0,0 @@ -package wrapper_test - -import ( - "context" - "io" - "testing" - - "github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable" - "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/memory" - "github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/wrapper" - "github.com/StasMerzlyakov/go-metrics/internal/server/domain" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestRetruableStorageStoreAndLoad(t *testing.T) { - - toLoad := []domain.Metrics{ - {MType: domain.CounterType, ID: "PollCount", Delta: domain.DeltaPtr(1)}, - {MType: domain.GaugeType, ID: "RandomValue", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "Alloc", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "BuckHashSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "Frees", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "GCCPUFraction", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "GCSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "HeapAlloc", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "HeapIdle", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "HeapInuse", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "HeapObjects", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "HeapReleased", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "HeapSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "LastGC", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "Lookups", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "MCacheInuse", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "MCacheSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "MSpanInuse", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "MSpanSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "Mallocs", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "NextGC", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "NumForcedGC", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "NumGC", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "OtherSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "PauseTotalNs", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "StackInuse", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "StackSys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "Sys", Value: domain.ValuePtr(1.123)}, - {MType: domain.GaugeType, ID: "TotalAlloc", Value: domain.ValuePtr(1.123)}, - } - - ctx := context.Background() - - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - out, err := storage.GetAllMetrics(ctx) - require.NoError(t, err) - require.True(t, len(out) == 0) - - err = storage.SetAllMetrics(ctx, toLoad) - require.NoError(t, err) - - out, err = storage.GetAllMetrics(ctx) - require.NoError(t, err) - require.Equal(t, len(toLoad), len(out)) -} - -func TestRetruableStorageGaugeOperations(t *testing.T) { - - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - GagueID := "NumGC" - - mConst := &domain.Metrics{ - ID: GagueID, - MType: domain.GaugeType, - Value: domain.ValuePtr(2), - } - - ctx := context.Background() - - ms, err := storage.Get(ctx, GagueID, domain.CounterType) - require.NoError(t, err) - require.Nil(t, ms) - - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.Set(ctx, mConst) - require.NoError(t, err) - - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.NotNil(t, ms) - require.Equal(t, ms.ID, GagueID) - require.Equal(t, ms.MType, domain.GaugeType) - require.NotNil(t, ms.Value) - require.Equal(t, float64(2), *ms.Value) - require.Nil(t, ms.Delta) - - ms, err = storage.Get(ctx, GagueID, domain.CounterType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.Set(ctx, mConst) - require.NoError(t, err) - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.NotNil(t, ms) - require.Equal(t, ms.ID, GagueID) - require.Equal(t, ms.MType, domain.GaugeType) - require.NotNil(t, ms.Value) - require.Equal(t, float64(2), *ms.Value) - require.Nil(t, ms.Delta) - - err = storage.Add(ctx, mConst) - require.NoError(t, err) - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.NotNil(t, ms) - require.Equal(t, ms.ID, GagueID) - require.Equal(t, ms.MType, domain.GaugeType) - require.NotNil(t, ms.Value) - require.Equal(t, float64(4), *ms.Value) - require.Nil(t, ms.Delta) -} - -func TestRetruableStorageCounterOperations(t *testing.T) { - - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - CounterID := "PollCount" - - mConst := &domain.Metrics{ - ID: CounterID, - MType: domain.CounterType, - Delta: domain.DeltaPtr(2), - } - - ctx := context.Background() - - ms, err := storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Nil(t, ms) - - ms, err = storage.Get(ctx, CounterID, domain.GaugeType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.Set(ctx, mConst) - require.NoError(t, err) - - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.NotNil(t, ms) - require.Equal(t, ms.ID, CounterID) - require.Equal(t, ms.MType, domain.CounterType) - require.NotNil(t, ms.Delta) - require.Equal(t, int64(2), *ms.Delta) - require.Nil(t, ms.Value) - - ms, err = storage.Get(ctx, CounterID, domain.GaugeType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.Set(ctx, mConst) - require.NoError(t, err) - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.NotNil(t, ms) - require.Equal(t, ms.ID, CounterID) - require.Equal(t, ms.MType, domain.CounterType) - require.NotNil(t, ms.Delta) - require.Equal(t, int64(2), *ms.Delta) - require.Nil(t, ms.Value) - - err = storage.Add(ctx, mConst) - require.NoError(t, err) - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.NotNil(t, ms) - require.Equal(t, ms.ID, CounterID) - require.Equal(t, ms.MType, domain.CounterType) - require.NotNil(t, ms.Delta) - require.Equal(t, int64(4), *ms.Delta) - require.Nil(t, ms.Value) -} - -func TestAddMetrics(t *testing.T) { - - t.Run("gague", func(t *testing.T) { - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - GagueID := "NumGC" - - mConst := &domain.Metrics{ - ID: GagueID, - MType: domain.GaugeType, - Value: domain.ValuePtr(2), - } - - ctx := context.Background() - - ms, err := storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Equal(t, *mConst.Value, *ms.Value) - - err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Equal(t, 2**mConst.Value, *ms.Value) - }) - - t.Run("counter", func(t *testing.T) { - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - CounterID := "NumGC" - - mConst := &domain.Metrics{ - ID: CounterID, - MType: domain.CounterType, - Delta: domain.DeltaPtr(2), - } - - ctx := context.Background() - - ms, err := storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Equal(t, *mConst.Delta, *ms.Delta) - - err = storage.AddMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Equal(t, 2**mConst.Delta, *ms.Delta) - }) -} - -func TestSetMetrics(t *testing.T) { - - t.Run("gague", func(t *testing.T) { - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - GagueID := "NumGC" - - mConst := &domain.Metrics{ - ID: GagueID, - MType: domain.GaugeType, - Value: domain.ValuePtr(2), - } - - ctx := context.Background() - - ms, err := storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Equal(t, *mConst.Value, *ms.Value) - - err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, GagueID, domain.GaugeType) - require.NoError(t, err) - require.Equal(t, *mConst.Value, *ms.Value) - }) - - t.Run("counter", func(t *testing.T) { - mStorage := memory.NewStorage() - conf := retriable.DefaultConf(io.EOF) - storage := wrapper.NewRetriable(conf, logger(), mStorage) - - CounterID := "NumGC" - - mConst := &domain.Metrics{ - ID: CounterID, - MType: domain.CounterType, - Delta: domain.DeltaPtr(2), - } - - ctx := context.Background() - - ms, err := storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Nil(t, ms) - - err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Equal(t, *mConst.Delta, *ms.Delta) - - err = storage.SetMetrics(ctx, []domain.Metrics{*mConst}) - require.NoError(t, err) - - ms, err = storage.Get(ctx, CounterID, domain.CounterType) - require.NoError(t, err) - require.Equal(t, *mConst.Delta, *ms.Delta) - }) -} - -func logger() *zap.SugaredLogger { - logger, err := zap.NewDevelopment() - if err != nil { - // вызываем панику, если ошибка - panic("cannot initialize zap") - } - defer logger.Sync() - - log := logger.Sugar() - return log -} diff --git a/internal/server/app/backup.go b/internal/server/app/backup.go index f22c1bd..56fd16f 100644 --- a/internal/server/app/backup.go +++ b/internal/server/app/backup.go @@ -3,6 +3,7 @@ package app import ( "context" "errors" + "fmt" "os" "github.com/StasMerzlyakov/go-metrics/internal/server/domain" @@ -42,14 +43,15 @@ func (bU *backUper) RestoreBackUp(ctx context.Context) error { func (bU *backUper) DoBackUp(ctx context.Context) error { logger := domain.GetMainLogger() + action := domain.GetAction(1) metrics, err := bU.storage.GetAllMetrics(ctx) if err != nil { return err } err = bU.formatter.Write(ctx, metrics) if err != nil { + logger.Errorf(action, "error", fmt.Sprintf("backup error - %s", err.Error())) return err } - logger.Infow("DoBackUp", "status", "ok", "msg", "backup is done") return nil } diff --git a/internal/server/app/doc.go b/internal/server/app/doc.go index 2d460a6..55644a1 100644 --- a/internal/server/app/doc.go +++ b/internal/server/app/doc.go @@ -1,4 +1,2 @@ -/* -Пакет app содержит реализацию вариантов использования приложения -*/ +// Package app contains application use-cases package app diff --git a/internal/server/domain/context.go b/internal/server/domain/context.go new file mode 100644 index 0000000..dc313ec --- /dev/null +++ b/internal/server/domain/context.go @@ -0,0 +1,62 @@ +package domain + +import ( + "context" + + "github.com/google/uuid" +) + +type ContextKey string + +const keyLogger = ContextKey("Logger") +const LoggerKeyRequestID = "requestID" + +//go:generate mockgen -destination "./mocks/$GOFILE" -package mocks . Logger +type Logger interface { + Debugw(msg string, keysAndValues ...any) + Infow(msg string, keysAndValues ...any) + Errorw(msg string, keysAndValues ...any) +} + +func EnrichWithRequestIDLogger(ctx context.Context, requestID uuid.UUID, logger Logger) context.Context { + requestIDLogger := &requestIDLogger{ + internalLogger: logger, + requestID: requestID.String(), + } + resultCtx := context.WithValue(ctx, keyLogger, requestIDLogger) + return resultCtx +} + +// GetCtxLogger возвращает логгер из контекста. Если не найден - то просто MainLogger +func GetCtxLogger(ctx context.Context) Logger { + if v := ctx.Value(keyLogger); v != nil { + lg, ok := v.(Logger) + if !ok { + return GetMainLogger() + } + return lg + } + return GetMainLogger() +} + +var _ Logger = (*requestIDLogger)(nil) + +type requestIDLogger struct { + requestID string + internalLogger Logger +} + +func (l *requestIDLogger) Debugw(msg string, keysAndValues ...any) { + keysAndValues = append(keysAndValues, LoggerKeyRequestID, l.requestID) + l.internalLogger.Debugw(msg, keysAndValues...) +} + +func (l *requestIDLogger) Infow(msg string, keysAndValues ...any) { + keysAndValues = append(keysAndValues, LoggerKeyRequestID, l.requestID) + l.internalLogger.Infow(msg, keysAndValues...) +} + +func (l *requestIDLogger) Errorw(msg string, keysAndValues ...any) { + keysAndValues = append(keysAndValues, LoggerKeyRequestID, l.requestID) + l.internalLogger.Infow(msg, keysAndValues...) +} diff --git a/internal/server/domain/context_test.go b/internal/server/domain/context_test.go new file mode 100644 index 0000000..6dcc7a1 --- /dev/null +++ b/internal/server/domain/context_test.go @@ -0,0 +1,56 @@ +package domain_test + +import ( + "context" + "testing" + + "github.com/StasMerzlyakov/go-metrics/internal/server/domain" + "github.com/StasMerzlyakov/go-metrics/internal/server/domain/mocks" + "github.com/golang/mock/gomock" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestEnrichContextRequestID(t *testing.T) { + + requestUUID := uuid.New() + reqStr := requestUUID.String() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + m := mocks.NewMockLogger(ctrl) + + testLoggerFn := func(msg string, keysAndValues ...any) { + // Проверяем что что при вызове метода логирования добавляется информация о пользователе и requstId + requestIDIsChecked := false + + for id, v := range keysAndValues { + switch v := v.(type) { + case string: + if v == domain.LoggerKeyRequestID { + require.True(t, id+1 < len(keysAndValues), "requestID is not set") + k := keysAndValues[id+1] + id, ok := k.(string) + require.True(t, ok, "requestID is not string") + require.Equal(t, reqStr, id, "unexpecred requestID value") + requestIDIsChecked = true + } + } + } + require.Truef(t, requestIDIsChecked, "requestID is not specified") + } + + m.EXPECT().Infow(gomock.Any(), gomock.Any()).DoAndReturn(testLoggerFn).AnyTimes() + + m.EXPECT().Errorw(gomock.Any(), gomock.Any()).DoAndReturn(testLoggerFn).AnyTimes() + + ctx := context.Background() + + enrichedCtx := domain.EnrichWithRequestIDLogger(ctx, requestUUID, m) + + log := domain.GetCtxLogger(enrichedCtx) + + log.Errorw("test errorw", "msg", "hello") + log.Infow("test errorw", "msg", "hello") +} diff --git a/internal/server/domain/doc.go b/internal/server/domain/doc.go new file mode 100644 index 0000000..499fd9c --- /dev/null +++ b/internal/server/domain/doc.go @@ -0,0 +1,2 @@ +// Package domain contains common structures, functions and constants +package domain diff --git a/internal/server/domain/errors.go b/internal/server/domain/errors.go index ecd3b76..cf5e7b4 100644 --- a/internal/server/domain/errors.go +++ b/internal/server/domain/errors.go @@ -2,6 +2,7 @@ package domain import ( "errors" + "net/http" ) var ( @@ -10,4 +11,33 @@ var ( ErrDataDigestMismath = errors.New("ErrDataDigestMismath") // Несовпадение хэша ErrNotFound = errors.New("NotFoundError") ErrDBConnection = errors.New("DatabaseConnectionError") + ErrMediaType = errors.New("UnsupportedMediaTypeError") ) + +func MapDomainErrorToHTTPStatusErr(err error) int { + if errors.Is(err, ErrMediaType) { + return http.StatusUnsupportedMediaType + } + + if errors.Is(err, ErrDataFormat) { + return http.StatusBadRequest + } + + if errors.Is(err, ErrDataDigestMismath) { + return http.StatusBadRequest + } + + if errors.Is(err, ErrServerInternal) { + return http.StatusBadRequest + } + + if errors.Is(err, ErrDBConnection) { + return http.StatusInternalServerError + } + + if errors.Is(err, ErrNotFound) { + return http.StatusNotFound + } + + return http.StatusInternalServerError +} diff --git a/internal/server/domain/mocks/context.go b/internal/server/domain/mocks/context.go new file mode 100644 index 0000000..c704204 --- /dev/null +++ b/internal/server/domain/mocks/context.go @@ -0,0 +1,85 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/StasMerzlyakov/go-metrics/internal/server/domain (interfaces: Logger) + +// Package mocks is a generated GoMock package. +package mocks + +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 +} + +// Debugw mocks base method. +func (m *MockLogger) Debugw(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debugw", varargs...) +} + +// Debugw indicates an expected call of Debugw. +func (mr *MockLoggerMockRecorder) Debugw(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugw", reflect.TypeOf((*MockLogger)(nil).Debugw), varargs...) +} + +// Errorw mocks base method. +func (m *MockLogger) Errorw(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Errorw", varargs...) +} + +// Errorw indicates an expected call of Errorw. +func (mr *MockLoggerMockRecorder) Errorw(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorw", reflect.TypeOf((*MockLogger)(nil).Errorw), varargs...) +} + +// Infow mocks base method. +func (m *MockLogger) Infow(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Infow", varargs...) +} + +// Infow indicates an expected call of Infow. +func (mr *MockLoggerMockRecorder) Infow(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infow", reflect.TypeOf((*MockLogger)(nil).Infow), varargs...) +} diff --git a/internal/server/domain/retriable.go b/internal/server/domain/retriable.go new file mode 100644 index 0000000..f320dc1 --- /dev/null +++ b/internal/server/domain/retriable.go @@ -0,0 +1,80 @@ +package domain + +import ( + "context" + "errors" + "time" +) + +type RetriableInvokerConf struct { + RetriableErr error + FirstRetryDelay time.Duration + DelayIncrement time.Duration + RetryCount int + PreProccFn ErrPreProcessFn +} + +type RetriableInvoker interface { + Invoke(ctx context.Context, fn InvokableFn) error +} + +type InvokableFn func(ctx context.Context) error + +type ErrPreProcessFn func(err error) error + +func CreateRetriableInvokerByConf(conf *RetriableInvokerConf) RetriableInvoker { + return &retriableInvoker{ + *conf, + } +} + +func CreateRetriableInvokerByErr(retriableErr error) RetriableInvoker { + return CreateInvokerByErrAndFn(retriableErr, nil) +} + +func CreateInvokerByErrAndFn(retriableErr error, preProccFn ErrPreProcessFn) RetriableInvoker { + return &retriableInvoker{ + RetriableInvokerConf{ + RetriableErr: retriableErr, + FirstRetryDelay: time.Duration(time.Second), + DelayIncrement: time.Duration(2 * time.Second), + RetryCount: 4, + PreProccFn: preProccFn, + }, + } +} + +type retriableInvoker struct { + RetriableInvokerConf +} + +func (r *retriableInvoker) Invoke(ctx context.Context, fn InvokableFn) error { + var err error + iter := 1 + + logger := GetCtxLogger(ctx) + action := GetAction(1) + for { + err = fn(ctx) + if err == nil { + return nil + } + + if r.PreProccFn != nil { + err = r.PreProccFn(err) + } + + if !errors.Is(err, r.RetriableErr) || iter == r.RetryCount { + logger.Errorw(action, "error", err.Error()) + return err + } + nextInvokation := r.FirstRetryDelay + time.Duration(iter-1)*r.DelayIncrement + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(nextInvokation): + iter++ + continue + } + } +} diff --git a/staticcheck.conf b/staticcheck.conf new file mode 100644 index 0000000..2f7f7aa --- /dev/null +++ b/staticcheck.conf @@ -0,0 +1,2 @@ +checks = ["all"] +initialisms = []