diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f14fdde..11aa09d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: ^1.21 + go-version: ^1.22 id: go - name: Check out code into the Go module directory @@ -30,7 +30,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: ^1.21 + go-version: ^1.22 id: go - name: Check out code into the Go module directory @@ -40,7 +40,7 @@ jobs: run: go install mvdan.cc/gofumpt@v0.4.0 - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@v0.4.2 + run: go install honnef.co/go/tools/cmd/staticcheck@2024.1.1 - name: Install golangci-lint run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 diff --git a/.golangci.yaml b/.golangci.yaml index bdb9e65..fc4d5cf 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -18,7 +18,6 @@ linters: - nlreturn - noctx - nonamedreturns - - nosnakecase - paralleltest - revive - testpackage @@ -26,10 +25,9 @@ linters: - varnamelen - wrapcheck - wsl - - deadcode - - varcheck - exhaustruct - depguard + - err113 # # Disabled because of generics: @@ -37,18 +35,13 @@ linters: - contextcheck - rowserrcheck - sqlclosecheck - - structcheck - wastedassign # # Disabled because deprecated: # - - exhaustivestruct - - golint - - ifshort - - interfacer - - maligned - - scopelint + - execinquery + - exportloopref linters-settings: # diff --git a/Makefile b/Makefile index f59b05b..5c9eb3d 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ lint: ## Run linters go vet ./... staticcheck ./... golangci-lint run - nilaway ./... + # nilaway ./... .PHONY: fmt fmt: ## Format the code diff --git a/README.md b/README.md index c71fc88..0c92c85 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # go-template [![Goreport status](https://goreportcard.com/badge/github.com/flashbots/go-template)](https://goreportcard.com/report/github.com/flashbots/go-template) -[![Test status](https://github.com/flashbots/go-template/workflows/Checks/badge.svg?branch=main)](https://github.com/flashbots/go-template/actions?query=workflow%3A%22Checks%22) +[![Test status](https://github.com/flashbots/go-template/actions/workflows/checks.yml/badge.svg?branch=main)](https://github.com/flashbots/go-template/actions?query=workflow%3A%22Checks%22) Toolbox and building blocks for new Go projects, to get started quickly and right-footed! diff --git a/cli.dockerfile b/cli.dockerfile index 3b00190..ec24cbf 100644 --- a/cli.dockerfile +++ b/cli.dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21 AS builder +FROM golang:1.23 AS builder ARG VERSION WORKDIR /build ADD go.mod /build/ diff --git a/go.mod b/go.mod index e1340b8..c021ba6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/flashbots/go-template -go 1.21 +go 1.22 require ( github.com/flashbots/go-utils v0.6.1-0.20240610084140-4461ab748667 diff --git a/httpserver.dockerfile b/httpserver.dockerfile index 0fed6bb..83184ce 100644 --- a/httpserver.dockerfile +++ b/httpserver.dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21 AS builder +FROM golang:1.23 AS builder ARG VERSION WORKDIR /build ADD go.mod /build/ diff --git a/httpserver/handler.go b/httpserver/handler.go index da04690..05a1f9a 100644 --- a/httpserver/handler.go +++ b/httpserver/handler.go @@ -7,8 +7,8 @@ import ( "github.com/flashbots/go-template/metrics" ) -func (s *Server) handleAPI(w http.ResponseWriter, r *http.Request) { - m := s.metricsSrv.Float64Histogram( +func (srv *Server) handleAPI(w http.ResponseWriter, r *http.Request) { + m := srv.metricsSrv.Float64Histogram( "request_duration_api", "API request handling duration", metrics.UomMicroseconds, @@ -23,12 +23,12 @@ func (s *Server) handleAPI(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func (s *Server) handleLivenessCheck(w http.ResponseWriter, r *http.Request) { +func (srv *Server) handleLivenessCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func (s *Server) handleReadinessCheck(w http.ResponseWriter, r *http.Request) { - if !s.isReady.Load() { +func (srv *Server) handleReadinessCheck(w http.ResponseWriter, r *http.Request) { + if !srv.isReady.Load() { w.WriteHeader(http.StatusServiceUnavailable) return } @@ -36,19 +36,19 @@ func (s *Server) handleReadinessCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func (s *Server) handleDrain(w http.ResponseWriter, r *http.Request) { - if wasReady := s.isReady.Swap(false); !wasReady { +func (srv *Server) handleDrain(w http.ResponseWriter, r *http.Request) { + if wasReady := srv.isReady.Swap(false); !wasReady { return } // l := logutils.ZapFromRequest(r) - s.log.Info("Server marked as not ready") - time.Sleep(s.cfg.DrainDuration) // Give LB enough time to detect us not ready + srv.log.Info("Server marked as not ready") + time.Sleep(srv.cfg.DrainDuration) // Give LB enough time to detect us not ready } -func (s *Server) handleUndrain(w http.ResponseWriter, r *http.Request) { - if wasReady := s.isReady.Swap(true); wasReady { +func (srv *Server) handleUndrain(w http.ResponseWriter, r *http.Request) { + if wasReady := srv.isReady.Swap(true); wasReady { return } // l := logutils.ZapFromRequest(r) - s.log.Info("Server marked as ready") + srv.log.Info("Server marked as ready") } diff --git a/httpserver/handler_test.go b/httpserver/handler_test.go index 81816fd..c27d5fb 100644 --- a/httpserver/handler_test.go +++ b/httpserver/handler_test.go @@ -94,3 +94,21 @@ func Test_Handlers_Healthcheck_Drain_Undrain(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode, "Healthcheck must return `Ok` after undraining") } } + +func Test_Handlers_Simple(t *testing.T) { + // This test doesn't need the server to actually start and serve. Instead it just tests the handlers. + //nolint: exhaustruct + srv, err := New(&HTTPServerConfig{ + Log: getTestLogger(), + }) + require.NoError(t, err) + + { // Check health + req, err := http.NewRequest(http.MethodGet, "/readyz", nil) //nolint:goconst,nolintlint + require.NoError(t, err) + + rr := httptest.NewRecorder() + srv.getRouter().ServeHTTP(rr, req) + require.Equal(t, http.StatusOK, rr.Code) + } +} diff --git a/httpserver/server.go b/httpserver/server.go index 9121d8b..084d1a5 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -50,6 +50,17 @@ func New(cfg *HTTPServerConfig) (srv *Server, err error) { } srv.isReady.Swap(true) + srv.srv = &http.Server{ + Addr: cfg.ListenAddr, + Handler: srv.getRouter(), + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + } + + return srv, nil +} + +func (srv *Server) getRouter() http.Handler { mux := chi.NewRouter() mux.With(srv.httpLogger).Get("/api", srv.handleAPI) // Never serve at `/` (root) path mux.With(srv.httpLogger).Get("/livez", srv.handleLivenessCheck) @@ -57,65 +68,57 @@ func New(cfg *HTTPServerConfig) (srv *Server, err error) { mux.With(srv.httpLogger).Get("/drain", srv.handleDrain) mux.With(srv.httpLogger).Get("/undrain", srv.handleUndrain) - if cfg.EnablePprof { + if srv.cfg.EnablePprof { srv.log.Info("pprof API enabled") mux.Mount("/debug", middleware.Profiler()) } - - srv.srv = &http.Server{ - Addr: cfg.ListenAddr, - Handler: mux, - ReadTimeout: cfg.ReadTimeout, - WriteTimeout: cfg.WriteTimeout, - } - - return srv, nil + return mux } -func (s *Server) httpLogger(next http.Handler) http.Handler { - return httplogger.LoggingMiddlewareSlog(s.log, next) +func (srv *Server) httpLogger(next http.Handler) http.Handler { + return httplogger.LoggingMiddlewareSlog(srv.log, next) } -func (s *Server) RunInBackground() { +func (srv *Server) RunInBackground() { // metrics - if s.cfg.MetricsAddr != "" { + if srv.cfg.MetricsAddr != "" { go func() { - s.log.With("metricsAddress", s.cfg.MetricsAddr).Info("Starting metrics server") - err := s.metricsSrv.ListenAndServe() + srv.log.With("metricsAddress", srv.cfg.MetricsAddr).Info("Starting metrics server") + err := srv.metricsSrv.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.log.Error("HTTP server failed", "err", err) + srv.log.Error("HTTP server failed", "err", err) } }() } // api go func() { - s.log.Info("Starting HTTP server", "listenAddress", s.cfg.ListenAddr) - if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - s.log.Error("HTTP server failed", "err", err) + srv.log.Info("Starting HTTP server", "listenAddress", srv.cfg.ListenAddr) + if err := srv.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + srv.log.Error("HTTP server failed", "err", err) } }() } -func (s *Server) Shutdown() { +func (srv *Server) Shutdown() { // api - ctx, cancel := context.WithTimeout(context.Background(), s.cfg.GracefulShutdownDuration) + ctx, cancel := context.WithTimeout(context.Background(), srv.cfg.GracefulShutdownDuration) defer cancel() - if err := s.srv.Shutdown(ctx); err != nil { - s.log.Error("Graceful HTTP server shutdown failed", "err", err) + if err := srv.srv.Shutdown(ctx); err != nil { + srv.log.Error("Graceful HTTP server shutdown failed", "err", err) } else { - s.log.Info("HTTP server gracefully stopped") + srv.log.Info("HTTP server gracefully stopped") } // metrics - if len(s.cfg.MetricsAddr) != 0 { - ctx, cancel := context.WithTimeout(context.Background(), s.cfg.GracefulShutdownDuration) + if len(srv.cfg.MetricsAddr) != 0 { + ctx, cancel := context.WithTimeout(context.Background(), srv.cfg.GracefulShutdownDuration) defer cancel() - if err := s.metricsSrv.Shutdown(ctx); err != nil { - s.log.Error("Graceful metrics server shutdown failed", "err", err) + if err := srv.metricsSrv.Shutdown(ctx); err != nil { + srv.log.Error("Graceful metrics server shutdown failed", "err", err) } else { - s.log.Info("Metrics server gracefully stopped") + srv.log.Info("Metrics server gracefully stopped") } } } diff --git a/metrics/README.md b/metrics/README.md index 2a7a7db..27fadeb 100644 --- a/metrics/README.md +++ b/metrics/README.md @@ -1,5 +1,12 @@ # metrics +This example metrics module uses the OpenTelemetry package. + +A solid alternative is [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics), see an example +implementation here: https://github.com/flashbots/mev-share-node/blob/main/metrics/metrics.go + +--- + Introduction: - https://opentelemetry.io/docs/languages/go/instrumentation/