diff --git a/.env.docker b/.env.docker index 079c46f..023d255 100644 --- a/.env.docker +++ b/.env.docker @@ -9,7 +9,10 @@ MAX_RELEASE_CATCHER_MESSAGE_SIZE=5000000 LISTEN=0.0.0.0:3000 RELEASE_EXCHANGE=release LOG_LEVEL=trace +DEPLOYMENT_ENVIRONMENT=local METRICS_LISTEN=localhost:2112 +# OTEL_LOGS_ENDPOINT=http://host.docker.internal:4319/v1/logs +OTEL_LOGS_ENDPOINT=http://otel:4318/v1/logs ## Hawk settings HAWK_ENABLED=true diff --git a/.env.sample b/.env.sample index d6c9939..a2cac6c 100644 --- a/.env.sample +++ b/.env.sample @@ -9,7 +9,9 @@ MAX_RELEASE_CATCHER_MESSAGE_SIZE=5000000 LISTEN=localhost:3000 RELEASE_EXCHANGE=release LOG_LEVEL=trace +DEPLOYMENT_ENVIRONMENT=local METRICS_LISTEN=localhost:2112 +OTEL_LOGS_ENDPOINT=http://otel:4318/v1/logs ## Hawk settings HAWK_ENABLED=true diff --git a/.gitignore b/.gitignore index acc256e..2cb7e25 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ venv .DS_Store bin/hawk.collector bin/golangci-lint -.env \ No newline at end of file +.env + +hawk.collector diff --git a/Dockerfile b/Dockerfile index e5baa97..7ae9f9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16-stretch as builder +FROM golang:1.24-bookworm as builder ARG BUILD_DIRECTORY=/build # disable CGO diff --git a/cmd/collector/main.go b/cmd/collector/main.go index 81659c7..393c2e3 100644 --- a/cmd/collector/main.go +++ b/cmd/collector/main.go @@ -2,9 +2,11 @@ package collector import ( "context" - "os" + "fmt" + "log/slog" "github.com/codex-team/hawk.collector/pkg/accounts" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/caarlos0/env/v6" "github.com/codex-team/hawk.collector/cmd" @@ -15,7 +17,6 @@ import ( "github.com/codex-team/hawk.collector/pkg/redis" "github.com/codex-team/hawk.collector/pkg/server" "github.com/joho/godotenv" - log "github.com/sirupsen/logrus" ) // RunCommand - Run server in the production mode @@ -27,48 +28,49 @@ type RunCommand struct { // Execute Run server - Load configuration file and start server func (x *RunCommand) Execute(args []string) error { - if err := godotenv.Load(); err != nil { - log.Println("File .env not found, reading configuration from ENV") + envLoadErr := godotenv.Load() + var err error + + // setup logging as early as possible so all logs go to stdout + OTEL + otelShutdown := log.SetupFromEnv(context.Background()) + defer func() { + if shutdownErr := otelShutdown(context.Background()); shutdownErr != nil { + slog.Warn("failed to shutdown OTLP logger", "error", shutdownErr) + } + }() + + if envLoadErr != nil { + slog.Info("File .env not found, reading configuration from ENV") } // load config from .env var cfg cmd.Config if err := env.Parse(&cfg); err != nil { - log.Fatalf("Failed to parse ENV") - } - - // setup logging and set log level from config - level, err := log.ParseLevel(cfg.LogLevel) - if err != nil { - level = log.ErrorLevel + slog.Error("Failed to parse ENV", "error", err) + return err } - log.SetFormatter(&log.TextFormatter{ - FullTimestamp: true, - }) - log.SetOutput(os.Stdout) - log.SetLevel(level) - log.Infof("✓ Log level set on %s", level) + slog.InfoContext(context.Background(), fmt.Sprintf("collector started on %s", cfg.Listen), "event", "startup") // Initialize Hawk Catcher if cfg.HawkEnabled { err = hawk.Init() if err != nil { - log.Errorf("✗ Cannot initialize Hawk Catcher: %s", err) + slog.Error("✗ Cannot initialize Hawk Catcher", "error", err) } else { go hawk.HawkCatcher.Run() defer hawk.HawkCatcher.Stop() - log.Infof("✓ Hawk Catcher initialized on %s", hawk.HawkCatcher.GetURL()) + slog.Info("✓ Hawk Catcher initialized", "url", hawk.HawkCatcher.GetURL()) } } // connect to AMQP broker with retries - log.Infof("Connecting to RabbitMQ %s", cfg.BrokerURL) + slog.Info("Connecting to RabbitMQ", "url", cfg.BrokerURL) brokerObj := broker.New(cfg.BrokerURL, cfg.Exchange) brokerObj.Init() - log.Infof("✓ Broker initialized on %s", cfg.BrokerURL) + slog.Info("✓ Broker initialized", "url", cfg.BrokerURL) // connect to Redis - log.Infof("Connecting to Redis %s", cfg.RedisURL) + slog.Info("Connecting to Redis", "url", cfg.RedisURL) ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -84,7 +86,7 @@ func (x *RunCommand) Execute(args []string) error { err = redisClient.LoadBlockedIDs() if err != nil { - log.Errorf("failed to load blocked IDs from Redis") + slog.Error("failed to load blocked IDs from Redis", "error", err) } // connect to accounts MongoDB @@ -93,12 +95,12 @@ func (x *RunCommand) Execute(args []string) error { err = accountsClient.UpdateTokenCache() if err != nil { - log.Errorf("failed to update token cache: %s", err) + slog.Error("failed to update token cache", "error", err) } err = accountsClient.UpdateProjectsLimitsCache() if err != nil { - log.Errorf("failed to update projects limits cache: %s", err) + slog.Error("failed to update projects limits cache", "error", err) } go periodic.RunPeriodically(accountsClient.UpdateTokenCache, cfg.TokenUpdatePeriod, doneAccountsContext) @@ -112,7 +114,7 @@ func (x *RunCommand) Execute(args []string) error { go periodic.RunPeriodically(redisClient.LoadBlockedIDs, cfg.BlockedIDsLoad, done) go periodic.RunPeriodically(serverObj.UpdateBlacklist, cfg.BlacklistUpdatePeriod, done) defer close(done) - log.Info("✓ Redis client initialized") + slog.Info("✓ Redis client initialized") // listen and serve prometheus metrics go metrics.RunServer(cfg.MetricsListen) diff --git a/cmd/config.go b/cmd/config.go index 39a093c..bd95aa0 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -34,6 +34,13 @@ type Config struct { // Log level LogLevel string `env:"LOG_LEVEL"` + // Deployment environment label (local/staging/production) + DeploymentEnvironment string `env:"DEPLOYMENT_ENVIRONMENT"` + + // OpenTelemetry configuration + OTelLogsEndpoint string `env:"OTEL_LOGS_ENDPOINT"` + ServiceName string `env:"SERVICE_NAME" defaultEnv:"collector"` + // Metrics listen host:port MetricsListen string `env:"METRICS_LISTEN"` diff --git a/cmd/error.go b/cmd/error.go index ccd531b..30fd7e9 100644 --- a/cmd/error.go +++ b/cmd/error.go @@ -1,9 +1,11 @@ package cmd import ( - "github.com/codex-team/hawk.collector/pkg/hawk" - "log" + "log/slog" + "os" "strings" + + "github.com/codex-team/hawk.collector/pkg/hawk" ) // FailOnError - throw fatal error and log it with the message provided by the msg argument if err is not nil @@ -13,11 +15,12 @@ func FailOnError(err error, msgs ...string) { } if hawkError := hawk.HawkCatcher.Catch(err); hawkError != nil { - log.Printf("Error in HawkCatcher.Catch: %s", hawkError) + slog.Error("Error in HawkCatcher.Catch", "error", hawkError) } hawk.HawkCatcher.Stop() - log.Fatalf("%s: %s", strings.Join(msgs, ". "), err) + slog.Error(strings.Join(msgs, ". "), "error", err) + os.Exit(1) } // PanicOnError - throw a recoverable panic diff --git a/go.mod b/go.mod index 5e8da88..6c84452 100644 --- a/go.mod +++ b/go.mod @@ -7,19 +7,73 @@ require ( github.com/cenkalti/backoff/v4 v4.1.0 github.com/codex-team/hawk.go v1.0.5 github.com/fasthttp/websocket v1.4.3 - github.com/go-redis/redis/v8 v8.8.3 - github.com/golang/protobuf v1.5.2 // indirect + github.com/go-redis/redis/v8 v8.11.5 github.com/jessevdk/go-flags v1.5.0 github.com/joho/godotenv v1.3.0 github.com/prometheus/client_golang v1.10.0 - github.com/prometheus/common v0.25.0 // indirect - github.com/savsgio/gotils v0.0.0-20210520110740-c57c45b83e0a // indirect - github.com/sirupsen/logrus v1.8.1 github.com/streadway/amqp v1.0.0 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.8.0 github.com/valyala/fasthttp v1.25.0 go.mongodb.org/mongo-driver v1.7.1 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/sdk v1.39.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 +) + +require ( + github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.0.4 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.12.2 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.25.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/savsgio/gotils v0.0.0-20210520110740-c57c45b83e0a // indirect + github.com/tidwall/match v1.0.3 // indirect + github.com/tidwall/pretty v1.1.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.0.2 // indirect + github.com/xdg-go/stringprep v1.0.2 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/grpc v1.77.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.16 +go 1.24.0 + +toolchain go1.24.12 diff --git a/go.sum b/go.sum index c8cc3d4..abb5336 100644 --- a/go.sum +++ b/go.sum @@ -35,16 +35,15 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/caarlos0/env/v6 v6.6.0 h1:kVhajCpqX5pSfH41gFd8cPXPZahqJrnn9HxJ1vKftW4= github.com/caarlos0/env/v6 v6.6.0/go.mod h1:P0BVSgU9zfkxfSpFUs6KsO3uWR4k3Ac0P66ibAGTybM= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -85,8 +84,13 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-redis/redis/v8 v8.8.3 h1:BefJyU89cTF25I00D5N9pJdWB1d1RBj8d7MBf71M7uQ= -github.com/go-redis/redis/v8 v8.8.3/go.mod h1:ik7vb7+gm8Izylxu6kf6wG26/t2VljgCfSQ1DM4O1uU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -138,9 +142,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= @@ -153,12 +156,14 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -167,6 +172,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -219,11 +226,13 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -261,21 +270,18 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= @@ -333,6 +339,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -341,13 +349,6 @@ github.com/savsgio/gotils v0.0.0-20210520110740-c57c45b83e0a h1:qqVWOiLdFpxFLRYQ github.com/savsgio/gotils v0.0.0-20210520110740-c57c45b83e0a/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -366,8 +367,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ= github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= @@ -394,7 +395,6 @@ github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -404,14 +404,28 @@ go.mongodb.org/mongo-driver v1.7.1/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8N go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -428,8 +442,9 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -438,7 +453,6 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -456,13 +470,11 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/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-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -472,9 +484,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -483,7 +495,6 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -493,31 +504,28 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -536,14 +544,12 @@ golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -553,6 +559,10 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -562,20 +572,22 @@ google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -590,10 +602,12 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/accounts/cache.go b/pkg/accounts/cache.go index 829d5ac..a3b84a2 100644 --- a/pkg/accounts/cache.go +++ b/pkg/accounts/cache.go @@ -11,7 +11,7 @@ import ( "go.mongodb.org/mongo-driver/bson" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "go.mongodb.org/mongo-driver/mongo" ) @@ -74,7 +74,7 @@ func (client *AccountsMongoDBClient) UpdateTokenCache() error { if err == nil { validTokensTmp[integrationSecret] = project.ProjectID.Hex() } else { - log.Errorf("Integration token %s is invalid: %s", project.Token, err) + log.Debugf("Integration token %s is invalid: %s", project.Token, err) } } @@ -149,8 +149,6 @@ func (client *AccountsMongoDBClient) UpdateProjectsLimitsCache() error { projectID := project.ProjectID.Hex() var finalLimits rateLimitSettings - log.Tracef("Project with id %s and limits %+v", projectID, project.RateLimitSettings) - if workspace, exists := workspaceMap[project.WorkspaceID.Hex()]; exists { finalLimits = workspace.TariffPlan.RateLimitSettings diff --git a/pkg/accounts/mongodb.go b/pkg/accounts/mongodb.go index 4842b80..957ab03 100644 --- a/pkg/accounts/mongodb.go +++ b/pkg/accounts/mongodb.go @@ -10,7 +10,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "go.mongodb.org/mongo-driver/mongo" ) diff --git a/pkg/broker/amqp.go b/pkg/broker/amqp.go index 13f5965..c0bdf0a 100644 --- a/pkg/broker/amqp.go +++ b/pkg/broker/amqp.go @@ -3,7 +3,7 @@ package broker import ( "fmt" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/codex-team/hawk.collector/cmd" "github.com/codex-team/hawk.collector/pkg/hawk" diff --git a/pkg/broker/broker.go b/pkg/broker/broker.go index 81ec45f..3c22490 100644 --- a/pkg/broker/broker.go +++ b/pkg/broker/broker.go @@ -2,7 +2,7 @@ package broker import ( "github.com/codex-team/hawk.collector/cmd" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" ) // Message represents message payload sent to the Queue and AMQP route diff --git a/pkg/hawk/main.go b/pkg/hawk/main.go index 4a427f6..7ff96bc 100644 --- a/pkg/hawk/main.go +++ b/pkg/hawk/main.go @@ -5,7 +5,7 @@ import ( "github.com/caarlos0/env/v6" hawkGo "github.com/codex-team/hawk.go" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" ) // Global catcher instance diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..9347fc7 --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,111 @@ +package logger + +import ( + "context" + "fmt" + "log/slog" + "os" + "strings" + + "github.com/codex-team/hawk.collector/pkg/otel" +) + +var bg = context.Background() + +func With(args ...any) *slog.Logger { + return slog.Default().With(args...) +} + +func SetupFromEnv(ctx context.Context) func(context.Context) error { + level := parseSlogLevel(os.Getenv("LOG_LEVEL")) + shutdown, err := otel.Setup(ctx, os.Getenv("OTEL_LOGS_ENDPOINT"), otel.Config{ + ServiceName: os.Getenv("SERVICE_NAME"), + Environment: os.Getenv("DEPLOYMENT_ENVIRONMENT"), + StdoutLevel: level, + StdoutWriter: os.Stdout, + }) + slog.Info("✓ Log level set", "level", level.String()) + if err != nil { + slog.Warn("failed to init OTLP logger", "error", err) + } + return shutdown +} + +func Tracef(format string, args ...any) { + if !slog.Default().Enabled(bg, slog.LevelDebug) { + return + } + slog.Log(bg, slog.LevelDebug, fmt.Sprintf(format, args...), "trace", true) +} + +func Debugf(format string, args ...any) { + logf(slog.LevelDebug, format, args...) +} + +func Infof(format string, args ...any) { + logf(slog.LevelInfo, format, args...) +} + +func Warnf(format string, args ...any) { + logf(slog.LevelWarn, format, args...) +} + +func Errorf(format string, args ...any) { + logf(slog.LevelError, format, args...) +} + +func Printf(format string, args ...any) { + logf(slog.LevelInfo, format, args...) +} + +func Println(args ...any) { + logv(slog.LevelInfo, args...) +} + +func Debug(args ...any) { + logv(slog.LevelDebug, args...) +} + +func Info(args ...any) { + logv(slog.LevelInfo, args...) +} + +func Warn(args ...any) { + logv(slog.LevelWarn, args...) +} + +func Error(args ...any) { + logv(slog.LevelError, args...) +} + +func Fatalf(format string, args ...any) { + logf(slog.LevelError, format, args...) + os.Exit(1) +} + +func logf(level slog.Level, format string, args ...any) { + if !slog.Default().Enabled(bg, level) { + return + } + slog.Log(bg, level, fmt.Sprintf(format, args...)) +} + +func logv(level slog.Level, args ...any) { + if !slog.Default().Enabled(bg, level) { + return + } + slog.Log(bg, level, fmt.Sprint(args...)) +} + +func parseSlogLevel(value string) slog.Level { + switch strings.ToLower(strings.TrimSpace(value)) { + case "trace", "debug": + return slog.LevelDebug + case "warn", "warning": + return slog.LevelWarn + case "error", "fatal", "panic": + return slog.LevelError + default: + return slog.LevelInfo + } +} diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go new file mode 100644 index 0000000..21ea261 --- /dev/null +++ b/pkg/logger/logger_test.go @@ -0,0 +1,28 @@ +package logger + +import ( + "bytes" + "encoding/json" + "log/slog" + "testing" +) + +func TestWithAddsAttributes(t *testing.T) { + var buf bytes.Buffer + handler := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}) + slog.SetDefault(slog.New(handler)) + + With("projectId", "proj-123").Info("hello") + + var payload map[string]any + if err := json.Unmarshal(buf.Bytes(), &payload); err != nil { + t.Fatalf("failed to unmarshal log: %v", err) + } + + if got := payload["projectId"]; got != "proj-123" { + t.Fatalf("expected projectId=proj-123, got %v", got) + } + if got := payload["msg"]; got != "hello" { + t.Fatalf("expected msg=hello, got %v", got) + } +} diff --git a/pkg/metrics/prometheus.go b/pkg/metrics/prometheus.go index 6f73107..3c3b1aa 100644 --- a/pkg/metrics/prometheus.go +++ b/pkg/metrics/prometheus.go @@ -3,7 +3,7 @@ package metrics import ( "github.com/codex-team/hawk.collector/pkg/hawk" "github.com/prometheus/client_golang/prometheus/promhttp" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "net/http" ) diff --git a/pkg/otel/otel.go b/pkg/otel/otel.go new file mode 100644 index 0000000..b2b0bc6 --- /dev/null +++ b/pkg/otel/otel.go @@ -0,0 +1,288 @@ +package otel + +import ( + "context" + "errors" + "fmt" + "io" + "log/slog" + "math" + "net/url" + "os" + "strings" + "time" + + "go.opentelemetry.io/otel/attribute" + logapi "go.opentelemetry.io/otel/log" + logglobal "go.opentelemetry.io/otel/log/global" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + sdklog "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/resource" +) + +type Config struct { + ServiceName string + Environment string + StdoutJSON bool + StdoutLevel slog.Leveler + StdoutWriter io.Writer +} + +func Setup(ctx context.Context, endpoint string, cfg Config) (func(context.Context) error, error) { + writer := cfg.StdoutWriter + if writer == nil { + writer = os.Stdout + } + + handlerOpts := &slog.HandlerOptions{Level: cfg.StdoutLevel} + var stdoutHandler slog.Handler + if cfg.StdoutJSON { + stdoutHandler = slog.NewJSONHandler(writer, handlerOpts) + } else { + stdoutHandler = slog.NewTextHandler(writer, handlerOpts) + } + + handlers := []slog.Handler{stdoutHandler} + shutdown := func(context.Context) error { return nil } + + if endpoint == "" { + slog.SetDefault(slog.New(teeHandler{handlers: handlers})) + return shutdown, nil + } + + parsed, err := url.Parse(endpoint) + if err != nil || parsed.Host == "" { + parsed, err = url.Parse("http://" + endpoint) + if err != nil { + slog.SetDefault(slog.New(teeHandler{handlers: handlers})) + return shutdown, err + } + } + + opts := []otlploghttp.Option{ + otlploghttp.WithEndpoint(parsed.Host), + } + if parsed.Scheme == "" || parsed.Scheme == "http" { + opts = append(opts, otlploghttp.WithInsecure()) + } + if path := strings.TrimSpace(parsed.EscapedPath()); path != "" { + opts = append(opts, otlploghttp.WithURLPath(path)) + } + + exporter, err := otlploghttp.New(ctx, opts...) + if err != nil { + slog.SetDefault(slog.New(teeHandler{handlers: handlers})) + return shutdown, err + } + + serviceName := cfg.ServiceName + if serviceName == "" { + serviceName = "collector" + } + + leveler := cfg.StdoutLevel + if leveler == nil { + leveler = slog.LevelInfo + } + + processor := sdklog.NewBatchProcessor(exporter) + attrs := []attribute.KeyValue{ + attribute.String("service.name", serviceName), + } + if env := strings.TrimSpace(cfg.Environment); env != "" { + attrs = append(attrs, attribute.String("deployment.environment", env)) + } + + provider := sdklog.NewLoggerProvider( + sdklog.WithProcessor(processor), + sdklog.WithResource(resource.NewWithAttributes("", attrs...)), + ) + + logglobal.SetLoggerProvider(provider) + otelHandler := otelSlogHandler{ + logger: logglobal.Logger(serviceName), + leveler: leveler, + } + handlers = append(handlers, otelHandler) + slog.SetDefault(slog.New(teeHandler{handlers: handlers})) + + shutdown = provider.Shutdown + return shutdown, nil +} + +type teeHandler struct { + handlers []slog.Handler +} + +func (t teeHandler) Enabled(ctx context.Context, level slog.Level) bool { + for _, handler := range t.handlers { + if handler.Enabled(ctx, level) { + return true + } + } + return false +} + +func (t teeHandler) Handle(ctx context.Context, record slog.Record) error { + var err error + for _, handler := range t.handlers { + if handler.Enabled(ctx, record.Level) { + if handleErr := handler.Handle(ctx, record); handleErr != nil { + err = errors.Join(err, handleErr) + } + } + } + return err +} + +func (t teeHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + next := make([]slog.Handler, 0, len(t.handlers)) + for _, handler := range t.handlers { + next = append(next, handler.WithAttrs(attrs)) + } + return teeHandler{handlers: next} +} + +func (t teeHandler) WithGroup(name string) slog.Handler { + next := make([]slog.Handler, 0, len(t.handlers)) + for _, handler := range t.handlers { + next = append(next, handler.WithGroup(name)) + } + return teeHandler{handlers: next} +} + +type otelSlogHandler struct { + logger logapi.Logger + leveler slog.Leveler + attrs []slog.Attr + groups []string +} + +func (h otelSlogHandler) Enabled(ctx context.Context, level slog.Level) bool { + if h.leveler == nil { + return level >= slog.LevelInfo + } + return level >= h.leveler.Level() +} + +func (h otelSlogHandler) Handle(ctx context.Context, record slog.Record) error { + if h.logger == nil { + return nil + } + + var otelRecord logapi.Record + if record.Time.IsZero() { + otelRecord.SetTimestamp(time.Now()) + } else { + otelRecord.SetTimestamp(record.Time) + } + otelRecord.SetObservedTimestamp(time.Now()) + otelRecord.SetSeverity(otelSeverity(record.Level)) + otelRecord.SetSeverityText(record.Level.String()) + otelRecord.SetBody(logapi.StringValue(record.Message)) + + prefix := strings.Join(h.groups, ".") + for _, attr := range h.attrs { + appendAttr(&otelRecord, prefix, attr) + } + record.Attrs(func(attr slog.Attr) bool { + appendAttr(&otelRecord, prefix, attr) + return true + }) + + h.logger.Emit(ctx, otelRecord) + return nil +} + +func (h otelSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + next := otelSlogHandler{ + logger: h.logger, + leveler: h.leveler, + attrs: append(append([]slog.Attr(nil), h.attrs...), attrs...), + groups: append([]string(nil), h.groups...), + } + return next +} + +func (h otelSlogHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + next := otelSlogHandler{ + logger: h.logger, + leveler: h.leveler, + attrs: append([]slog.Attr(nil), h.attrs...), + groups: append(append([]string(nil), h.groups...), name), + } + return next +} + +func appendAttr(record *logapi.Record, prefix string, attr slog.Attr) { + if attr.Equal(slog.Attr{}) { + return + } + + key := joinKey(prefix, attr.Key) + + switch attr.Value.Kind() { + case slog.KindGroup: + groupPrefix := key + for _, groupAttr := range attr.Value.Group() { + appendAttr(record, groupPrefix, groupAttr) + } + default: + record.AddAttributes(logapi.KeyValue{ + Key: key, + Value: otelValueFromSlog(attr.Value), + }) + } +} + +func joinKey(prefix, key string) string { + if prefix == "" { + return key + } + if key == "" { + return prefix + } + return prefix + "." + key +} + +func otelSeverity(level slog.Level) logapi.Severity { + switch { + case level >= slog.LevelError: + return logapi.SeverityError + case level >= slog.LevelWarn: + return logapi.SeverityWarn + case level >= slog.LevelInfo: + return logapi.SeverityInfo + default: + return logapi.SeverityDebug + } +} + +func otelValueFromSlog(value slog.Value) logapi.Value { + switch value.Kind() { + case slog.KindString: + return logapi.StringValue(value.String()) + case slog.KindInt64: + return logapi.Int64Value(value.Int64()) + case slog.KindUint64: + if value.Uint64() <= math.MaxInt64 { + return logapi.Int64Value(int64(value.Uint64())) + } + return logapi.StringValue(value.String()) + case slog.KindFloat64: + return logapi.Float64Value(value.Float64()) + case slog.KindBool: + return logapi.BoolValue(value.Bool()) + case slog.KindDuration: + return logapi.StringValue(value.Duration().String()) + case slog.KindTime: + return logapi.StringValue(value.Time().Format(time.RFC3339Nano)) + case slog.KindAny: + return logapi.StringValue(fmt.Sprint(value.Any())) + default: + return logapi.StringValue(value.String()) + } +} diff --git a/pkg/otel/otel_test.go b/pkg/otel/otel_test.go new file mode 100644 index 0000000..b98fcb0 --- /dev/null +++ b/pkg/otel/otel_test.go @@ -0,0 +1,117 @@ +package otel + +import ( + "context" + "log/slog" + "sync" + "testing" + "time" + + logapi "go.opentelemetry.io/otel/log" +) + +type captureLogger struct { + logapi.Logger + mu sync.Mutex + records []logapi.Record +} + +func (c *captureLogger) logger() {} + +func (c *captureLogger) Emit(_ context.Context, record logapi.Record) { + c.mu.Lock() + defer c.mu.Unlock() + c.records = append(c.records, record) +} + +func (c *captureLogger) Enabled(context.Context, logapi.EnabledParameters) bool { + return true +} + +func (c *captureLogger) lastRecord() (logapi.Record, bool) { + c.mu.Lock() + defer c.mu.Unlock() + if len(c.records) == 0 { + return logapi.Record{}, false + } + return c.records[len(c.records)-1], true +} + +func TestOtelSlogHandlerSeverity(t *testing.T) { + logger := &captureLogger{} + handler := otelSlogHandler{logger: logger} + + record := slog.NewRecord(time.Now(), slog.LevelWarn, "warn message", 0) + if err := handler.Handle(context.Background(), record); err != nil { + t.Fatalf("Handle error: %v", err) + } + + got, ok := logger.lastRecord() + if !ok { + t.Fatal("expected record, got none") + } + if got.Severity() != logapi.SeverityWarn { + t.Fatalf("expected severity %v, got %v", logapi.SeverityWarn, got.Severity()) + } +} + +func TestOtelSlogHandlerAttributes(t *testing.T) { + logger := &captureLogger{} + handler := otelSlogHandler{logger: logger} + + handler = handler.WithGroup("ctx").WithAttrs([]slog.Attr{slog.String("foo", "bar")}).(otelSlogHandler) + record := slog.NewRecord(time.Now(), slog.LevelInfo, "message", 0) + record.AddAttrs(slog.Int("count", 2)) + + if err := handler.Handle(context.Background(), record); err != nil { + t.Fatalf("Handle error: %v", err) + } + + got, ok := logger.lastRecord() + if !ok { + t.Fatal("expected record, got none") + } + + attrs := map[string]logapi.Value{} + got.WalkAttributes(func(kv logapi.KeyValue) bool { + attrs[kv.Key] = kv.Value + return true + }) + + if _, ok := attrs["ctx.foo"]; !ok { + t.Fatalf("expected attribute ctx.foo") + } + if _, ok := attrs["ctx.count"]; !ok { + t.Fatalf("expected attribute ctx.count") + } +} + +func TestOtelSlogHandlerAnyValue(t *testing.T) { + logger := &captureLogger{} + handler := otelSlogHandler{logger: logger} + + record := slog.NewRecord(time.Now(), slog.LevelInfo, "message", 0) + record.AddAttrs(slog.Any("meta", map[string]int{"a": 1})) + + if err := handler.Handle(context.Background(), record); err != nil { + t.Fatalf("Handle error: %v", err) + } + + got, ok := logger.lastRecord() + if !ok { + t.Fatal("expected record, got none") + } + + found := false + got.WalkAttributes(func(kv logapi.KeyValue) bool { + if kv.Key == "meta" { + found = kv.Value.Kind() == logapi.KindString + return false + } + return true + }) + + if !found { + t.Fatalf("expected meta attribute as string") + } +} diff --git a/pkg/periodic/periodic.go b/pkg/periodic/periodic.go index a8d2e9c..28881bb 100644 --- a/pkg/periodic/periodic.go +++ b/pkg/periodic/periodic.go @@ -3,7 +3,7 @@ package periodic import ( "time" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" ) type PeriodicFunc func() error diff --git a/pkg/redis/client.go b/pkg/redis/client.go index 15feb6a..d31b689 100644 --- a/pkg/redis/client.go +++ b/pkg/redis/client.go @@ -9,7 +9,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/go-redis/redis/v8" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" ) type RedisClient struct { diff --git a/pkg/server/errorshandler/handler.go b/pkg/server/errorshandler/handler.go index 3454a89..bb1c3b2 100644 --- a/pkg/server/errorshandler/handler.go +++ b/pkg/server/errorshandler/handler.go @@ -8,9 +8,9 @@ import ( "github.com/codex-team/hawk.collector/pkg/accounts" "github.com/codex-team/hawk.collector/pkg/broker" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/codex-team/hawk.collector/pkg/redis" "github.com/prometheus/client_golang/prometheus" - log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) @@ -62,13 +62,14 @@ func (handler *Handler) process(body []byte) ResponseMessage { log.Debugf("Token %s is not in the accounts cache", integrationSecret) return ResponseMessage{400, true, fmt.Sprintf("Integration token invalid: %s", integrationSecret)} } - log.Debugf("Found project with ID %s for integration token %s", projectId, integrationSecret) + projectLogger := log.With("projectId", projectId) + projectLogger.Debug(fmt.Sprintf("Found project with ID %s for integration token %s", projectId, integrationSecret)) projectLimits, ok := handler.AccountsMongoDBClient.GetProjectLimits(projectId) if !ok { - log.Warnf("Project %s is not in the projects limits cache", projectId) + projectLogger.Warn(fmt.Sprintf("Project %s is not in the projects limits cache", projectId)) } else { - log.Debugf("Project %s limits: %+v", projectId, projectLimits) + projectLogger.Debug(fmt.Sprintf("Project %s limits: %+v", projectId, projectLimits)) } if handler.RedisClient.IsBlocked(projectId) { @@ -79,7 +80,7 @@ func (handler *Handler) process(body []byte) ResponseMessage { rateWithinLimit, err := handler.RedisClient.UpdateRateLimit(projectId, projectLimits.EventsLimit, projectLimits.EventsPeriod) if err != nil { - log.Errorf("Failed to update rate limit: %s", err) + projectLogger.Error(fmt.Sprintf("Failed to update rate limit: %s", err)) return ResponseMessage{402, true, "Failed to update rate limit"} } if !rateWithinLimit { @@ -103,7 +104,7 @@ func (handler *Handler) process(body []byte) ResponseMessage { // send serialized message to a broker brokerMessage := broker.Message{Payload: rawMessage, Route: handler.determineQueue(message.CatcherType)} - log.Debugf("Send to queue: %s", brokerMessage) + projectLogger.Debug(fmt.Sprintf("Send to queue: %s", brokerMessage)) handler.Broker.Chan <- brokerMessage // increment processed errors counter @@ -140,6 +141,7 @@ func getTimeSeriesKey(projectId, metricType, granularity string) string { // recordProjectMetrics records project metrics to Redis TimeSeries // metricType can be: "events-accepted", "events-rate-limited", etc. func (handler *Handler) recordProjectMetrics(projectId, metricType string) { + projectLogger := log.With("projectId", projectId) minutelyKey := getTimeSeriesKey(projectId, metricType, "minutely") hourlyKey := getTimeSeriesKey(projectId, metricType, "hourly") dailyKey := getTimeSeriesKey(projectId, metricType, "daily") @@ -153,16 +155,16 @@ func (handler *Handler) recordProjectMetrics(projectId, metricType string) { // minutely: store for 24 hours // Use TS.ADD with ON_DUPLICATE SUM to accumulate events within the same timestamp if err := handler.RedisClient.SafeTSAdd(minutelyKey, 1, labels, 24*time.Hour); err != nil { - log.Errorf("failed to add minutely TS for %s: %v", metricType, err) + projectLogger.Error(fmt.Sprintf("failed to add minutely TS for %s: %v", metricType, err)) } // hourly: store for 7 days if err := handler.RedisClient.SafeTSAdd(hourlyKey, 1, labels, 7*24*time.Hour); err != nil { - log.Errorf("failed to add hourly TS for %s: %v", metricType, err) + projectLogger.Error(fmt.Sprintf("failed to add hourly TS for %s: %v", metricType, err)) } // daily: store for 90 days if err := handler.RedisClient.SafeTSAdd(dailyKey, 1, labels, 90*24*time.Hour); err != nil { - log.Errorf("failed to add daily TS for %s: %v", metricType, err) + projectLogger.Error(fmt.Sprintf("failed to add daily TS for %s: %v", metricType, err)) } } diff --git a/pkg/server/errorshandler/handler_http.go b/pkg/server/errorshandler/handler_http.go index f7f8c2d..4b1692e 100644 --- a/pkg/server/errorshandler/handler_http.go +++ b/pkg/server/errorshandler/handler_http.go @@ -5,7 +5,7 @@ import ( "errors" "github.com/codex-team/hawk.collector/pkg/hawk" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/valyala/fasthttp" ) diff --git a/pkg/server/errorshandler/handler_sentry.go b/pkg/server/errorshandler/handler_sentry.go index 16e9c58..d7e3f5f 100644 --- a/pkg/server/errorshandler/handler_sentry.go +++ b/pkg/server/errorshandler/handler_sentry.go @@ -6,7 +6,7 @@ import ( "time" "github.com/codex-team/hawk.collector/pkg/broker" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/valyala/fasthttp" ) @@ -91,13 +91,14 @@ func (handler *Handler) HandleSentry(ctx *fasthttp.RequestCtx) { sendAnswerHTTP(ctx, ResponseMessage{400, true, fmt.Sprintf("Integration token invalid: %s", hawkToken)}) return } - log.Debugf("Found project with ID %s for integration token %s", projectId, hawkToken) + projectLogger := log.With("projectId", projectId) + projectLogger.Debug(fmt.Sprintf("Found project with ID %s for integration token %s", projectId, hawkToken)) projectLimits, ok := handler.AccountsMongoDBClient.GetProjectLimits(projectId) if !ok { - log.Warnf("Project %s is not in the projects limits cache", projectId) + projectLogger.Warn(fmt.Sprintf("Project %s is not in the projects limits cache", projectId)) } else { - log.Debugf("Project %s limits: %+v", projectId, projectLimits) + projectLogger.Debug(fmt.Sprintf("Project %s limits: %+v", projectId, projectLimits)) } if handler.RedisClient.IsBlocked(projectId) { @@ -109,7 +110,7 @@ func (handler *Handler) HandleSentry(ctx *fasthttp.RequestCtx) { rateWithinLimit, err := handler.RedisClient.UpdateRateLimit(projectId, projectLimits.EventsLimit, projectLimits.EventsPeriod) if err != nil { - log.Errorf("Failed to update rate limit: %s", err) + projectLogger.Error(fmt.Sprintf("Failed to update rate limit: %s", err)) sendAnswerHTTP(ctx, ResponseMessage{402, true, "Failed to update rate limit"}) return } @@ -131,13 +132,13 @@ func (handler *Handler) HandleSentry(ctx *fasthttp.RequestCtx) { messageToSend := BrokerMessage{Timestamp: time.Now().Unix(), ProjectId: projectId, Payload: json.RawMessage(jsonMessage), CatcherType: CatcherType} payloadToSend, err := json.Marshal(messageToSend) if err != nil { - log.Errorf("Message marshalling error: %v", err) + projectLogger.Error(fmt.Sprintf("Message marshalling error: %v", err)) sendAnswerHTTP(ctx, ResponseMessage{400, true, "Cannot serialize envelope"}) } // send serialized message to a broker brokerMessage := broker.Message{Payload: payloadToSend, Route: SentryQueueName} - log.Debugf("Send to queue: %s", brokerMessage) + projectLogger.Debug(fmt.Sprintf("Send to queue: %s", brokerMessage)) handler.Broker.Chan <- brokerMessage // increment processed errors counter diff --git a/pkg/server/errorshandler/handler_test.go b/pkg/server/errorshandler/handler_test.go new file mode 100644 index 0000000..416b588 --- /dev/null +++ b/pkg/server/errorshandler/handler_test.go @@ -0,0 +1,26 @@ +package errorshandler + +import "testing" + +func TestDetermineQueue(t *testing.T) { + handler := &Handler{ + NonDefaultQueues: map[string]bool{ + "errors/custom": true, + }, + } + + if got := handler.determineQueue("errors/custom"); got != "errors/custom" { + t.Fatalf("expected custom queue, got %s", got) + } + if got := handler.determineQueue("errors/unknown"); got != DefaultQueueName { + t.Fatalf("expected default queue, got %s", got) + } +} + +func TestGetTimeSeriesKey(t *testing.T) { + got := getTimeSeriesKey("proj-1", "events-accepted", "hourly") + want := "ts:project-events-accepted:proj-1:hourly" + if got != want { + t.Fatalf("expected %s, got %s", want, got) + } +} diff --git a/pkg/server/errorshandler/handler_websocket.go b/pkg/server/errorshandler/handler_websocket.go index 128151f..ee912cd 100644 --- a/pkg/server/errorshandler/handler_websocket.go +++ b/pkg/server/errorshandler/handler_websocket.go @@ -7,7 +7,7 @@ import ( "github.com/fasthttp/websocket" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/valyala/fasthttp" ) diff --git a/pkg/server/errorshandler/sentry_utils.go b/pkg/server/errorshandler/sentry_utils.go index 20f36f2..0ccfd4a 100644 --- a/pkg/server/errorshandler/sentry_utils.go +++ b/pkg/server/errorshandler/sentry_utils.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/andybalholm/brotli" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" ) func decompressGzipString(gzipString []byte) ([]byte, error) { diff --git a/pkg/server/errorshandler/test_helpers.go b/pkg/server/errorshandler/test_helpers.go index 07a1ea8..cec708a 100644 --- a/pkg/server/errorshandler/test_helpers.go +++ b/pkg/server/errorshandler/test_helpers.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" ) // GenerateTestTimeSeriesData - generates test data for minutely, hourly, and daily time series @@ -15,17 +15,18 @@ func (handler *Handler) GenerateTestTimeSeriesData(projectId string) error { minutelyKey := getTimeSeriesKey(projectId, metricType, "minutely") hourlyKey := getTimeSeriesKey(projectId, metricType, "hourly") dailyKey := getTimeSeriesKey(projectId, metricType, "daily") + projectLogger := log.With("projectId", projectId) // Delete existing keys to avoid accumulation - log.Infof("Deleting existing test data keys for project %s...", projectId) + projectLogger.Info(fmt.Sprintf("Deleting existing test data keys for project %s...", projectId)) if err := handler.RedisClient.DeleteKey(minutelyKey); err != nil { - log.Warnf("Failed to delete minutely key: %v", err) + projectLogger.Warn(fmt.Sprintf("Failed to delete minutely key: %v", err)) } if err := handler.RedisClient.DeleteKey(hourlyKey); err != nil { - log.Warnf("Failed to delete hourly key: %v", err) + projectLogger.Warn(fmt.Sprintf("Failed to delete hourly key: %v", err)) } if err := handler.RedisClient.DeleteKey(dailyKey); err != nil { - log.Warnf("Failed to delete daily key: %v", err) + projectLogger.Warn(fmt.Sprintf("Failed to delete daily key: %v", err)) } labels := map[string]string{ @@ -37,7 +38,7 @@ func (handler *Handler) GenerateTestTimeSeriesData(projectId string) error { now := time.Now() // Minutely data: last 24 hours (1440 minutes) - log.Infof("Generating minutely test data for project %s...", projectId) + projectLogger.Info(fmt.Sprintf("Generating minutely test data for project %s...", projectId)) minuteStart := now.Add(-24 * time.Hour) for t := minuteStart; t.Before(now); t = t.Add(1 * time.Minute) { // Hash-based pseudo-random: 0-10 events per minute with realistic peaks/valleys @@ -54,7 +55,7 @@ func (handler *Handler) GenerateTestTimeSeriesData(projectId string) error { } // Hourly data: last 7 days (168 hours) - log.Infof("Generating hourly test data for project %s...", projectId) + projectLogger.Info(fmt.Sprintf("Generating hourly test data for project %s...", projectId)) hourStart := now.Add(-7 * 24 * time.Hour) for t := hourStart; t.Before(now); t = t.Add(1 * time.Hour) { // Hash-based pseudo-random: 5-95 events per hour @@ -68,7 +69,7 @@ func (handler *Handler) GenerateTestTimeSeriesData(projectId string) error { } // Daily data: last 90 days - log.Infof("Generating daily test data for project %s...", projectId) + projectLogger.Info(fmt.Sprintf("Generating daily test data for project %s...", projectId)) dayStart := now.Add(-90 * 24 * time.Hour) for t := dayStart; t.Before(now); t = t.Add(24 * time.Hour) { // Hash-based pseudo-random: 100-1900 events per day @@ -81,6 +82,6 @@ func (handler *Handler) GenerateTestTimeSeriesData(projectId string) error { } } - log.Infof("Test data generation completed for project %s", projectId) + projectLogger.Info(fmt.Sprintf("Test data generation completed for project %s", projectId)) return nil } diff --git a/pkg/server/health.go b/pkg/server/health.go index f2e9dda..ad137cc 100644 --- a/pkg/server/health.go +++ b/pkg/server/health.go @@ -4,7 +4,7 @@ import ( "encoding/json" "github.com/codex-team/hawk.collector/pkg/hawk" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/valyala/fasthttp" ) diff --git a/pkg/server/releasehandler/handler.go b/pkg/server/releasehandler/handler.go index cb10023..3b0704b 100644 --- a/pkg/server/releasehandler/handler.go +++ b/pkg/server/releasehandler/handler.go @@ -10,8 +10,8 @@ import ( "github.com/codex-team/hawk.collector/pkg/accounts" "github.com/codex-team/hawk.collector/pkg/broker" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/codex-team/hawk.collector/pkg/redis" - log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) @@ -38,13 +38,14 @@ func (handler *Handler) process(form *multipart.Form, token string) ResponseMess log.Debugf("Token %s is not in the accounts cache", token) return ResponseMessage{400, true, fmt.Sprintf("Integration token invalid: %s", token)} } - log.Debugf("Found project with ID %s for integration token %s", projectId, token) + projectLogger := log.With("projectId", projectId) + projectLogger.Debug(fmt.Sprintf("Found project with ID %s for integration token %s", projectId, token)) projectLimits, ok := handler.AccountsMongoDBClient.GetProjectLimits(projectId) if !ok { - log.Warnf("Project %s is not in the projects limits cache", projectId) + projectLogger.Warn(fmt.Sprintf("Project %s is not in the projects limits cache", projectId)) } else { - log.Debugf("Project %s limits: %+v", projectId, projectLimits) + projectLogger.Debug(fmt.Sprintf("Project %s limits: %+v", projectId, projectLimits)) } if handler.RedisClient.IsBlocked(projectId) { @@ -66,7 +67,7 @@ func (handler *Handler) process(form *multipart.Form, token string) ResponseMess } // append file name and content to files array - log.Debugf("[release] Got filename: %s", header.Filename) + projectLogger.Debug(fmt.Sprintf("[release] Got filename: %s", header.Filename)) files = append(files, ReleaseFile{Name: header.Filename, Payload: buf.Bytes()}) } } @@ -82,7 +83,7 @@ func (handler *Handler) process(form *multipart.Form, token string) ResponseMess messageToSend := ReleaseMessage{ProjectId: projectId, Type: AddReleaseType, Payload: ReleaseMessagePayload{Files: files, Release: release, Commits: []byte(commits)}} rawMessage, err := json.Marshal(messageToSend) if err != nil { - log.Errorf("Message marshalling error: %v", err) + projectLogger.Error(fmt.Sprintf("Message marshalling error: %v", err)) return ResponseMessage{400, true, "Cannot encode message to JSON"} } diff --git a/pkg/server/releasehandler/handler_http.go b/pkg/server/releasehandler/handler_http.go index 7089773..39b2851 100644 --- a/pkg/server/releasehandler/handler_http.go +++ b/pkg/server/releasehandler/handler_http.go @@ -6,7 +6,7 @@ import ( "github.com/codex-team/hawk.collector/pkg/accounts" "github.com/codex-team/hawk.collector/pkg/hawk" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/valyala/fasthttp" ) diff --git a/pkg/server/releasehandler/multipart.go b/pkg/server/releasehandler/multipart.go index f39c861..3c98bce 100644 --- a/pkg/server/releasehandler/multipart.go +++ b/pkg/server/releasehandler/multipart.go @@ -2,7 +2,7 @@ package releasehandler import ( "fmt" - log "github.com/sirupsen/logrus" + log "github.com/codex-team/hawk.collector/pkg/logger" "mime/multipart" ) diff --git a/pkg/server/releasehandler/multipart_test.go b/pkg/server/releasehandler/multipart_test.go new file mode 100644 index 0000000..0a9f7ea --- /dev/null +++ b/pkg/server/releasehandler/multipart_test.go @@ -0,0 +1,24 @@ +package releasehandler + +import ( + "mime/multipart" + "testing" +) + +func TestGetSingleFormValue(t *testing.T) { + form := &multipart.Form{Value: map[string][]string{}} + + if err, _ := getSingleFormValue(form, "release"); err == nil { + t.Fatalf("expected error for missing value") + } + + form.Value["release"] = []string{"a", "b"} + if err, _ := getSingleFormValue(form, "release"); err == nil { + t.Fatalf("expected error for multiple values") + } + + form.Value["release"] = []string{"v1.2.3"} + if err, value := getSingleFormValue(form, "release"); err != nil || value != "v1.2.3" { + t.Fatalf("expected value v1.2.3, got %q err=%v", value, err) + } +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 3d0c5fe..76fd6c7 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -13,12 +13,12 @@ import ( "github.com/codex-team/hawk.collector/pkg/alerts" "github.com/codex-team/hawk.collector/pkg/broker" "github.com/codex-team/hawk.collector/pkg/hawk" + log "github.com/codex-team/hawk.collector/pkg/logger" "github.com/codex-team/hawk.collector/pkg/redis" "github.com/codex-team/hawk.collector/pkg/server/errorshandler" "github.com/codex-team/hawk.collector/pkg/server/releasehandler" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - log "github.com/sirupsen/logrus" "github.com/valyala/fasthttp" ) @@ -198,11 +198,12 @@ func (s *Server) HandleGenerateTestTimeSeries(ctx *fasthttp.RequestCtx) { return } - log.Infof("Generating test time series data for project %s", reqBody.ProjectId) + projectLogger := log.With("projectId", reqBody.ProjectId) + projectLogger.Info(fmt.Sprintf("Generating test time series data for project %s", reqBody.ProjectId)) // Generate test data if err := s.ErrorsHandler.GenerateTestTimeSeriesData(reqBody.ProjectId); err != nil { - log.Errorf("Failed to generate test data: %v", err) + projectLogger.Error(fmt.Sprintf("Failed to generate test data: %v", err)) ctx.Error(fmt.Sprintf("Failed to generate test data: %v", err), fasthttp.StatusInternalServerError) return } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go new file mode 100644 index 0000000..03a20e8 --- /dev/null +++ b/pkg/server/server_test.go @@ -0,0 +1,57 @@ +package server + +import ( + "testing" + + "github.com/valyala/fasthttp" +) + +func TestHandleGenerateTestTimeSeries_MethodNotAllowed(t *testing.T) { + req := fasthttp.AcquireRequest() + req.Header.SetMethod(fasthttp.MethodGet) + req.SetRequestURI("/test/generate-timeseries") + + ctx := &fasthttp.RequestCtx{} + ctx.Init(req, nil, nil) + + srv := &Server{} + srv.HandleGenerateTestTimeSeries(ctx) + + if ctx.Response.StatusCode() != fasthttp.StatusMethodNotAllowed { + t.Fatalf("expected status %d, got %d", fasthttp.StatusMethodNotAllowed, ctx.Response.StatusCode()) + } +} + +func TestHandleGenerateTestTimeSeries_InvalidJSON(t *testing.T) { + req := fasthttp.AcquireRequest() + req.Header.SetMethod(fasthttp.MethodPost) + req.SetRequestURI("/test/generate-timeseries") + req.SetBodyString("{bad json") + + ctx := &fasthttp.RequestCtx{} + ctx.Init(req, nil, nil) + + srv := &Server{} + srv.HandleGenerateTestTimeSeries(ctx) + + if ctx.Response.StatusCode() != fasthttp.StatusBadRequest { + t.Fatalf("expected status %d, got %d", fasthttp.StatusBadRequest, ctx.Response.StatusCode()) + } +} + +func TestHandleGenerateTestTimeSeries_MissingProjectId(t *testing.T) { + req := fasthttp.AcquireRequest() + req.Header.SetMethod(fasthttp.MethodPost) + req.SetRequestURI("/test/generate-timeseries") + req.SetBodyString(`{"projectId": ""}`) + + ctx := &fasthttp.RequestCtx{} + ctx.Init(req, nil, nil) + + srv := &Server{} + srv.HandleGenerateTestTimeSeries(ctx) + + if ctx.Response.StatusCode() != fasthttp.StatusBadRequest { + t.Fatalf("expected status %d, got %d", fasthttp.StatusBadRequest, ctx.Response.StatusCode()) + } +}