diff --git a/.golangci.yml b/.golangci.yml index 4847901..a17d6a3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -78,6 +78,9 @@ linters-settings: - github.com/caitlinelfring/go-env-default - github.com/go-http-utils/headers - github.com/carlmjohnson/flowmatic + - github.com/go-chi/httplog/v2 + - github.com/go-chi/chi/v5 + - github.com/urfave/cli/v2 issues: max-same-issues: 0 # unlimited diff --git a/Dockerfile b/Dockerfile index c807252..07ea105 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN apk add --update-cache \ WORKDIR /src COPY . . -RUN go build -o rpc-gateway cmd/rpcgateway/main.go +RUN go build . FROM alpine:3.19 diff --git a/README.md b/README.md index 1888d99..585818f 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ go test -v ./... To run the app locally ```console -DEBUG=true go run cmd/rpcgateway/main.go --config example_config.yml +DEBUG=true go run . --config example_config.yml ``` ## Configuration diff --git a/cmd/rpcgateway/main.go b/cmd/rpcgateway/main.go deleted file mode 100644 index 058a7f4..0000000 --- a/cmd/rpcgateway/main.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "context" - "flag" - "os" - "os/signal" - "syscall" - - "github.com/0xProject/rpc-gateway/internal/rpcgateway" - "github.com/carlmjohnson/flowmatic" - "github.com/pkg/errors" - "go.uber.org/zap" -) - -func main() { - c, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - - debugLogEnabled := os.Getenv("DEBUG") == "true" - logLevel := zap.WarnLevel - if debugLogEnabled { - logLevel = zap.DebugLevel - } - zapConfig := zap.NewProductionConfig() - zapConfig.Level = zap.NewAtomicLevelAt(logLevel) - logger, _ := zapConfig.Build() - - // We replace the global logger with this initialized here for simplyfication. - // Do see: https://github.com/uber-go/zap/blob/master/FAQ.md#why-include-package-global-loggers - // ref: https://pkg.go.dev/go.uber.org/zap?utm_source=godoc#ReplaceGlobals - // - zap.ReplaceGlobals(logger) - defer func() { - err := logger.Sync() // flushes buffer, if any - if err != nil { - logger.Error("failed to flush logger with err: %s", zap.Error(err)) - } - }() - - // Initialize config - configFileLocation := flag.String("config", "./config.yml", "path to rpc gateway config file") - flag.Parse() - config, err := rpcgateway.NewRPCGatewayFromConfigFile(*configFileLocation) - if err != nil { - logger.Fatal("failed to get config", zap.Error(err)) - } - - service := rpcgateway.NewRPCGateway(*config) - - err = flowmatic.Do( - func() error { - return errors.Wrap(service.Start(c), "cannot start a service") - }, - func() error { - <-c.Done() - - return errors.Wrap(service.Stop(c), "cannot stop a service") - }, - ) - - if err != nil { - logger.Fatal("errors", zap.Error(err)) - } -} diff --git a/example_config.yml b/example_config.yml index f953f60..3c0d5dc 100644 --- a/example_config.yml +++ b/example_config.yml @@ -12,8 +12,12 @@ healthChecks: successThreshold: 1 # how many successes to be marked as healthy again targets: - - name: "QuickNode" + - name: "Ankr" connection: http: # ws is supported by default, it will be a sticky connection. url: "https://rpc.ankr.com/eth" # compression: true # Specify if the target supports request compression + - name: "Cloudflare" + connection: + http: + url: "https://cloudflare-eth.com" diff --git a/go.mod b/go.mod index c00ffb0..19b5ba4 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,13 @@ require ( github.com/caitlinelfring/go-env-default v1.1.0 github.com/carlmjohnson/flowmatic v0.23.4 github.com/ethereum/go-ethereum v1.13.12 + github.com/go-chi/chi/v5 v5.0.12 + github.com/go-chi/httplog/v2 v2.0.9 github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a - github.com/gorilla/mux v1.8.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 - github.com/purini-to/zapmw v1.1.0 github.com/stretchr/testify v1.8.4 - go.uber.org/zap v1.26.0 + github.com/urfave/cli/v2 v2.25.7 gopkg.in/yaml.v2 v2.4.0 ) @@ -22,6 +22,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/carlmjohnson/deque v0.23.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -31,11 +32,12 @@ require ( github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.47.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect golang.org/x/mod v0.15.0 // indirect diff --git a/go.sum b/go.sum index 823739e..95c6628 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -32,7 +34,10 @@ github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.13.12 h1:iDr9UM2JWkngBHGovRJEQn4Kor7mT4gt9rUZqB5M29Y= github.com/ethereum/go-ethereum v1.13.12/go.mod h1:hKL2Qcj1OvStXNSEDbucexqnEt1Wh4Cz329XsjAalZY= -github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/httplog/v2 v2.0.9 h1:RK1TBETd4SSwu075tcfm0KKxR/k98RUfzmOWxLaocGg= +github.com/go-chi/httplog/v2 v2.0.9/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -40,9 +45,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 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/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= @@ -65,10 +67,10 @@ github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpj github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/purini-to/zapmw v1.1.0 h1:izEoLBAv2nXrvIqEndnMdZepwMec2pXLIe/hT1lKSwI= -github.com/purini-to/zapmw v1.1.0/go.mod h1:jJEKz2/jGpBvCjK48sHgJ1/mF80CQ1CuzVx+KR42GII= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -79,17 +81,12 @@ github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08 github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 4fa68e5..46d8a7d 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -5,6 +5,8 @@ import ( "net/http" "time" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -21,16 +23,14 @@ func (s *Server) Stop() error { } func NewServer(config Config) *Server { - mux := http.NewServeMux() + r := chi.NewRouter() - mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - }) - mux.Handle("/metrics", promhttp.Handler()) + r.Use(middleware.Heartbeat("/healthz")) + r.Handle("/metrics", promhttp.Handler()) return &Server{ server: &http.Server{ - Handler: mux, + Handler: r, Addr: fmt.Sprintf(":%d", config.Port), WriteTimeout: time.Second * 15, ReadTimeout: time.Second * 15, diff --git a/internal/proxy/healthchecker.go b/internal/proxy/healthchecker.go index 6071df7..0bdc5d4 100644 --- a/internal/proxy/healthchecker.go +++ b/internal/proxy/healthchecker.go @@ -2,13 +2,13 @@ package proxy import ( "context" + "log/slog" "net/http" "sync" "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - "go.uber.org/zap" ) const ( @@ -16,8 +16,9 @@ const ( ) type HealthCheckerConfig struct { - URL string - Name string // identifier imported from RPC gateway config + URL string + Name string // identifier imported from RPC gateway config + Logger *slog.Logger // How often to check health. Interval time.Duration `yaml:"healthcheckInterval"` @@ -36,6 +37,7 @@ type HealthChecker struct { client *rpc.Client httpClient *http.Client config HealthCheckerConfig + logger *slog.Logger // latest known blockNumber from the RPC. blockNumber uint64 @@ -57,6 +59,7 @@ func NewHealthChecker(config HealthCheckerConfig) (*HealthChecker, error) { client.SetHeader("User-Agent", userAgent) healthchecker := &HealthChecker{ + logger: config.Logger.With("nodeprovider", config.Name), client: client, httpClient: &http.Client{}, config: config, @@ -77,11 +80,11 @@ func (h *HealthChecker) checkBlockNumber(c context.Context) (uint64, error) { err := h.client.CallContext(c, &blockNumber, "eth_blockNumber") if err != nil { - zap.L().Warn("error fetching the block number", zap.Error(err), zap.String("name", h.config.Name)) + h.logger.Error("could not fetch block number", "error", err) return 0, err } - zap.L().Debug("fetched block", zap.Uint64("blockNumber", uint64(blockNumber)), zap.String("rpcProvider", h.config.Name)) + h.logger.Debug("fetch block number completed", "blockNumber", uint64(blockNumber)) return uint64(blockNumber), nil } @@ -92,12 +95,12 @@ func (h *HealthChecker) checkBlockNumber(c context.Context) (uint64, error) { // RPC provider's side. func (h *HealthChecker) checkGasLimit(c context.Context) (uint64, error) { gasLimit, err := performGasLeftCall(c, h.httpClient, h.config.URL) - zap.L().Debug("fetched gas limit", zap.Uint64("gasLimit", gasLimit), zap.String("rpcProvider", h.config.Name)) if err != nil { - zap.L().Warn("failed fetching the gas limit", zap.Error(err), zap.String("rpcProvider", h.config.Name)) + h.logger.Error("could not fetch gas limit", "error", err) return gasLimit, err } + h.logger.Debug("fetch gas limit completed", "gasLimit", gasLimit) return gasLimit, nil } diff --git a/internal/proxy/healthchecker_test.go b/internal/proxy/healthchecker_test.go index 69a14a2..72216bd 100644 --- a/internal/proxy/healthchecker_test.go +++ b/internal/proxy/healthchecker_test.go @@ -2,23 +2,18 @@ package proxy import ( "context" + "log/slog" "net/http" + "os" "testing" "time" "github.com/caitlinelfring/go-env-default" "github.com/stretchr/testify/assert" - "go.uber.org/zap" ) // TestBasicHealthchecker checks if it runs with default options. func TestBasicHealthchecker(t *testing.T) { - logger, _ := zap.NewDevelopment() - // We replace the global logger with this initialized here for simplyfication. - // Do see: https://github.com/uber-go/zap/blob/master/FAQ.md#why-include-package-global-loggers - // ref: https://pkg.go.dev/go.uber.org/zap?utm_source=godoc#ReplaceGlobals - zap.ReplaceGlobals(logger) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -28,6 +23,7 @@ func TestBasicHealthchecker(t *testing.T) { Timeout: 2 * time.Second, FailureThreshold: 1, SuccessThreshold: 1, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), } healthchecker, err := NewHealthChecker(healtcheckConfig) diff --git a/internal/proxy/healthcheckmanager.go b/internal/proxy/healthcheckmanager.go index 93825af..31b9bb6 100644 --- a/internal/proxy/healthcheckmanager.go +++ b/internal/proxy/healthcheckmanager.go @@ -2,21 +2,23 @@ package proxy import ( "context" + "log/slog" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "go.uber.org/zap" ) type HealthCheckManagerConfig struct { Targets []NodeProviderConfig Config HealthCheckConfig + Logger *slog.Logger } type HealthCheckManager struct { - hcs []*HealthChecker + hcs []*HealthChecker + logger *slog.Logger metricRPCProviderInfo *prometheus.GaugeVec metricRPCProviderStatus *prometheus.GaugeVec @@ -26,6 +28,7 @@ type HealthCheckManager struct { func NewHealthCheckManager(config HealthCheckManagerConfig) *HealthCheckManager { hcm := &HealthCheckManager{ + logger: config.Logger, metricRPCProviderInfo: promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: "zeroex_rpc_gateway_provider_info", @@ -61,6 +64,7 @@ func NewHealthCheckManager(config HealthCheckManagerConfig) *HealthCheckManager for _, target := range config.Targets { hc, err := NewHealthChecker( HealthCheckerConfig{ + Logger: config.Logger, URL: target.Connection.HTTP.URL, Name: target.Name, Interval: config.Config.Interval, @@ -129,7 +133,7 @@ func (h *HealthCheckManager) Stop(c context.Context) error { for _, hc := range h.hcs { err := hc.Stop(c) if err != nil { - zap.L().Error("healtchecker stop error", zap.Error(err)) + h.logger.Error("could not stop health check manager", "error", err) } } diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index e2791ad..cc4647e 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -4,8 +4,10 @@ import ( "bytes" "compress/gzip" "io" + "log/slog" "net/http" "net/http/httptest" + "os" "strconv" "testing" "time" @@ -67,6 +69,7 @@ func TestHttpFailoverProxyRerouteRequests(t *testing.T) { healthcheckManager := NewHealthCheckManager(HealthCheckManagerConfig{ Targets: rpcGatewayConfig.Targets, Config: rpcGatewayConfig.HealthChecks, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), }) // Setup HttpFailoverProxy but not starting the HealthCheckManager @@ -118,6 +121,7 @@ func TestHttpFailoverProxyDecompressRequest(t *testing.T) { healthcheckManager := NewHealthCheckManager(HealthCheckManagerConfig{ Targets: rpcGatewayConfig.Targets, Config: rpcGatewayConfig.HealthChecks, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), }) // Setup HttpFailoverProxy but not starting the HealthCheckManager // so the no target will be tainted or marked as unhealthy by the HealthCheckManager @@ -172,6 +176,7 @@ func TestHttpFailoverProxyWithCompressionSupportedTarget(t *testing.T) { healthcheckManager := NewHealthCheckManager(HealthCheckManagerConfig{ Targets: rpcGatewayConfig.Targets, Config: rpcGatewayConfig.HealthChecks, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), }) // Setup HttpFailoverProxy but not starting the HealthCheckManager // so the no target will be tainted or marked as unhealthy by the HealthCheckManager @@ -236,6 +241,7 @@ func TestHTTPFailoverProxyWhenCannotConnectToPrimaryProvider(t *testing.T) { healthcheckManager := NewHealthCheckManager(HealthCheckManagerConfig{ Targets: rpcGatewayConfig.Targets, Config: rpcGatewayConfig.HealthChecks, + Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)), }) // Setup HttpFailoverProxy but not starting the HealthCheckManager so the diff --git a/internal/rpcgateway/rpcgateway.go b/internal/rpcgateway/rpcgateway.go index 86af174..32fba0e 100644 --- a/internal/rpcgateway/rpcgateway.go +++ b/internal/rpcgateway/rpcgateway.go @@ -3,6 +3,7 @@ package rpcgateway import ( "context" "fmt" + "log/slog" "net/http" "os" "time" @@ -10,11 +11,9 @@ import ( "github.com/0xProject/rpc-gateway/internal/metrics" "github.com/0xProject/rpc-gateway/internal/proxy" "github.com/carlmjohnson/flowmatic" - "github.com/gorilla/mux" + "github.com/go-chi/chi/v5" + "github.com/go-chi/httplog/v2" "github.com/pkg/errors" - "github.com/purini-to/zapmw" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" "gopkg.in/yaml.v2" ) @@ -59,10 +58,25 @@ func (r *RPCGateway) Stop(c context.Context) error { } func NewRPCGateway(config RPCGatewayConfig) *RPCGateway { + logLevel := slog.LevelWarn + if os.Getenv("DEBUG") == "true" { + logLevel = slog.LevelDebug + } + + logger := httplog.NewLogger("rpc-gateway", httplog.Options{ + JSON: true, + RequestHeaders: true, + LogLevel: logLevel, + }) + hcm := proxy.NewHealthCheckManager( proxy.HealthCheckManagerConfig{ Targets: config.Targets, Config: config.HealthChecks, + Logger: slog.New( + slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: logLevel, + })), }) proxy := proxy.NewProxy( proxy.Config{ @@ -73,14 +87,9 @@ func NewRPCGateway(config RPCGatewayConfig) *RPCGateway { hcm, ) - r := mux.NewRouter() - - r.Use( - zapmw.WithZap(zap.L()), - zapmw.Request(zapcore.InfoLevel, "request"), - zapmw.Recoverer(zapcore.ErrorLevel, "recover", zapmw.RecovererDefault), - ) - r.PathPrefix("/").Handler(proxy) + r := chi.NewRouter() + r.Use(httplog.RequestLogger(logger)) + r.Handle("/", proxy) return &RPCGateway{ config: config, diff --git a/internal/rpcgateway/rpcgateway_test.go b/internal/rpcgateway/rpcgateway_test.go index 11c0584..e6f58cf 100644 --- a/internal/rpcgateway/rpcgateway_test.go +++ b/internal/rpcgateway/rpcgateway_test.go @@ -15,7 +15,6 @@ import ( "github.com/go-http-utils/headers" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" - "go.uber.org/zap" ) var rpcGatewayConfig = ` @@ -59,10 +58,6 @@ type TestURL struct { func TestRpcGatewayFailover(t *testing.T) { prometheus.DefaultRegisterer = prometheus.NewRegistry() - // initial setup - logger, _ := zap.NewDevelopment() - zap.ReplaceGlobals(logger) - // RPC backends setup onReq := func(r *http.Request) { fmt.Println("got request") diff --git a/main.go b/main.go new file mode 100644 index 0000000..f0ea4ce --- /dev/null +++ b/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/0xProject/rpc-gateway/internal/rpcgateway" + "github.com/carlmjohnson/flowmatic" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +func main() { + c, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + app := &cli.App{ + Name: "rpc-gateway", + Usage: "The failover proxy for node providers.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Usage: "The configuration file path.", + Required: true, + }, + }, + Action: func(cc *cli.Context) error { + config, err := rpcgateway.NewRPCGatewayFromConfigFile(cc.String("config")) + if err != nil { + return err + } + + service := rpcgateway.NewRPCGateway(*config) + + return flowmatic.Do( + func() error { + return errors.Wrap(service.Start(c), "cannot start a service") + }, + func() error { + <-c.Done() + + return errors.Wrap(service.Stop(c), "cannot stop a service") + }, + ) + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "error: %v", err) + } +}