From 85a1e53fbb7f5bd0699ebf04e47c9df17d2446c8 Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Thu, 29 May 2025 01:31:03 +0300 Subject: [PATCH 01/11] Add metrics to API mode. Add env var support to the pkg apifw --- cmd/api-firewall/internal/handlers/api/app.go | 11 +++ cmd/api-firewall/internal/handlers/api/run.go | 38 +++++++++ go.mod | 8 +- go.sum | 22 ++++- internal/config/api.go | 1 + internal/config/metrics.go | 7 ++ internal/platform/metrics/prometheus.go | 83 +++++++++++++++++++ .../validator/api_mode_validate_request.go | 10 ++- pkg/APIMode/apifw.go | 30 +++++++ pkg/APIMode/validator/validator.go | 14 ++++ 10 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 internal/config/metrics.go create mode 100644 internal/platform/metrics/prometheus.go diff --git a/cmd/api-firewall/internal/handlers/api/app.go b/cmd/api-firewall/internal/handlers/api/app.go index 1b1c7567..30aa5258 100644 --- a/cmd/api-firewall/internal/handlers/api/app.go +++ b/cmd/api-firewall/internal/handlers/api/app.go @@ -9,12 +9,14 @@ import ( "strings" "sync" "syscall" + "time" "github.com/google/uuid" "github.com/rs/zerolog" "github.com/savsgio/gotils/strconv" "github.com/valyala/fasthttp" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/router" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/platform/web" @@ -149,10 +151,19 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { // Add request ID ctx.SetUserValue(web.RequestID, uuid.NewString()) + // Request handling start time + start := time.Now() + + defer func() { + metrics.IncHTTPRequestStat(start, ctx.Method(), ctx.Path(), ctx.Response.StatusCode()) + }() + schemaIDs, notFoundSchemaIDs, err := getWallarmSchemaID(ctx, a.storedSpecs) if err != nil { defer web.LogRequestResponseAtTraceLevel(ctx, a.Log) + metrics.IncErrorTypeCounter("schema_id not found", ctx.Method(), ctx.Path()) + a.Log.Error(). Err(err). Bytes("host", ctx.Request.Header.Host()). diff --git a/cmd/api-firewall/internal/handlers/api/run.go b/cmd/api-firewall/internal/handlers/api/run.go index e34de850..0b0673a2 100644 --- a/cmd/api-firewall/internal/handlers/api/run.go +++ b/cmd/api-firewall/internal/handlers/api/run.go @@ -10,11 +10,14 @@ import ( "github.com/ardanlabs/conf" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" "github.com/wallarm/api-firewall/internal/config" "github.com/wallarm/api-firewall/internal/platform/allowiplist" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/version" ) @@ -190,6 +193,41 @@ func Run(logger zerolog.Logger) error { NoDefaultServerHeader: true, } + // ========================================================================= + // Init Metrics + + if cfg.Metrics.Enabled { + + // Init metrics + metrics.InitializeMetrics() + + // Prometheus service handler + fastPrometheusHandler := fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler()) + metricsHandler := func(ctx *fasthttp.RequestCtx) { + switch string(ctx.Path()) { + case cfg.Metrics.Endpoint: + fastPrometheusHandler(ctx) + return + default: + ctx.Error("Unsupported path", fasthttp.StatusNotFound) + } + } + + metricsAPI := fasthttp.Server{ + Handler: metricsHandler, + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + NoDefaultServerHeader: true, + Logger: zeroLogger, + } + + // Start the service listening for requests. + go func() { + logger.Info().Msgf("%s: Metrics API listening on %s%s", logPrefix, cfg.Metrics.Host, cfg.Metrics.Endpoint) + serverErrors <- metricsAPI.ListenAndServe(cfg.Metrics.Host) + }() + } + // ========================================================================= // Init Regular Update Controller diff --git a/go.mod b/go.mod index f747c057..29dbcf2f 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/klauspost/compress v1.18.0 github.com/mattn/go-sqlite3 v1.14.28 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.22.0 github.com/rs/zerolog v1.34.0 github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 github.com/spf13/viper v1.20.1 @@ -37,6 +38,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/corazawaf/libinjection-go v0.2.2 // indirect @@ -66,6 +68,7 @@ require ( github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/nats.go v1.34.1 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect @@ -75,6 +78,9 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect @@ -99,7 +105,7 @@ require ( golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/tools v0.31.0 // indirect - google.golang.org/protobuf v1.36.1 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect nhooyr.io/websocket v1.8.17 // indirect diff --git a/go.sum b/go.sum index fcfe8d51..9a5ce443 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c= github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -73,8 +75,8 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -118,6 +120,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= @@ -145,6 +149,8 @@ github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/I github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4= github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= @@ -167,6 +173,14 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -348,8 +362,8 @@ golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ 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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config/api.go b/internal/config/api.go index 5fa0fca1..913143ea 100644 --- a/internal/config/api.go +++ b/internal/config/api.go @@ -6,6 +6,7 @@ type APIMode struct { APIFWInit APIFWServer ModSecurity + Metrics AllowIP AllowIP TLS TLS diff --git a/internal/config/metrics.go b/internal/config/metrics.go new file mode 100644 index 00000000..cdbcf123 --- /dev/null +++ b/internal/config/metrics.go @@ -0,0 +1,7 @@ +package config + +type Metrics struct { + Endpoint string `conf:"default:metrics,env:METRICS_ENDPOINT" validate:"required,url"` + Host string `conf:"default:0.0.0.0:9090,env:METRICS_HOST" validate:"required"` + Enabled bool `conf:"default:false,env:METRICS_ENABLED"` +} diff --git a/internal/platform/metrics/prometheus.go b/internal/platform/metrics/prometheus.go new file mode 100644 index 00000000..5056f8b8 --- /dev/null +++ b/internal/platform/metrics/prometheus.go @@ -0,0 +1,83 @@ +package metrics + +import ( + strconv2 "strconv" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/savsgio/gotils/strconv" +) + +// List of metrics +var ( + //todo: fix comments + TotalErrors = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "apifw_service_errors_total", + Help: "Total number of errors occurred in the APIFW service.", + }) + + ErrorTypeCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "apifw_service_errors_by_type_total", + Help: "Total number of errors by type and endpoint.", + }, + []string{"error_type", "method", "endpoint"}, + ) + + // Counter: Total number of HTTP requests + HttpRequestsTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Total number of HTTP requests", + }, + []string{"method", "endpoint", "status_code"}, + ) + + // Histogram: HTTP request duration + HttpRequestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "http_request_duration_seconds", + Help: "HTTP request duration in seconds", + //Buckets: prometheus.DefBuckets, // Default buckets: .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 + Buckets: []float64{.001, .005, .025, .05, .25, .5, 1, 2.5, 5}, + }, + []string{"method", "endpoint"}, + ) +) + +// InitializeMetrics metrics +func InitializeMetrics() { + prometheus.MustRegister(TotalErrors, ErrorTypeCounter, HttpRequestsTotal, HttpRequestDuration) +} + +func IncErrorTypeCounter(err string, method, endpoint []byte) { + TotalErrors.Add(1) + ErrorTypeCounter.WithLabelValues(err, strconv.B2S(method), normalizeEndpoint(strconv.B2S(endpoint))).Inc() +} + +func IncHTTPRequestStat(start time.Time, method, endpoint []byte, statusCode int) { + HttpRequestDuration.WithLabelValues(strconv.B2S(method), normalizeEndpoint(strconv.B2S(endpoint))).Observe(time.Since(start).Seconds()) + HttpRequestsTotal.WithLabelValues(strconv.B2S(method), normalizeEndpoint(strconv.B2S(endpoint)), strconv2.Itoa(statusCode)).Inc() +} + +// Normalize endpoints for metrics (replace dynamic parts) +func normalizeEndpoint(path string) string { + // Replace numeric IDs with placeholder + parts := strings.Split(path, "/") + for i, part := range parts { + if part != "" { + // Check if part is numeric (ID) + if _, err := strconv2.Atoi(part); err == nil { + parts[i] = "{id}" + } + } + } + + normalized := strings.Join(parts, "/") + if normalized == "" { + return "/" + } + return normalized +} diff --git a/internal/platform/validator/api_mode_validate_request.go b/internal/platform/validator/api_mode_validate_request.go index 1876b5d8..d6fddb74 100644 --- a/internal/platform/validator/api_mode_validate_request.go +++ b/internal/platform/validator/api_mode_validate_request.go @@ -14,6 +14,7 @@ import ( "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpadaptor" "github.com/valyala/fastjson" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/loader" "github.com/wallarm/api-firewall/internal/platform/router" @@ -102,6 +103,7 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, jsonParserPool *fastjson.P // Convert fasthttp request to net/http request req := http.Request{} if err := fasthttpadaptor.ConvertRequest(ctx, &req, false); err != nil { + metrics.IncErrorTypeCounter("request conversion error", ctx.Method(), ctx.Path()) return nil, errors.Wrap(err, "request conversion error") } @@ -110,6 +112,7 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, jsonParserPool *fastjson.P if requestContentEncoding != "" { var err error if req.Body, err = web.GetDecompressedRequestBody(&ctx.Request, requestContentEncoding); err != nil { + metrics.IncErrorTypeCounter("request body decompression error", ctx.Method(), ctx.Path()) return nil, errors.Wrap(err, "request body decompression error") } } @@ -180,7 +183,8 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, jsonParserPool *fastjson.P // Parse validator error and build the response parsedValErrs, unknownErr := GetErrorResponse(currentErr) if unknownErr != nil { - return nil, errors.Wrap(unknownErr, "validator response parsing error") + metrics.IncErrorTypeCounter("request body decode error: unsupported content type", ctx.Method(), ctx.Path()) + return nil, errors.Wrap(unknownErr, "request body decode error: unsupported content type") } if len(parsedValErrs) > 0 { @@ -192,7 +196,8 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, jsonParserPool *fastjson.P // Parse validator error and build the response parsedValErrs, unknownErr := GetErrorResponse(valErr) if unknownErr != nil { - return nil, errors.Wrap(unknownErr, "validator response parsing error") + metrics.IncErrorTypeCounter("request body decode error: unsupported content type", ctx.Method(), ctx.Path()) + return nil, errors.Wrap(unknownErr, "request body decode error: unsupported content type") } if parsedValErrs != nil { respErrors = append(respErrors, parsedValErrs...) @@ -207,6 +212,7 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, jsonParserPool *fastjson.P // If it is a parsing error then it already handled by the request validator var parseError *ParseError if !errors.As(valUPReqErrors, &parseError) { + metrics.IncErrorTypeCounter("unknown parameter detection error", ctx.Method(), ctx.Path()) return nil, errors.Wrap(valUPReqErrors, "unknown parameter detection error") } } diff --git a/pkg/APIMode/apifw.go b/pkg/APIMode/apifw.go index 0758855e..c543eb53 100644 --- a/pkg/APIMode/apifw.go +++ b/pkg/APIMode/apifw.go @@ -4,12 +4,19 @@ import ( "bufio" "errors" "fmt" + "os" "sync" + "time" + "github.com/ardanlabs/conf" "github.com/valyala/fasthttp" "github.com/valyala/fastjson" + + "github.com/wallarm/api-firewall/internal/config" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/router" "github.com/wallarm/api-firewall/internal/platform/storage" + "github.com/wallarm/api-firewall/internal/version" "github.com/wallarm/api-firewall/pkg/APIMode/validator" ) @@ -94,6 +101,21 @@ func NewAPIFirewall(options ...Option) (APIFirewall, error) { }, } + var cfg config.APIMode + cfg.Version.SVN = version.Version + cfg.Version.Desc = version.ProjectName + + if err := conf.Parse(os.Args[1:], version.Namespace, &cfg); err != nil { + return nil, fmt.Errorf("parsing config: %w", err) + } + + // apply env var params + apiMode.options.PassOptionsRequests = cfg.PassOptionsRequests + apiMode.options.MaxErrorsInResponse = cfg.MaxErrorsInResponse + apiMode.options.UnknownParametersDetection = cfg.UnknownParametersDetection + apiMode.options.PathToSpecDB = cfg.PathToSpecDB + apiMode.options.DBVersion = cfg.DBVersion + // apply all the functional options for _, opt := range options { opt(apiMode.options) @@ -167,6 +189,9 @@ func (a *APIFWModeAPI) ValidateRequest(schemaIDs []int, uri, method, body []byte var wg sync.WaitGroup var m sync.Mutex + // Request handling start time + start := time.Now() + for _, schemaID := range schemaIDs { // build fasthttp RequestCTX @@ -186,6 +211,7 @@ func (a *APIFWModeAPI) ValidateRequest(schemaIDs []int, uri, method, body []byte go func(ctx *fasthttp.RequestCtx, sID int) { defer wg.Done() + defer metrics.IncHTTPRequestStat(start, ctx.Method(), ctx.Path(), ctx.Response.StatusCode()) pReqResp, pReqErrs := validator.ProcessRequest(sID, ctx, a.routers, a.lock, a.options.PassOptionsRequests, a.options.MaxErrorsInResponse) @@ -223,6 +249,9 @@ func (a *APIFWModeAPI) ValidateRequestFromReader(schemaIDs []int, r *bufio.Reade var wg sync.WaitGroup var m sync.Mutex + // Request handling start time + start := time.Now() + for _, schemaID := range schemaIDs { // build fasthttp RequestCTX @@ -243,6 +272,7 @@ func (a *APIFWModeAPI) ValidateRequestFromReader(schemaIDs []int, r *bufio.Reade go func(sID int) { defer wg.Done() + defer metrics.IncHTTPRequestStat(start, ctx.Method(), ctx.Path(), ctx.Response.StatusCode()) pReqResp, pReqErrs := validator.ProcessRequest(sID, ctx, a.routers, a.lock, a.options.PassOptionsRequests, a.options.MaxErrorsInResponse) diff --git a/pkg/APIMode/validator/validator.go b/pkg/APIMode/validator/validator.go index 6add4c75..f40b5e9a 100644 --- a/pkg/APIMode/validator/validator.go +++ b/pkg/APIMode/validator/validator.go @@ -7,6 +7,8 @@ import ( "github.com/savsgio/gotils/strconv" "github.com/valyala/fasthttp" + + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/router" ) @@ -31,6 +33,9 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, routers map[int]*rou case error: err = e default: + + metrics.IncErrorTypeCounter("request parsing error", ctx.Method(), ctx.Path()) + err = fmt.Errorf("%w: panic: %v", ErrRequestParsing, r) } @@ -51,6 +56,9 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, routers map[int]*rou rctx := router.NewRouteContext() handler, err := find(routers, lock, rctx, schemaID, strconv.B2S(ctx.Method()), strconv.B2S(ctx.Request.URI().Path())) if err != nil { + + metrics.IncErrorTypeCounter("schema_id not found", ctx.Method(), ctx.Path()) + return &ValidationResponse{ Summary: []*ValidationResponseSummary{ { @@ -91,6 +99,9 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, routers map[int]*rou ctx.SetUserValue(router.RouteCtxKey, rctx) if err := handler(ctx); err != nil { + + metrics.IncErrorTypeCounter("request parsing error", ctx.Method(), ctx.Path()) + return &ValidationResponse{ Summary: []*ValidationResponseSummary{ { @@ -106,6 +117,9 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, routers map[int]*rou statusCode, ok := ctx.UserValue(strconv2.Itoa(schemaID) + APIModePostfixStatusCode).(int) if !ok { + + metrics.IncErrorTypeCounter("request parsing error", ctx.Method(), ctx.Path()) + // Didn't receive the response code. It means that the router respond to the request because it was not valid. // The API Firewall should respond by 500 status code in this case. return &ValidationResponse{ From 0391b8a6cfd75b7bcd413c6934dda944f31a63ee Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Thu, 29 May 2025 01:33:32 +0300 Subject: [PATCH 02/11] Update description and name of the metrics --- internal/platform/metrics/prometheus.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/platform/metrics/prometheus.go b/internal/platform/metrics/prometheus.go index 5056f8b8..27bccf9f 100644 --- a/internal/platform/metrics/prometheus.go +++ b/internal/platform/metrics/prometheus.go @@ -11,16 +11,17 @@ import ( // List of metrics var ( - //todo: fix comments + // Counter: Total number of errors TotalErrors = prometheus.NewCounter( prometheus.CounterOpts{ Name: "apifw_service_errors_total", Help: "Total number of errors occurred in the APIFW service.", }) + // Counter: Errors by types ErrorTypeCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "apifw_service_errors_by_type_total", + Name: "apifw_service_errors_by_type", Help: "Total number of errors by type and endpoint.", }, []string{"error_type", "method", "endpoint"}, @@ -29,7 +30,7 @@ var ( // Counter: Total number of HTTP requests HttpRequestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "http_requests_total", + Name: "apifw_http_requests_total", Help: "Total number of HTTP requests", }, []string{"method", "endpoint", "status_code"}, @@ -38,7 +39,7 @@ var ( // Histogram: HTTP request duration HttpRequestDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Name: "http_request_duration_seconds", + Name: "apifw_http_request_duration_seconds", Help: "HTTP request duration in seconds", //Buckets: prometheus.DefBuckets, // Default buckets: .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 Buckets: []float64{.001, .005, .025, .05, .25, .5, 1, 2.5, 5}, From 432411138677e19075880bdd7abec2da0882d7cb Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Thu, 29 May 2025 01:52:45 +0300 Subject: [PATCH 03/11] Update default value of the pass options method param --- cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes internal/platform/metrics/prometheus.go | 5 ++--- pkg/APIMode/apifw.go | 8 ++++---- pkg/APIMode/apifw_test.go | 7 +------ 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/cmd/api-firewall/tests/wallarm_api2_update.db b/cmd/api-firewall/tests/wallarm_api2_update.db index 0b1333e78c437977b77ab33ad60e0654496f6a9f..4f06215a7afa782f52899725b17767355b187e0c 100644 GIT binary patch delta 46 zcmZo@U~6b#n;^|Nf1->tYezLsf>0{}zu B4VVA` diff --git a/internal/platform/metrics/prometheus.go b/internal/platform/metrics/prometheus.go index 27bccf9f..2f84a8e0 100644 --- a/internal/platform/metrics/prometheus.go +++ b/internal/platform/metrics/prometheus.go @@ -39,9 +39,8 @@ var ( // Histogram: HTTP request duration HttpRequestDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Name: "apifw_http_request_duration_seconds", - Help: "HTTP request duration in seconds", - //Buckets: prometheus.DefBuckets, // Default buckets: .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 + Name: "apifw_http_request_duration_seconds", + Help: "HTTP request duration in seconds", Buckets: []float64{.001, .005, .025, .05, .25, .5, 1, 2.5, 5}, }, []string{"method", "endpoint"}, diff --git a/pkg/APIMode/apifw.go b/pkg/APIMode/apifw.go index c543eb53..292b3cb7 100644 --- a/pkg/APIMode/apifw.go +++ b/pkg/APIMode/apifw.go @@ -74,10 +74,10 @@ func DisableUnknownParameters() Option { } } -// DisablePassOptionsRequests is a functional option to disable requests with method OPTIONS -func DisablePassOptionsRequests() Option { +// EnablePassOptionsRequests is a functional option to disable requests with method OPTIONS +func EnablePassOptionsRequests() Option { return func(c *Configuration) { - c.PassOptionsRequests = false + c.PassOptionsRequests = true } } @@ -96,7 +96,7 @@ func NewAPIFirewall(options ...Option) (APIFirewall, error) { PathToSpecDB: "", DBVersion: 0, UnknownParametersDetection: true, - PassOptionsRequests: true, + PassOptionsRequests: false, MaxErrorsInResponse: 0, }, } diff --git a/pkg/APIMode/apifw_test.go b/pkg/APIMode/apifw_test.go index 2dca0407..9502161b 100644 --- a/pkg/APIMode/apifw_test.go +++ b/pkg/APIMode/apifw_test.go @@ -299,6 +299,7 @@ func TestAPIFWBasic(t *testing.T) { apifw, err := NewAPIFirewall( WithPathToDB("./wallarm_apifw_test.db"), + EnablePassOptionsRequests(), ) if err != nil { t.Fatal(err) @@ -356,7 +357,6 @@ func TestAPIFWBasicUpdate(t *testing.T) { apifw, err := NewAPIFirewall( WithPathToDB(tmpFileName), DisableUnknownParameters(), - DisablePassOptionsRequests(), ) if err != nil { t.Fatal(err) @@ -419,7 +419,6 @@ func TestAPIFWBasicErrors(t *testing.T) { _, err := NewAPIFirewall( WithPathToDB("./wallarm_apifw_invalid_db_schema.db"), DisableUnknownParameters(), - DisablePassOptionsRequests(), ) if !errors.Is(err, validator.ErrSpecLoading) { t.Errorf("expected ErrSpecLoading but got %v", err) @@ -429,7 +428,6 @@ func TestAPIFWBasicErrors(t *testing.T) { _, err = NewAPIFirewall( WithPathToDB("./wallarm_apifw_invalid_spec.db"), DisableUnknownParameters(), - DisablePassOptionsRequests(), ) if !errors.Is(err, validator.ErrSpecParsing) { t.Errorf("expected ErrSpecParsing but got %v", err) @@ -439,7 +437,6 @@ func TestAPIFWBasicErrors(t *testing.T) { apifw, err := NewAPIFirewall( WithPathToDB("./wallarm_apifw_test.db"), DisableUnknownParameters(), - DisablePassOptionsRequests(), ) if err != nil { t.Fatal(err) @@ -475,7 +472,6 @@ func TestAPIFWBasicErrors(t *testing.T) { _, err = NewAPIFirewall( WithPathToDB("./wallarm_apifw_spec_validation_failed.db"), DisableUnknownParameters(), - DisablePassOptionsRequests(), ) if !errors.Is(err, validator.ErrSpecValidation) { t.Errorf("expected ErrSpecValidation but got %v", err) @@ -489,7 +485,6 @@ func TestSafeCounterThreadSafety(t *testing.T) { apifw, err := NewAPIFirewall( WithPathToDB("./wallarm_apifw_test.db"), - DisablePassOptionsRequests(), ) if err != nil { t.Fatal(err) From ea5e34043c4cd97a1a32df2b8950f8091be7718d Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Fri, 30 May 2025 19:08:28 +0300 Subject: [PATCH 04/11] Bump up Go ver and some dependencies --- .github/workflows/binaries.yml | 12 ++++++------ go.mod | 14 +++++++------- go.sum | 12 ++++++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 62fd30c8..117965e4 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -51,7 +51,7 @@ jobs: needs: - draft-release env: - X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.8.linux-amd64.tar.gz" + X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.9.linux-amd64.tar.gz" APIFIREWALL_NAMESPACE: "github.com/wallarm/api-firewall" strategy: matrix: @@ -162,7 +162,7 @@ jobs: needs: - draft-release env: - X_GO_VERSION: "1.23.8" + X_GO_VERSION: "1.23.9" APIFIREWALL_NAMESPACE: "github.com/wallarm/api-firewall" strategy: matrix: @@ -272,19 +272,19 @@ jobs: include: - arch: armv6 distro: bookworm - go_distribution: https://go.dev/dl/go1.23.8.linux-armv6l.tar.gz + go_distribution: https://go.dev/dl/go1.23.9.linux-armv6l.tar.gz artifact: armv6-libc - arch: aarch64 distro: bookworm - go_distribution: https://go.dev/dl/go1.23.8.linux-arm64.tar.gz + go_distribution: https://go.dev/dl/go1.23.9.linux-arm64.tar.gz artifact: arm64-libc - arch: armv6 distro: alpine_latest - go_distribution: https://go.dev/dl/go1.23.8.linux-armv6l.tar.gz + go_distribution: https://go.dev/dl/go1.23.9.linux-armv6l.tar.gz artifact: armv6-musl - arch: aarch64 distro: alpine_latest - go_distribution: https://go.dev/dl/go1.23.8.linux-arm64.tar.gz + go_distribution: https://go.dev/dl/go1.23.9.linux-arm64.tar.gz artifact: arm64-musl steps: - uses: actions/checkout@v4 diff --git a/go.mod b/go.mod index 29dbcf2f..efbfd25e 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/wallarm/api-firewall go 1.23.0 -toolchain go1.23.8 +toolchain go1.23.9 require ( github.com/andybalholm/brotli v1.1.1 @@ -27,10 +27,10 @@ require ( github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - github.com/valyala/fasthttp v1.61.0 + github.com/valyala/fasthttp v1.62.0 github.com/valyala/fastjson v1.6.4 github.com/wundergraph/graphql-go-tools v1.67.4 - golang.org/x/sync v0.13.0 + golang.org/x/sync v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -98,12 +98,12 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.37.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.31.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 9a5ce443..9f27c6f2 100644 --- a/go.sum +++ b/go.sum @@ -242,6 +242,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.61.0 h1:VV08V0AfoRaFurP1EWKvQQdPTZHiUzaVoulX1aBDgzU= github.com/valyala/fasthttp v1.61.0/go.mod h1:wRIV/4cMwUPWnRcDno9hGnYZGh78QzODFfo1LTUhBog= +github.com/valyala/fasthttp v1.62.0 h1:8dKRBX/y2rCzyc6903Zu1+3qN0H/d2MsxPPmVNamiH0= +github.com/valyala/fasthttp v1.62.0/go.mod h1:FCINgr4GKdKqV8Q0xv8b+UxPV+H/O5nNFo3D+r54Htg= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8= @@ -276,6 +278,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -301,6 +305,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -310,6 +316,8 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -329,6 +337,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -346,6 +356,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= From 92268523ac222fbc286d291302f24c371f91265a Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Fri, 30 May 2025 22:38:36 +0300 Subject: [PATCH 05/11] Update metrics --- cmd/api-firewall/internal/handlers/api/app.go | 13 +++++---- .../internal/handlers/api/handler.go | 2 +- cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes go.sum | 12 --------- internal/platform/metrics/prometheus.go | 25 +++++++++--------- .../validator/api_mode_validate_request.go | 12 ++++----- pkg/APIMode/apifw.go | 13 ++++++--- pkg/APIMode/handler.go | 2 +- pkg/APIMode/validator/validator.go | 8 +++--- 9 files changed, 42 insertions(+), 45 deletions(-) diff --git a/cmd/api-firewall/internal/handlers/api/app.go b/cmd/api-firewall/internal/handlers/api/app.go index 30aa5258..f4db692b 100644 --- a/cmd/api-firewall/internal/handlers/api/app.go +++ b/cmd/api-firewall/internal/handlers/api/app.go @@ -154,15 +154,11 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { // Request handling start time start := time.Now() - defer func() { - metrics.IncHTTPRequestStat(start, ctx.Method(), ctx.Path(), ctx.Response.StatusCode()) - }() - schemaIDs, notFoundSchemaIDs, err := getWallarmSchemaID(ctx, a.storedSpecs) if err != nil { defer web.LogRequestResponseAtTraceLevel(ctx, a.Log) - metrics.IncErrorTypeCounter("schema_id not found", ctx.Method(), ctx.Path()) + metrics.IncErrorTypeCounter("schema_id not found", 0) a.Log.Error(). Err(err). @@ -172,6 +168,8 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { Interface("request_id", ctx.UserValue(web.RequestID)). Msg("error while getting schema ID") + metrics.IncHTTPRequestStat(start, 0, fasthttp.StatusInternalServerError) + if err := web.RespondError(ctx, fasthttp.StatusInternalServerError, ""); err != nil { a.Log.Error(). Err(err). @@ -299,6 +297,11 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { ctx.Request.Header.SetMethod(fasthttp.MethodGet) } + // save http request count for each schema ID + for _, schemaID := range schemaIDs { + metrics.IncHTTPRequestStat(start, schemaID, fasthttp.StatusOK) + } + // limit amount of errors to reduce the total size of the response limitedResponseErrors := validator.SampleSlice(responseErrors, a.maxErrorsInResponse) diff --git a/cmd/api-firewall/internal/handlers/api/handler.go b/cmd/api-firewall/internal/handlers/api/handler.go index ba2aa0e8..47f58136 100644 --- a/cmd/api-firewall/internal/handlers/api/handler.go +++ b/cmd/api-firewall/internal/handlers/api/handler.go @@ -55,7 +55,7 @@ func (s *RequestValidator) Handler(ctx *fasthttp.RequestCtx) error { return nil } - validationErrors, err := apiMode.APIModeValidateRequest(ctx, s.ParserPool, s.CustomRoute, s.Cfg.UnknownParametersDetection) + validationErrors, err := apiMode.APIModeValidateRequest(ctx, s.SchemaID, s.ParserPool, s.CustomRoute, s.Cfg.UnknownParametersDetection) if err != nil { s.Log.Error(). Err(err). diff --git a/cmd/api-firewall/tests/wallarm_api2_update.db b/cmd/api-firewall/tests/wallarm_api2_update.db index 4f06215a7afa782f52899725b17767355b187e0c..4845a4f0e2a16a06aeca3a7a9789d1b580a6994a 100644 GIT binary patch delta 46 zcmZo@U~6b#n;^}&b)t+jtYezLsf> Date: Fri, 30 May 2025 22:50:10 +0300 Subject: [PATCH 06/11] Add GetPrometheusMetrics method --- cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes pkg/APIMode/apifw.go | 20 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/cmd/api-firewall/tests/wallarm_api2_update.db b/cmd/api-firewall/tests/wallarm_api2_update.db index 4845a4f0e2a16a06aeca3a7a9789d1b580a6994a..c4ebb397ee6f3dddc707b18af4def6fc1cbf541e 100644 GIT binary patch delta 47 zcmZo@U~6b#n;^}2V4{pOb(?p~x(G4WH5n{3U Date: Fri, 30 May 2025 22:51:30 +0300 Subject: [PATCH 07/11] Add init metrics --- pkg/APIMode/apifw.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/APIMode/apifw.go b/pkg/APIMode/apifw.go index 4e1c9496..bf32fc7f 100644 --- a/pkg/APIMode/apifw.go +++ b/pkg/APIMode/apifw.go @@ -135,6 +135,11 @@ func NewAPIFirewall(options ...Option) (APIFirewall, error) { var err error + // init metrics + if apiMode.options.Metrics { + metrics.InitializeMetrics() + } + // load spec from the database specsStorage, errLoadDB := storage.NewOpenAPIDB(apiMode.options.PathToSpecDB, apiMode.options.DBVersion) if errLoadDB != nil { From e34152579b2525fc357ae56693dd01a3a98e0625 Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Mon, 2 Jun 2025 17:12:08 +0300 Subject: [PATCH 08/11] Metrics refactoring --- cmd/api-firewall/internal/handlers/api/app.go | 10 +- .../internal/handlers/api/handler.go | 4 +- .../internal/handlers/api/routes.go | 8 +- cmd/api-firewall/internal/handlers/api/run.go | 66 +++++------- .../internal/handlers/api/updater.go | 7 +- .../tests/main_api_mode_bench_test.go | 3 +- cmd/api-firewall/tests/main_api_mode_test.go | 77 ++++++------- cmd/api-firewall/tests/updater_test.go | 33 +++--- cmd/api-firewall/tests/updater_v2_test.go | 35 +++--- cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes internal/config/api.go | 2 +- internal/config/metrics.go | 10 +- internal/platform/metrics/metrics.go | 11 ++ internal/platform/metrics/prometheus.go | 102 ++++++++++++++---- .../validator/api_mode_validate_request.go | 2 +- pkg/APIMode/apifw.go | 37 +++---- pkg/APIMode/handler.go | 5 +- pkg/APIMode/helpers.go | 4 +- pkg/APIMode/validator/validator.go | 2 +- 19 files changed, 246 insertions(+), 172 deletions(-) create mode 100644 internal/platform/metrics/metrics.go diff --git a/cmd/api-firewall/internal/handlers/api/app.go b/cmd/api-firewall/internal/handlers/api/app.go index f4db692b..d8f5bacb 100644 --- a/cmd/api-firewall/internal/handlers/api/app.go +++ b/cmd/api-firewall/internal/handlers/api/app.go @@ -34,6 +34,7 @@ var ( type App struct { Routers map[int]*router.Mux Log zerolog.Logger + Metrics metrics.Metrics passOPTIONS bool maxErrorsInResponse int shutdown chan os.Signal @@ -43,7 +44,7 @@ type App struct { } // NewApp creates an App value that handle a set of routes for the set of application. -func NewApp(lock *sync.RWMutex, passOPTIONS bool, maxErrorsInResponse int, storedSpecs storage.DBOpenAPILoader, shutdown chan os.Signal, logger zerolog.Logger, mw ...web.Middleware) *App { +func NewApp(lock *sync.RWMutex, passOPTIONS bool, maxErrorsInResponse int, storedSpecs storage.DBOpenAPILoader, shutdown chan os.Signal, logger zerolog.Logger, pMetrics metrics.Metrics, mw ...web.Middleware) *App { schemaIDs := storedSpecs.SchemaIDs() @@ -58,6 +59,7 @@ func NewApp(lock *sync.RWMutex, passOPTIONS bool, maxErrorsInResponse int, store shutdown: shutdown, mw: mw, Log: logger, + Metrics: pMetrics, storedSpecs: storedSpecs, lock: lock, passOPTIONS: passOPTIONS, @@ -158,7 +160,7 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { if err != nil { defer web.LogRequestResponseAtTraceLevel(ctx, a.Log) - metrics.IncErrorTypeCounter("schema_id not found", 0) + a.Metrics.IncErrorTypeCounter("schema_id not found", 0) a.Log.Error(). Err(err). @@ -168,7 +170,7 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { Interface("request_id", ctx.UserValue(web.RequestID)). Msg("error while getting schema ID") - metrics.IncHTTPRequestStat(start, 0, fasthttp.StatusInternalServerError) + a.Metrics.IncHTTPRequestTotalCountOnly(0, fasthttp.StatusInternalServerError) if err := web.RespondError(ctx, fasthttp.StatusInternalServerError, ""); err != nil { a.Log.Error(). @@ -299,7 +301,7 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { // save http request count for each schema ID for _, schemaID := range schemaIDs { - metrics.IncHTTPRequestStat(start, schemaID, fasthttp.StatusOK) + a.Metrics.IncHTTPRequestStat(start, schemaID, fasthttp.StatusOK) } // limit amount of errors to reduce the total size of the response diff --git a/cmd/api-firewall/internal/handlers/api/handler.go b/cmd/api-firewall/internal/handlers/api/handler.go index 47f58136..1631a327 100644 --- a/cmd/api-firewall/internal/handlers/api/handler.go +++ b/cmd/api-firewall/internal/handlers/api/handler.go @@ -10,6 +10,7 @@ import ( "github.com/wallarm/api-firewall/internal/config" "github.com/wallarm/api-firewall/internal/platform/loader" + "github.com/wallarm/api-firewall/internal/platform/metrics" apiMode "github.com/wallarm/api-firewall/internal/platform/validator" "github.com/wallarm/api-firewall/internal/platform/web" "github.com/wallarm/api-firewall/pkg/APIMode/validator" @@ -21,6 +22,7 @@ type RequestValidator struct { Log zerolog.Logger Cfg *config.APIMode ParserPool *fastjson.ParserPool + Metrics metrics.Metrics SchemaID int } @@ -55,7 +57,7 @@ func (s *RequestValidator) Handler(ctx *fasthttp.RequestCtx) error { return nil } - validationErrors, err := apiMode.APIModeValidateRequest(ctx, s.SchemaID, s.ParserPool, s.CustomRoute, s.Cfg.UnknownParametersDetection) + validationErrors, err := apiMode.APIModeValidateRequest(ctx, s.Metrics, s.SchemaID, s.ParserPool, s.CustomRoute, s.Cfg.UnknownParametersDetection) if err != nil { s.Log.Error(). Err(err). diff --git a/cmd/api-firewall/internal/handlers/api/routes.go b/cmd/api-firewall/internal/handlers/api/routes.go index 25b0d6e5..55e8a169 100644 --- a/cmd/api-firewall/internal/handlers/api/routes.go +++ b/cmd/api-firewall/internal/handlers/api/routes.go @@ -1,13 +1,13 @@ package api import ( - "github.com/rs/zerolog" "net/url" "os" "runtime/debug" "sync" "github.com/corazawaf/coraza/v3" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/valyala/fasthttp" "github.com/valyala/fastjson" @@ -16,11 +16,12 @@ import ( "github.com/wallarm/api-firewall/internal/mid" "github.com/wallarm/api-firewall/internal/platform/allowiplist" "github.com/wallarm/api-firewall/internal/platform/loader" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/platform/web" ) -func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal, logger zerolog.Logger, storedSpecs storage.DBOpenAPILoader, AllowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) fasthttp.RequestHandler { +func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal, logger zerolog.Logger, metrics metrics.Metrics, storedSpecs storage.DBOpenAPILoader, AllowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) fasthttp.RequestHandler { // handle panic defer func() { @@ -52,7 +53,7 @@ func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal, } // Construct the App which holds all routes as well as common Middleware. - apps := NewApp(lock, cfg.PassOptionsRequests, cfg.MaxErrorsInResponse, storedSpecs, shutdown, logger, mid.IPAllowlist(&ipAllowlistOptions), mid.WAFModSecurity(&modSecOptions), mid.Logger(logger), mid.MIMETypeIdentifier(logger), mid.Errors(logger), mid.Panics(logger)) + apps := NewApp(lock, cfg.PassOptionsRequests, cfg.MaxErrorsInResponse, storedSpecs, shutdown, logger, metrics, mid.IPAllowlist(&ipAllowlistOptions), mid.WAFModSecurity(&modSecOptions), mid.Logger(logger), mid.MIMETypeIdentifier(logger), mid.Errors(logger), mid.Panics(logger)) for _, schemaID := range schemaIDs { @@ -89,6 +90,7 @@ func Handlers(lock *sync.RWMutex, cfg *config.APIMode, shutdown chan os.Signal, ParserPool: &parserPool, OpenAPIRouter: newSwagRouter, SchemaID: schemaID, + Metrics: metrics, } updRoutePathEsc, err := url.JoinPath(serverURL.Path, newSwagRouter.Routes[i].Path) if err != nil { diff --git a/cmd/api-firewall/internal/handlers/api/run.go b/cmd/api-firewall/internal/handlers/api/run.go index 0b0673a2..615b047c 100644 --- a/cmd/api-firewall/internal/handlers/api/run.go +++ b/cmd/api-firewall/internal/handlers/api/run.go @@ -10,10 +10,8 @@ import ( "github.com/ardanlabs/conf" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpadaptor" "github.com/wallarm/api-firewall/internal/config" "github.com/wallarm/api-firewall/internal/platform/allowiplist" @@ -108,10 +106,32 @@ func Run(logger zerolog.Logger) error { zeroLogger := &config.ZerologAdapter{Logger: logger} + // ========================================================================= + // Init Metrics + + // make a channel to listen for errors coming from the metrics listener. Use a + // buffered channel so the goroutine can exit if we don't collect this error. + metricsErrors := make(chan error, 1) + + options := metrics.Options{ + EndpointName: cfg.Metrics.EndpointName, + Host: cfg.Metrics.Host, + ReadTimeout: cfg.Metrics.ReadTimeout, + WriteTimeout: cfg.Metrics.WriteTimeout, + } + + metricsController := metrics.NewPrometheusMetrics(cfg.Metrics.Enabled) + + if cfg.Metrics.Enabled { + go func() { + metricsErrors <- metricsController.StartService(&logger, &options) + }() + } + // ========================================================================= // Init Handlers - requestHandlers := Handlers(&dbLock, &cfg, shutdown, logger, specStorage, allowedIPCache, waf) + requestHandlers := Handlers(&dbLock, &cfg, shutdown, logger, metricsController, specStorage, allowedIPCache, waf) // ========================================================================= // Start Health API Service @@ -193,47 +213,12 @@ func Run(logger zerolog.Logger) error { NoDefaultServerHeader: true, } - // ========================================================================= - // Init Metrics - - if cfg.Metrics.Enabled { - - // Init metrics - metrics.InitializeMetrics() - - // Prometheus service handler - fastPrometheusHandler := fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler()) - metricsHandler := func(ctx *fasthttp.RequestCtx) { - switch string(ctx.Path()) { - case cfg.Metrics.Endpoint: - fastPrometheusHandler(ctx) - return - default: - ctx.Error("Unsupported path", fasthttp.StatusNotFound) - } - } - - metricsAPI := fasthttp.Server{ - Handler: metricsHandler, - ReadTimeout: cfg.ReadTimeout, - WriteTimeout: cfg.WriteTimeout, - NoDefaultServerHeader: true, - Logger: zeroLogger, - } - - // Start the service listening for requests. - go func() { - logger.Info().Msgf("%s: Metrics API listening on %s%s", logPrefix, cfg.Metrics.Host, cfg.Metrics.Endpoint) - serverErrors <- metricsAPI.ListenAndServe(cfg.Metrics.Host) - }() - } - // ========================================================================= // Init Regular Update Controller updSpecErrors := make(chan error, 1) - updOpenAPISpec := NewHandlerUpdater(&dbLock, logger, specStorage, &cfg, &api, shutdown, &healthData, allowedIPCache, waf) + updOpenAPISpec := NewHandlerUpdater(&dbLock, logger, metricsController, specStorage, &cfg, &api, shutdown, &healthData, allowedIPCache, waf) // disable updater if SpecificationUpdatePeriod == 0 if cfg.SpecificationUpdatePeriod.Seconds() > 0 { @@ -266,6 +251,9 @@ func Run(logger zerolog.Logger) error { case err := <-updSpecErrors: return errors.Wrap(err, "regular updater error") + case err := <-metricsErrors: + return errors.Wrap(err, "metrics error") + case sig := <-shutdown: logger.Info().Msgf("%s: %v: Start shutdown", logPrefix, sig) diff --git a/cmd/api-firewall/internal/handlers/api/updater.go b/cmd/api-firewall/internal/handlers/api/updater.go index 114379c0..c252351c 100644 --- a/cmd/api-firewall/internal/handlers/api/updater.go +++ b/cmd/api-firewall/internal/handlers/api/updater.go @@ -1,6 +1,7 @@ package api import ( + "github.com/wallarm/api-firewall/internal/platform/metrics" "os" "runtime/debug" "sync" @@ -35,10 +36,11 @@ type Specification struct { health *Health lock *sync.RWMutex allowedIPCache *allowiplist.AllowedIPsType + metrics metrics.Metrics } // NewHandlerUpdater function defines configuration updater controller -func NewHandlerUpdater(lock *sync.RWMutex, logger zerolog.Logger, sqlLiteStorage storage.DBOpenAPILoader, cfg *config.APIMode, api *fasthttp.Server, shutdown chan os.Signal, health *Health, allowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) updater.Updater { +func NewHandlerUpdater(lock *sync.RWMutex, logger zerolog.Logger, metrics metrics.Metrics, sqlLiteStorage storage.DBOpenAPILoader, cfg *config.APIMode, api *fasthttp.Server, shutdown chan os.Signal, health *Health, allowedIPCache *allowiplist.AllowedIPsType, waf coraza.WAF) updater.Updater { return &Specification{ logger: logger, waf: waf, @@ -51,6 +53,7 @@ func NewHandlerUpdater(lock *sync.RWMutex, logger zerolog.Logger, sqlLiteStorage health: health, lock: lock, allowedIPCache: allowedIPCache, + metrics: metrics, } } @@ -100,7 +103,7 @@ func (s *Specification) Run() { s.lock.Lock() s.sqlLiteStorage = newSpecDB - s.api.Handler = Handlers(s.lock, s.cfg, s.shutdown, s.logger, s.sqlLiteStorage, s.allowedIPCache, s.waf) + s.api.Handler = Handlers(s.lock, s.cfg, s.shutdown, s.logger, s.metrics, s.sqlLiteStorage, s.allowedIPCache, s.waf) s.health.OpenAPIDB = s.sqlLiteStorage if err := s.sqlLiteStorage.AfterLoad(s.cfg.PathToSpecDB); err != nil { s.logger.Error().Err(err).Msgf("%s: error in after specification loading function", logPrefix) diff --git a/cmd/api-firewall/tests/main_api_mode_bench_test.go b/cmd/api-firewall/tests/main_api_mode_bench_test.go index beb17ae1..cb19fc05 100644 --- a/cmd/api-firewall/tests/main_api_mode_bench_test.go +++ b/cmd/api-firewall/tests/main_api_mode_bench_test.go @@ -13,6 +13,7 @@ import ( "github.com/valyala/fasthttp" handlersAPI "github.com/wallarm/api-firewall/cmd/api-firewall/internal/handlers/api" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/platform/web" ) @@ -35,7 +36,7 @@ func BenchmarkAPIModeBasic(b *testing.B) { shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) - handler := handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + handler := handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", diff --git a/cmd/api-firewall/tests/main_api_mode_test.go b/cmd/api-firewall/tests/main_api_mode_test.go index cd0824c1..436b0800 100644 --- a/cmd/api-firewall/tests/main_api_mode_test.go +++ b/cmd/api-firewall/tests/main_api_mode_test.go @@ -26,6 +26,7 @@ import ( handlersAPI "github.com/wallarm/api-firewall/cmd/api-firewall/internal/handlers/api" "github.com/wallarm/api-firewall/internal/config" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/platform/web" "github.com/wallarm/api-firewall/pkg/APIMode/validator" @@ -747,7 +748,7 @@ func (s *APIModeServiceTests) testAPIRunBasic(t *testing.T) { func (s *APIModeServiceTests) testAPIModeSuccess(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -810,7 +811,7 @@ func (s *APIModeServiceTests) testAPIModeSuccess(t *testing.T) { func (s *APIModeServiceTests) testAPIModeMissedMultipleReqParams(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -904,7 +905,7 @@ func (s *APIModeServiceTests) testAPIModeMissedMultipleReqParams(t *testing.T) { func (s *APIModeServiceTests) testAPIModeSuccessEmptyPathParameter(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI(fmt.Sprintf("/absolute-redirect/%d", rand.Uint32())) @@ -941,7 +942,7 @@ func (s *APIModeServiceTests) testAPIModeSuccessEmptyPathParameter(t *testing.T) func (s *APIModeServiceTests) testAPIModeSuccessMultipartStringParameter(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/redirect-to") @@ -1009,7 +1010,7 @@ func (s *APIModeServiceTests) testAPIModeSuccessMultipartStringParameter(t *test func (s *APIModeServiceTests) testAPIModeOneSchemeMultipleIDs(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) // one schema p, err := json.Marshal(map[string]any{ @@ -1093,7 +1094,7 @@ func (s *APIModeServiceTests) testAPIModeOneSchemeMultipleIDs(t *testing.T) { func (s *APIModeServiceTests) testAPIModeTwoDifferentSchemesMultipleIDs(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) // one schema p, err := json.Marshal(map[string]any{ @@ -1200,7 +1201,7 @@ func (s *APIModeServiceTests) testAPIModeTwoDifferentSchemesMultipleIDs(t *testi func (s *APIModeServiceTests) testAPIModeTwoSchemesMultipleIDs(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -1308,7 +1309,7 @@ func (s *APIModeServiceTests) testAPIModeTwoSchemesMultipleIDs(t *testing.T) { func (s *APIModeServiceTests) testAPIModeJSONParseError(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/signup") @@ -1332,7 +1333,7 @@ func (s *APIModeServiceTests) testAPIModeJSONParseError(t *testing.T) { func (s *APIModeServiceTests) testAPIModeInvalidCTParseError(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -1368,7 +1369,7 @@ func (s *APIModeServiceTests) testAPIModeInvalidCTParseError(t *testing.T) { func (s *APIModeServiceTests) testAPIModeCTNotInSpec(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -1404,7 +1405,7 @@ func (s *APIModeServiceTests) testAPIModeCTNotInSpec(t *testing.T) { func (s *APIModeServiceTests) testAPIModeEmptyBody(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/signup") @@ -1428,7 +1429,7 @@ func (s *APIModeServiceTests) testAPIModeEmptyBody(t *testing.T) { func (s *APIModeServiceTests) testAPIModeNoXWallarmSchemaIDHeader(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -1530,7 +1531,7 @@ func (s *APIModeServiceTests) testAPIModeNoXWallarmSchemaIDHeader(t *testing.T) func (s *APIModeServiceTests) testAPIModeMethodAndPathNotFound(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -1621,7 +1622,7 @@ func (s *APIModeServiceTests) testAPIModeMethodAndPathNotFound(t *testing.T) { func (s *APIModeServiceTests) testAPIModeRequiredQueryParameterMissed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/query?id=" + uuid.New().String()) @@ -1657,7 +1658,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredQueryParameterMissed(t *testing func (s *APIModeServiceTests) testAPIModeRequiredHeaderParameterMissed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) xReqTestValue := uuid.New() @@ -1696,7 +1697,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredHeaderParameterMissed(t *testin func (s *APIModeServiceTests) testAPIModeRequiredCookieParameterMissed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/cookies/request") @@ -1733,7 +1734,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredCookieParameterMissed(t *testin func (s *APIModeServiceTests) testAPIModeRequiredBodyMissed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "status": uuid.New().String(), @@ -1784,7 +1785,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredBodyMissed(t *testing.T) { func (s *APIModeServiceTests) testAPIModeRequiredBodyParameterMissed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "status": uuid.New().String(), @@ -1847,7 +1848,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredBodyParameterMissed(t *testing. // Invalid parameters errors func (s *APIModeServiceTests) testAPIModeRequiredQueryParameterInvalidValue(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/query?id=" + uuid.New().String()) @@ -1897,7 +1898,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredQueryParameterInvalidValue(t *t func (s *APIModeServiceTests) testAPIModeRequiredHeaderParameterInvalidValue(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) xReqTestValue := uuid.New() @@ -1953,7 +1954,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredHeaderParameterInvalidValue(t * func (s *APIModeServiceTests) testAPIModeRequiredCookieParameterInvalidValue(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/cookies/request") @@ -2005,7 +2006,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredCookieParameterInvalidValue(t * func (s *APIModeServiceTests) testAPIModeRequiredBodyParameterInvalidValue(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "status": uuid.New().String(), @@ -2160,7 +2161,7 @@ func (s *APIModeServiceTests) testAPIModeRequiredBodyParameterInvalidValue(t *te // security requirements func (s *APIModeServiceTests) testAPIModeBasicAuthFailed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/security/basic") @@ -2229,7 +2230,7 @@ func (s *APIModeServiceTests) testAPIModeBasicAuthFailed(t *testing.T) { func (s *APIModeServiceTests) testAPIModeBearerTokenFailed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/security/bearer") @@ -2298,7 +2299,7 @@ func (s *APIModeServiceTests) testAPIModeBearerTokenFailed(t *testing.T) { func (s *APIModeServiceTests) testAPIModeAPITokenCookieFailed(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/security/cookie") @@ -2367,7 +2368,7 @@ func (s *APIModeServiceTests) testAPIModeAPITokenCookieFailed(t *testing.T) { // unknown parameters func (s *APIModeServiceTests) testAPIModeUnknownParameterBodyJSON(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -2437,7 +2438,7 @@ func (s *APIModeServiceTests) testAPIModeUnknownParameterBodyJSON(t *testing.T) func (s *APIModeServiceTests) testAPIModeUnknownParameterBodyPost(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/signup") @@ -2491,7 +2492,7 @@ func (s *APIModeServiceTests) testAPIModeUnknownParameterBodyPost(t *testing.T) func (s *APIModeServiceTests) testAPIModeUnknownParameterQuery(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/query?uparam=test&id=" + uuid.New().String()) @@ -2530,7 +2531,7 @@ func (s *APIModeServiceTests) testAPIModeUnknownParameterQuery(t *testing.T) { func (s *APIModeServiceTests) testAPIModeUnknownParameterTextPlainCT(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/plain") @@ -2554,7 +2555,7 @@ func (s *APIModeServiceTests) testAPIModeUnknownParameterTextPlainCT(t *testing. func (s *APIModeServiceTests) testAPIModeUnknownParameterInvalidCT(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/unknownCT") @@ -2603,7 +2604,7 @@ func (s *APIModeServiceTests) testAPIModePassOptionsRequest(t *testing.T) { PassOptionsRequests: true, } - handler := handlersAPI.Handlers(s.lock, &cfgPassOptions, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfgPassOptions, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/signup") @@ -2625,7 +2626,7 @@ func (s *APIModeServiceTests) testAPIModePassOptionsRequest(t *testing.T) { func (s *APIModeServiceTests) testAPIModeMultipartOptionalParams(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/multipart") @@ -2692,7 +2693,7 @@ func (s *APIModeServiceTests) testAPIModeMultipartOptionalParams(t *testing.T) { func (s *APIModeServiceTests) testAPIModeInvalidRouteInRequest(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -2748,7 +2749,7 @@ func (s *APIModeServiceTests) testAPIModeInvalidRouteInRequest(t *testing.T) { func (s *APIModeServiceTests) testAPIModeInvalidRouteInRequestInMultipleSchemas(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", @@ -2806,7 +2807,7 @@ func (s *APIModeServiceTests) testAPIModeInvalidRouteInRequestInMultipleSchemas( func (s *APIModeServiceTests) testAPIModeAllMethods(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) // check all supported methods: GET POST PUT PATCH DELETE TRACE OPTIONS HEAD for _, m := range []string{"GET", "POST", "PUT", "PATCH", "DELETE", "TRACE", "OPTIONS", "HEAD"} { @@ -2848,7 +2849,7 @@ func (s *APIModeServiceTests) testAPIModeAllMethods(t *testing.T) { func (s *APIModeServiceTests) testConflictsInThePath(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) // check all related paths for _, path := range []string{"/path/testValue1", "/path/value1.php"} { @@ -2890,7 +2891,7 @@ func (s *APIModeServiceTests) testConflictsInThePath(t *testing.T) { func (s *APIModeServiceTests) testObjectInQuery(t *testing.T) { - handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &cfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) for _, path := range []string{"/query/paramsObject?f.0%5Bf%5D%5B0%5D=test"} { @@ -2940,7 +2941,7 @@ func (s *APIModeServiceTests) testAPIModeMissedMultipleReqParamsLimitedResponse( MaxErrorsInResponse: 1, } - handler := handlersAPI.Handlers(s.lock, &updatedCfg, s.shutdown, s.logger, s.dbSpec, nil, nil) + handler := handlersAPI.Handlers(s.lock, &updatedCfg, s.shutdown, s.logger, metrics.NewPrometheusMetrics(false), s.dbSpec, nil, nil) p, err := json.Marshal(map[string]any{ "firstname": "test", diff --git a/cmd/api-firewall/tests/updater_test.go b/cmd/api-firewall/tests/updater_test.go index ad6732eb..14298c73 100644 --- a/cmd/api-firewall/tests/updater_test.go +++ b/cmd/api-firewall/tests/updater_test.go @@ -15,6 +15,7 @@ import ( handlersAPI "github.com/wallarm/api-firewall/cmd/api-firewall/internal/handlers/api" "github.com/wallarm/api-firewall/internal/config" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/platform/web" "github.com/wallarm/api-firewall/pkg/APIMode/validator" @@ -50,7 +51,7 @@ func TestUpdaterBasic(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) // invalid route in the old spec req := fasthttp.AcquireRequest() @@ -125,7 +126,7 @@ func TestUpdaterBasic(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) health := handlersAPI.Health{} - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -226,7 +227,7 @@ func TestUpdaterFromEmptyDB(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -266,7 +267,7 @@ func TestUpdaterFromEmptyDB(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -367,7 +368,7 @@ func TestUpdaterToEmptyDB(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -450,7 +451,7 @@ func TestUpdaterToEmptyDB(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdaterEmpty, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdaterEmpty, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -516,7 +517,7 @@ func TestUpdaterInvalidDBSchema(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/new") @@ -560,7 +561,7 @@ func TestUpdaterInvalidDBFile(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/test/new") @@ -604,7 +605,7 @@ func TestUpdaterToInvalidDB(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -687,7 +688,7 @@ func TestUpdaterToInvalidDB(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdaterEmpty, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdaterEmpty, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -753,7 +754,7 @@ func TestUpdaterFromInvalidDB(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -783,7 +784,7 @@ func TestUpdaterFromInvalidDB(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -884,7 +885,7 @@ func TestUpdaterToNotExistDB(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -967,7 +968,7 @@ func TestUpdaterToNotExistDB(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdaterEmpty, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdaterEmpty, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -1033,7 +1034,7 @@ func TestUpdaterFromNotExistDB(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfgUpdater, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -1063,7 +1064,7 @@ func TestUpdaterFromNotExistDB(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgUpdater, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfgUpdater.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() diff --git a/cmd/api-firewall/tests/updater_v2_test.go b/cmd/api-firewall/tests/updater_v2_test.go index d5082925..18eeea58 100644 --- a/cmd/api-firewall/tests/updater_v2_test.go +++ b/cmd/api-firewall/tests/updater_v2_test.go @@ -17,6 +17,7 @@ import ( handlersAPI "github.com/wallarm/api-firewall/cmd/api-firewall/internal/handlers/api" "github.com/wallarm/api-firewall/internal/config" + "github.com/wallarm/api-firewall/internal/platform/metrics" "github.com/wallarm/api-firewall/internal/platform/storage" "github.com/wallarm/api-firewall/internal/platform/web" "github.com/wallarm/api-firewall/pkg/APIMode/validator" @@ -214,7 +215,7 @@ func TestLoadBasicV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) // invalid route in the old spec req := fasthttp.AcquireRequest() @@ -317,7 +318,7 @@ func TestUpdaterBasicV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -392,7 +393,7 @@ func TestUpdaterBasicV2(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -447,7 +448,7 @@ func TestUpdaterBasicV2(t *testing.T) { // start updater second time. updNewSpecErrors := make(chan error, 1) - updater = handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2, &api, shutdown, &health, nil, nil) + updater = handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updNewSpecErrors <- updater.Start() @@ -513,7 +514,7 @@ func TestUpdaterFromEmptyDBV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -553,7 +554,7 @@ func TestUpdaterFromEmptyDBV2(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -654,7 +655,7 @@ func TestUpdaterToEmptyDBV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -737,7 +738,7 @@ func TestUpdaterToEmptyDBV2(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2Empty, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2Empty, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -803,7 +804,7 @@ func TestUpdaterInvalidDBSchemaV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) req := fasthttp.AcquireRequest() req.SetRequestURI("/") @@ -847,7 +848,7 @@ func TestUpdaterToInvalidDBV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -930,7 +931,7 @@ func TestUpdaterToInvalidDBV2(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2Invalid, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2Invalid, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -996,7 +997,7 @@ func TestUpdaterFromInvalidDBV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -1026,7 +1027,7 @@ func TestUpdaterFromInvalidDBV2(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -1127,7 +1128,7 @@ func TestUpdaterFromV1DBToV2(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -1216,7 +1217,7 @@ func TestUpdaterFromV1DBToV2(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfgV2, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfgV2, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() @@ -1317,7 +1318,7 @@ func TestUpdaterFromV2DBToV1(t *testing.T) { signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) api := fasthttp.Server{} - api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, specStorage, nil, nil) + api.Handler = handlersAPI.Handlers(&lock, &cfg, shutdown, logger, metrics.NewPrometheusMetrics(false), specStorage, nil, nil) health := handlersAPI.Health{} // invalid route in the old spec @@ -1357,7 +1358,7 @@ func TestUpdaterFromV2DBToV1(t *testing.T) { // start updater updSpecErrors := make(chan error, 1) - updater := handlersAPI.NewHandlerUpdater(&lock, logger, specStorage, &cfg, &api, shutdown, &health, nil, nil) + updater := handlersAPI.NewHandlerUpdater(&lock, logger, metrics.NewPrometheusMetrics(false), specStorage, &cfg, &api, shutdown, &health, nil, nil) go func() { t.Logf("starting specification regular update process every %.0f seconds", cfg.SpecificationUpdatePeriod.Seconds()) updSpecErrors <- updater.Start() diff --git a/cmd/api-firewall/tests/wallarm_api2_update.db b/cmd/api-firewall/tests/wallarm_api2_update.db index c4ebb397ee6f3dddc707b18af4def6fc1cbf541e..8983c4a8c4783984debb6455d47b66b370e0ba31 100644 GIT binary patch delta 46 zcmZo@U~6b#n;^}2ZK8}b Date: Mon, 2 Jun 2025 18:28:58 +0300 Subject: [PATCH 09/11] Update metrics --- cmd/api-firewall/internal/handlers/api/app.go | 2 +- cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes demo/interface/api-mode/main.go | 47 +++++++++++++++++- internal/config/metrics.go | 2 +- internal/platform/metrics/prometheus.go | 4 +- pkg/APIMode/apifw.go | 13 +++++ pkg/APIMode/validator/validator.go | 4 +- 7 files changed, 65 insertions(+), 7 deletions(-) diff --git a/cmd/api-firewall/internal/handlers/api/app.go b/cmd/api-firewall/internal/handlers/api/app.go index d8f5bacb..6ec09cde 100644 --- a/cmd/api-firewall/internal/handlers/api/app.go +++ b/cmd/api-firewall/internal/handlers/api/app.go @@ -160,7 +160,7 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { if err != nil { defer web.LogRequestResponseAtTraceLevel(ctx, a.Log) - a.Metrics.IncErrorTypeCounter("schema_id not found", 0) + a.Metrics.IncErrorTypeCounter("schema not found", 0) a.Log.Error(). Err(err). diff --git a/cmd/api-firewall/tests/wallarm_api2_update.db b/cmd/api-firewall/tests/wallarm_api2_update.db index 8983c4a8c4783984debb6455d47b66b370e0ba31..9fcdd63a81637e83ca2dfa2f2b6fa36cf520fda2 100644 GIT binary patch delta 46 zcmZo@U~6b#n;^~jW}=KU3H87wnk3;+OA CC=Jg5 delta 46 zcmZo@U~6b#n;^}2ZK8}b Date: Mon, 2 Jun 2025 20:02:34 +0300 Subject: [PATCH 10/11] Update metrics --- cmd/api-firewall/internal/handlers/api/app.go | 5 +++++ cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes .../validator/api_mode_validate_request.go | 9 +++++---- pkg/APIMode/apifw.go | 2 +- pkg/APIMode/validator/validator.go | 6 ++---- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cmd/api-firewall/internal/handlers/api/app.go b/cmd/api-firewall/internal/handlers/api/app.go index 6ec09cde..42cfea03 100644 --- a/cmd/api-firewall/internal/handlers/api/app.go +++ b/cmd/api-firewall/internal/handlers/api/app.go @@ -142,6 +142,9 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { // handle panic defer func() { if r := recover(); r != nil { + + a.Metrics.IncErrorTypeCounter("request processing error", 0) + a.Log.Error().Msgf("panic: %v", r) // Log the Go stack trace for this panic'd goroutine. @@ -267,6 +270,8 @@ func (a *App) APIModeMainHandler(ctx *fasthttp.RequestCtx) { continue } + a.Metrics.IncErrorTypeCounter("request processing error", schemaIDs[i]) + // Didn't receive the response code. It means that the router respond to the request because it was not valid. // The API Firewall should respond by 500 status code in this case. ctx.Response.Header.Reset() diff --git a/cmd/api-firewall/tests/wallarm_api2_update.db b/cmd/api-firewall/tests/wallarm_api2_update.db index 9fcdd63a81637e83ca2dfa2f2b6fa36cf520fda2..7f92790c6cc962629c61235b53ecac848c371495 100644 GIT binary patch delta 46 zcmZo@U~6b#n;^~jeWHvr3H87wnk3;+OA CC=Jg5 diff --git a/internal/platform/validator/api_mode_validate_request.go b/internal/platform/validator/api_mode_validate_request.go index 86b53913..c2c8e6bf 100644 --- a/internal/platform/validator/api_mode_validate_request.go +++ b/internal/platform/validator/api_mode_validate_request.go @@ -86,6 +86,7 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, metrics metrics.Metrics, s case error: err = e default: + metrics.IncErrorTypeCounter("request processing error", schemaID) err = fmt.Errorf("panic: %v", r) } @@ -103,8 +104,8 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, metrics metrics.Metrics, s // Convert fasthttp request to net/http request req := http.Request{} if err := fasthttpadaptor.ConvertRequest(ctx, &req, false); err != nil { - metrics.IncErrorTypeCounter("request conversion error", schemaID) - return nil, errors.Wrap(err, "request conversion error") + metrics.IncErrorTypeCounter("request context error", schemaID) + return nil, errors.Wrap(err, "request context error") } // Decode request body @@ -183,7 +184,7 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, metrics metrics.Metrics, s // Parse validator error and build the response parsedValErrs, unknownErr := GetErrorResponse(currentErr) if unknownErr != nil { - metrics.IncErrorTypeCounter("request body decode error: unsupported content type", schemaID) + metrics.IncErrorTypeCounter("request body parsing error", schemaID) return nil, errors.Wrap(unknownErr, "request body decode error: unsupported content type") } @@ -196,7 +197,7 @@ func APIModeValidateRequest(ctx *fasthttp.RequestCtx, metrics metrics.Metrics, s // Parse validator error and build the response parsedValErrs, unknownErr := GetErrorResponse(valErr) if unknownErr != nil { - metrics.IncErrorTypeCounter("request body decode error: unsupported content type", schemaID) + metrics.IncErrorTypeCounter("request body parsing error", schemaID) return nil, errors.Wrap(unknownErr, "request body decode error: unsupported content type") } if parsedValErrs != nil { diff --git a/pkg/APIMode/apifw.go b/pkg/APIMode/apifw.go index 798cc310..3c4e7167 100644 --- a/pkg/APIMode/apifw.go +++ b/pkg/APIMode/apifw.go @@ -288,7 +288,7 @@ func (a *APIFWModeAPI) ValidateRequestFromReader(schemaIDs []int, r *bufio.Reade respErr = fmt.Errorf("%w; %w: %w", respErr, validator.ErrRequestParsing, err) - a.metrics.IncErrorTypeCounter("request conversion error", schemaID) + a.metrics.IncErrorTypeCounter("request context error", schemaID) continue } diff --git a/pkg/APIMode/validator/validator.go b/pkg/APIMode/validator/validator.go index 8ac56683..9a70886f 100644 --- a/pkg/APIMode/validator/validator.go +++ b/pkg/APIMode/validator/validator.go @@ -34,7 +34,7 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, metrics metrics.Metr err = e default: - metrics.IncErrorTypeCounter("request parsing error", schemaID) + metrics.IncErrorTypeCounter("request processing error", schemaID) err = fmt.Errorf("%w: panic: %v", ErrRequestParsing, r) } @@ -100,8 +100,6 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, metrics metrics.Metr if err := handler(ctx); err != nil { - // todo: add metric - return &ValidationResponse{ Summary: []*ValidationResponseSummary{ { @@ -118,7 +116,7 @@ func ProcessRequest(schemaID int, ctx *fasthttp.RequestCtx, metrics metrics.Metr statusCode, ok := ctx.UserValue(strconv2.Itoa(schemaID) + APIModePostfixStatusCode).(int) if !ok { - metrics.IncErrorTypeCounter("request parsing error", schemaID) + metrics.IncErrorTypeCounter("request processing error", schemaID) // Didn't receive the response code. It means that the router respond to the request because it was not valid. // The API Firewall should respond by 500 status code in this case. From 48eedec7421e42e716c35b4cade6b70e9315c21c Mon Sep 17 00:00:00 2001 From: Nikolay Tkachenko Date: Fri, 6 Jun 2025 22:21:46 +0300 Subject: [PATCH 11/11] Cover additional case. Bump up go version. Update dependencies. Update docs. --- .github/workflows/binaries.yml | 12 +++---- Makefile | 4 +-- cmd/api-firewall/internal/handlers/api/app.go | 2 ++ cmd/api-firewall/internal/handlers/api/run.go | 5 +++ cmd/api-firewall/tests/wallarm_api2_update.db | Bin 98304 -> 98304 bytes .../OWASP_CoreRuleSet/docker-compose.yml | 2 +- .../docker-compose-api-mode.yml | 2 +- .../docker-compose-graphql-mode.yml | 2 +- demo/docker-compose/docker-compose.yml | 2 +- docs/configuration-guides/allowlist.md | 2 +- docs/installation-guides/api-mode.md | 2 +- docs/installation-guides/docker-container.md | 4 +-- .../graphql/docker-container.md | 4 +-- docs/release-notes.md | 6 ++++ go.mod | 4 +-- go.sum | 4 +-- helm/api-firewall/Chart.yaml | 2 +- internal/platform/metrics/prometheus.go | 33 ++++++++++++------ resources/test/docker-compose-api-mode.yml | 2 +- 19 files changed, 60 insertions(+), 34 deletions(-) diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 117965e4..1fa916c8 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -51,7 +51,7 @@ jobs: needs: - draft-release env: - X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.9.linux-amd64.tar.gz" + X_GO_DISTRIBUTION: "https://go.dev/dl/go1.23.10.linux-amd64.tar.gz" APIFIREWALL_NAMESPACE: "github.com/wallarm/api-firewall" strategy: matrix: @@ -162,7 +162,7 @@ jobs: needs: - draft-release env: - X_GO_VERSION: "1.23.9" + X_GO_VERSION: "1.23.10" APIFIREWALL_NAMESPACE: "github.com/wallarm/api-firewall" strategy: matrix: @@ -272,19 +272,19 @@ jobs: include: - arch: armv6 distro: bookworm - go_distribution: https://go.dev/dl/go1.23.9.linux-armv6l.tar.gz + go_distribution: https://go.dev/dl/go1.23.10.linux-armv6l.tar.gz artifact: armv6-libc - arch: aarch64 distro: bookworm - go_distribution: https://go.dev/dl/go1.23.9.linux-arm64.tar.gz + go_distribution: https://go.dev/dl/go1.23.10.linux-arm64.tar.gz artifact: arm64-libc - arch: armv6 distro: alpine_latest - go_distribution: https://go.dev/dl/go1.23.9.linux-armv6l.tar.gz + go_distribution: https://go.dev/dl/go1.23.10.linux-armv6l.tar.gz artifact: armv6-musl - arch: aarch64 distro: alpine_latest - go_distribution: https://go.dev/dl/go1.23.9.linux-arm64.tar.gz + go_distribution: https://go.dev/dl/go1.23.10.linux-arm64.tar.gz artifact: arm64-musl steps: - uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index 3631eb90..3eebd2f5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION := 0.9.1 +VERSION := 0.9.2 NAMESPACE := github.com/wallarm/api-firewall .DEFAULT_GOAL := build @@ -43,7 +43,7 @@ stop_k6_tests: run_k6_tests: stop_k6_tests @docker compose -f resources/test/docker-compose-api-mode.yml up --build --detach --force-recreate - docker run --rm -i --network host grafana/k6 run --vus 100 --iterations 1200 -v - -e APIFW_SERVER_URL= \ -e APIFW_REQUEST_VALIDATION= -e APIFW_RESPONSE_VALIDATION= \ -e APIFW_ALLOW_IP_FILE=/opt/ip-allowlist.txt -e APIFW_ALLOW_IP_HEADER_NAME="X-Real-IP" \ - -p 8088:8088 wallarm/api-firewall:v0.9.1 + -p 8088:8088 wallarm/api-firewall:v0.9.2 ``` | Environment variable | Description | diff --git a/docs/installation-guides/api-mode.md b/docs/installation-guides/api-mode.md index 633b69b9..0e72c031 100644 --- a/docs/installation-guides/api-mode.md +++ b/docs/installation-guides/api-mode.md @@ -38,7 +38,7 @@ Use the following command to run the API Firewall container: ``` docker run --rm -it -v :/var/lib/wallarm-api/1/wallarm_api.db \ - -e APIFW_MODE=API -p 8282:8282 wallarm/api-firewall:v0.9.1 + -e APIFW_MODE=API -p 8282:8282 wallarm/api-firewall:v0.9.2 ``` You can pass to the container the following variables: diff --git a/docs/installation-guides/docker-container.md b/docs/installation-guides/docker-container.md index ee31ebd8..70ae5a0a 100644 --- a/docs/installation-guides/docker-container.md +++ b/docs/installation-guides/docker-container.md @@ -27,7 +27,7 @@ networks: services: api-firewall: container_name: api-firewall - image: wallarm/api-firewall:v0.9.1 + image: wallarm/api-firewall:v0.9.2 restart: on-failure volumes: - : @@ -169,6 +169,6 @@ To start API Firewall on Docker, you can also use regular Docker commands as in -v : -e APIFW_API_SPECS= \ -e APIFW_URL= -e APIFW_SERVER_URL= \ -e APIFW_REQUEST_VALIDATION= -e APIFW_RESPONSE_VALIDATION= \ - -p 8088:8088 wallarm/api-firewall:v0.9.1 + -p 8088:8088 wallarm/api-firewall:v0.9.2 ``` 4. When the environment is started, test it and enable traffic on API Firewall following steps 6 and 7. diff --git a/docs/installation-guides/graphql/docker-container.md b/docs/installation-guides/graphql/docker-container.md index b0dfa3e8..cfd573f5 100644 --- a/docs/installation-guides/graphql/docker-container.md +++ b/docs/installation-guides/graphql/docker-container.md @@ -29,7 +29,7 @@ networks: services: api-firewall: container_name: api-firewall - image: wallarm/api-firewall:v0.9.1 + image: wallarm/api-firewall:v0.9.2 restart: on-failure volumes: - : @@ -200,6 +200,6 @@ To start API Firewall on Docker, you can also use regular Docker commands as in -e APIFW_GRAPHQL_MAX_QUERY_COMPLEXITY= \ -e APIFW_GRAPHQL_MAX_QUERY_DEPTH= -e APIFW_GRAPHQL_NODE_COUNT_LIMIT= \ -e APIFW_GRAPHQL_INTROSPECTION= \ - -p 8088:8088 wallarm/api-firewall:v0.9.1 + -p 8088:8088 wallarm/api-firewall:v0.9.2 ``` 4. When the environment is started, test it and enable traffic on API Firewall following steps 6 and 7. diff --git a/docs/release-notes.md b/docs/release-notes.md index 5240124e..e94f89d6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,12 @@ This page describes new releases of Wallarm API Firewall. +## v0.9.2 (2025-06-06) + +* Added Prometheus metrics support in the `API` mode +* Added support of the env vars in the API-Firewall pkg +* Dependency upgrade + ## v0.9.1 (2025-04-23) * Added the `APIFW_API_MODE_MAX_ERRORS_IN_RESPONSE` [environment variable to limit the number of returned validation errors](installation-guides/api-mode.md#running-the-api-firewall-container) in the `API` mode diff --git a/go.mod b/go.mod index efbfd25e..08ee76a4 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/wallarm/api-firewall go 1.23.0 -toolchain go1.23.9 +toolchain go1.23.10 require ( github.com/andybalholm/brotli v1.1.1 @@ -30,7 +30,7 @@ require ( github.com/valyala/fasthttp v1.62.0 github.com/valyala/fastjson v1.6.4 github.com/wundergraph/graphql-go-tools v1.67.4 - golang.org/x/sync v0.14.0 + golang.org/x/sync v0.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index c6fe384f..b8f31c80 100644 --- a/go.sum +++ b/go.sum @@ -308,8 +308,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/helm/api-firewall/Chart.yaml b/helm/api-firewall/Chart.yaml index bdf2daa7..36daed69 100644 --- a/helm/api-firewall/Chart.yaml +++ b/helm/api-firewall/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 name: api-firewall version: 0.7.2 -appVersion: 0.9.1 +appVersion: 0.9.2 description: Wallarm OpenAPI-based API Firewall home: https://github.com/wallarm/api-firewall icon: https://static.wallarm.com/wallarm-logo.svg diff --git a/internal/platform/metrics/prometheus.go b/internal/platform/metrics/prometheus.go index be46c554..fcfb4124 100644 --- a/internal/platform/metrics/prometheus.go +++ b/internal/platform/metrics/prometheus.go @@ -1,7 +1,9 @@ package metrics import ( + "errors" "fmt" + "net/url" strconv2 "strconv" "time" @@ -10,10 +12,11 @@ import ( "github.com/rs/zerolog" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpadaptor" + "github.com/wallarm/api-firewall/internal/config" ) -const logMetricsPrefix = "prometheus metrics" +const logMetricsPrefix = "Prometheus metrics" var ( // Counter: Total number of errors @@ -63,6 +66,7 @@ type PrometheusMetrics struct { logger *zerolog.Logger serviceOpts *Options enabled bool + registry *prometheus.Registry } var _ Metrics = (*PrometheusMetrics)(nil) @@ -80,12 +84,17 @@ func (p *PrometheusMetrics) StartService(logger *zerolog.Logger, options *Option return fmt.Errorf("%s: logger not initialized", logMetricsPrefix) } - p.initializeMetrics() + if err := p.initializeMetrics(); err != nil { + return err + } - endpointName := fmt.Sprintf("/%s", p.serviceOpts.EndpointName) + endpointName, err := url.JoinPath("/", p.serviceOpts.EndpointName) + if err != nil { + return err + } // Prometheus service handler - fastPrometheusHandler := fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler()) + fastPrometheusHandler := fasthttpadaptor.NewFastHTTPHandler(promhttp.HandlerFor(p.registry, promhttp.HandlerOpts{})) metricsHandler := func(ctx *fasthttp.RequestCtx) { switch string(ctx.Path()) { case endpointName: @@ -104,15 +113,19 @@ func (p *PrometheusMetrics) StartService(logger *zerolog.Logger, options *Option Logger: &config.ZerologAdapter{Logger: *p.logger}, } - // Start the service listening for requests. - p.logger.Info().Msgf("%s: API listening on %s%s", logMetricsPrefix, p.serviceOpts.Host, p.serviceOpts.EndpointName) - return metricsAPI.ListenAndServe(p.serviceOpts.Host) } -// initializeMetrics initialized metrics -func (p *PrometheusMetrics) initializeMetrics() { - prometheus.MustRegister(TotalErrors, ErrorTypeCounter, HttpRequestsTotal, HttpRequestDuration) +// initializeMetrics initializes prometheus registry and registers metrics +func (p *PrometheusMetrics) initializeMetrics() error { + if p.registry == nil { + p.registry = prometheus.NewRegistry() + p.registry.MustRegister(TotalErrors, ErrorTypeCounter, HttpRequestsTotal, HttpRequestDuration) + + return nil + } + + return errors.New("registry not initialized") } func (p *PrometheusMetrics) IncErrorTypeCounter(err string, schemaID int) { diff --git a/resources/test/docker-compose-api-mode.yml b/resources/test/docker-compose-api-mode.yml index b6e7b831..1384a6c0 100644 --- a/resources/test/docker-compose-api-mode.yml +++ b/resources/test/docker-compose-api-mode.yml @@ -2,7 +2,7 @@ version: '3.8' services: api-firewall: container_name: api-firewall - image: wallarm/api-firewall:v0.9.1 + image: wallarm/api-firewall:v0.9.2 build: context: ../../ dockerfile: Dockerfile