mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
Add tracing and relevant tests (#21)
* Add tracing and relevant tests. * fixup! Add tracing and relevant tests. * gofmt the code 🤷 * fixup! gofmt the code 🤷
This commit is contained in:
@@ -14,6 +14,7 @@ This project is in active use by [telegram-bot.app](https://telegram-bot.app), a
|
|||||||
- [Endpoints](#endpoints)
|
- [Endpoints](#endpoints)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
|
- [Tracing](#tracing)
|
||||||
- [Speed](#speed)
|
- [Speed](#speed)
|
||||||
- [Caching](#caching)
|
- [Caching](#caching)
|
||||||
- [Read-only endpoint](#read-only-endpoint)
|
- [Read-only endpoint](#read-only-endpoint)
|
||||||
@@ -107,6 +108,7 @@ In this case, both proxy and websockets will be available under the `/v1/graphql
|
|||||||
| monitor | Extracting user id from JWT token and adding it as a label to metrics |
|
| monitor | Extracting user id from JWT token and adding it as a label to metrics |
|
||||||
| monitor | Extracting the query name and type and adding it as a label to metrics|
|
| monitor | Extracting the query name and type and adding it as a label to metrics|
|
||||||
| monitor | Calculating the query duration and adding it to the metrics |
|
| monitor | Calculating the query duration and adding it to the metrics |
|
||||||
|
| monitor | OpenTelemetry tracing support with configurable endpoint |
|
||||||
| speed | Caching the queries, together with per-query cache and TTL |
|
| speed | Caching the queries, together with per-query cache and TTL |
|
||||||
| speed | Support for READ ONLY graphql endpoint |
|
| speed | Support for READ ONLY graphql endpoint |
|
||||||
| security | Blocking schema introspection |
|
| security | Blocking schema introspection |
|
||||||
@@ -155,6 +157,26 @@ You can still use the non-prefixed environment variables in the spirit of the ba
|
|||||||
| `HASURA_EVENT_CLEANER` | Enable the hasura event cleaner | `false` |
|
| `HASURA_EVENT_CLEANER` | Enable the hasura event cleaner | `false` |
|
||||||
| `HASURA_EVENT_CLEANER_OLDER_THAN` | The interval for the hasura event cleaner (in days) | `1` |
|
| `HASURA_EVENT_CLEANER_OLDER_THAN` | The interval for the hasura event cleaner (in days) | `1` |
|
||||||
| `HASURA_EVENT_METADATA_DB` | URL to the hasura metadata database | `postgresql://localhost:5432/hasura` |
|
| `HASURA_EVENT_METADATA_DB` | URL to the hasura metadata database | `postgresql://localhost:5432/hasura` |
|
||||||
|
| `ENABLE_TRACE` | Enable OpenTelemetry tracing | `false` |
|
||||||
|
| `TRACE_ENDPOINT` | OpenTelemetry collector endpoint | `localhost:4317` |
|
||||||
|
|
||||||
|
### Tracing
|
||||||
|
|
||||||
|
The proxy supports OpenTelemetry tracing to help monitor and debug requests. When enabled, it will create spans for each proxied request and send them to the configured OpenTelemetry collector.
|
||||||
|
|
||||||
|
To use tracing:
|
||||||
|
|
||||||
|
1. Enable tracing by setting `ENABLE_TRACE=true`
|
||||||
|
2. Configure the OpenTelemetry collector endpoint using `TRACE_ENDPOINT` (defaults to `localhost:4317`)
|
||||||
|
3. Include trace context in your requests using the `X-Trace-Span` header with the following format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The proxy will extract the trace context from the header and create child spans for each request, allowing you to trace requests through your system.
|
||||||
|
|
||||||
### Speed
|
### Speed
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
module github.com/lukaszraczylo/graphql-monitoring-proxy
|
module github.com/lukaszraczylo/graphql-monitoring-proxy
|
||||||
|
|
||||||
go 1.22.4
|
go 1.22.7
|
||||||
|
|
||||||
|
toolchain go1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/VictoriaMetrics/metrics v1.35.1
|
github.com/VictoriaMetrics/metrics v1.35.1
|
||||||
@@ -17,39 +19,53 @@ require (
|
|||||||
github.com/lukaszraczylo/go-ratecounter v0.1.12
|
github.com/lukaszraczylo/go-ratecounter v0.1.12
|
||||||
github.com/lukaszraczylo/go-simple-graphql v1.2.37
|
github.com/lukaszraczylo/go-simple-graphql v1.2.37
|
||||||
github.com/redis/go-redis/v9 v9.7.0
|
github.com/redis/go-redis/v9 v9.7.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/valyala/fasthttp v1.58.0
|
github.com/valyala/fasthttp v1.58.0
|
||||||
|
go.opentelemetry.io/otel v1.33.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0
|
||||||
|
go.opentelemetry.io/otel/sdk v1.33.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.33.0
|
||||||
|
google.golang.org/grpc v1.69.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/gookit/color v1.5.4 // indirect
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fastrand v1.1.0 // indirect
|
github.com/valyala/fastrand v1.1.0 // indirect
|
||||||
github.com/valyala/histogram v1.2.0 // indirect
|
github.com/valyala/histogram v1.2.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/term v0.28.0 // indirect
|
golang.org/x/term v0.28.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,14 +12,20 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
|||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
|
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
|
||||||
@@ -28,6 +34,10 @@ github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27X
|
|||||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
@@ -36,6 +46,8 @@ github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
|
|||||||
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
||||||
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
|
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
|
||||||
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
|
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -70,13 +82,13 @@ github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93Ge
|
|||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||||
@@ -93,6 +105,26 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
|
|||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||||
|
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
|
||||||
|
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||||
|
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||||
|
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||||
|
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
@@ -109,6 +141,14 @@ golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
|||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
|
||||||
|
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||||
|
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
|
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
||||||
|
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
+26
-26
@@ -38,7 +38,7 @@ func prepareQueriesAndExemptions() {
|
|||||||
|
|
||||||
// Process allowed URLs
|
// Process allowed URLs
|
||||||
for _, u := range cfg.Server.AllowURLs {
|
for _, u := range cfg.Server.AllowURLs {
|
||||||
allowedUrls[u] = struct{}{}
|
allowedUrls[u] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,33 +178,33 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
|
|||||||
|
|
||||||
func checkSelections(c *fiber.Ctx, selections []ast.Selection) bool {
|
func checkSelections(c *fiber.Ctx, selections []ast.Selection) bool {
|
||||||
for _, s := range selections {
|
for _, s := range selections {
|
||||||
switch sel := s.(type) {
|
switch sel := s.(type) {
|
||||||
case *ast.Field:
|
case *ast.Field:
|
||||||
fieldName := strings.ToLower(sel.Name.Value)
|
fieldName := strings.ToLower(sel.Name.Value)
|
||||||
if _, exists := introspectionQueries[fieldName]; exists {
|
if _, exists := introspectionQueries[fieldName]; exists {
|
||||||
if len(cfg.Security.IntrospectionAllowed) > 0 {
|
if len(cfg.Security.IntrospectionAllowed) > 0 {
|
||||||
_, allowed := introspectionAllowedQueries[fieldName]
|
_, allowed := introspectionAllowedQueries[fieldName]
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return true // Block if this field isn't allowed
|
return true // Block if this field isn't allowed
|
||||||
}
|
|
||||||
// Even if this field is allowed, we need to check its nested selections
|
|
||||||
} else {
|
|
||||||
return true // Block if no allowlist exists
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Always check nested selections
|
|
||||||
if sel.SelectionSet != nil {
|
|
||||||
if checkSelections(c, sel.GetSelectionSet().Selections) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.InlineFragment:
|
|
||||||
if sel.SelectionSet != nil {
|
|
||||||
if checkSelections(c, sel.GetSelectionSet().Selections) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Even if this field is allowed, we need to check its nested selections
|
||||||
|
} else {
|
||||||
|
return true // Block if no allowlist exists
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Always check nested selections
|
||||||
|
if sel.SelectionSet != nil {
|
||||||
|
if checkSelections(c, sel.GetSelectionSet().Selections) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.InlineFragment:
|
||||||
|
if sel.SelectionSet != nil {
|
||||||
|
if checkSelections(c, sel.GetSelectionSet().Selections) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -13,11 +14,13 @@ import (
|
|||||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||||
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||||
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||||
|
libpack_tracing "github.com/lukaszraczylo/graphql-monitoring-proxy/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg *config
|
cfg *config
|
||||||
once sync.Once
|
once sync.Once
|
||||||
|
tracer *libpack_tracing.TracingSetup
|
||||||
)
|
)
|
||||||
|
|
||||||
// getDetailsFromEnv retrieves the value from the environment or returns the default.
|
// getDetailsFromEnv retrieves the value from the environment or returns the default.
|
||||||
@@ -102,8 +105,38 @@ func parseConfig() {
|
|||||||
c.HasuraEventCleaner.Enable = getDetailsFromEnv("HASURA_EVENT_CLEANER", false)
|
c.HasuraEventCleaner.Enable = getDetailsFromEnv("HASURA_EVENT_CLEANER", false)
|
||||||
c.HasuraEventCleaner.ClearOlderThan = getDetailsFromEnv("HASURA_EVENT_CLEANER_OLDER_THAN", 1)
|
c.HasuraEventCleaner.ClearOlderThan = getDetailsFromEnv("HASURA_EVENT_CLEANER_OLDER_THAN", 1)
|
||||||
c.HasuraEventCleaner.EventMetadataDb = getDetailsFromEnv("HASURA_EVENT_METADATA_DB", "")
|
c.HasuraEventCleaner.EventMetadataDb = getDetailsFromEnv("HASURA_EVENT_METADATA_DB", "")
|
||||||
|
// Tracing configuration
|
||||||
|
c.Tracing.Enable = getDetailsFromEnv("ENABLE_TRACE", false)
|
||||||
|
c.Tracing.Endpoint = getDetailsFromEnv("TRACE_ENDPOINT", "localhost:4317")
|
||||||
cfg = &c
|
cfg = &c
|
||||||
|
|
||||||
|
// Initialize tracing if enabled
|
||||||
|
if cfg.Tracing.Enable {
|
||||||
|
if cfg.Tracing.Endpoint == "" {
|
||||||
|
cfg.Logger.Warning(&libpack_logging.LogMessage{
|
||||||
|
Message: "Tracing endpoint not configured, using default localhost:4317",
|
||||||
|
})
|
||||||
|
cfg.Tracing.Endpoint = "localhost:4317"
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tracer, err = libpack_tracing.NewTracing(ctx, cfg.Tracing.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
cfg.Logger.Error(&libpack_logging.LogMessage{
|
||||||
|
Message: "Failed to initialize tracing",
|
||||||
|
Pairs: map[string]interface{}{"error": err.Error()},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cfg.Logger.Info(&libpack_logging.LogMessage{
|
||||||
|
Message: "Tracing initialized",
|
||||||
|
Pairs: map[string]interface{}{"endpoint": cfg.Tracing.Endpoint},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize cache if enabled
|
// Initialize cache if enabled
|
||||||
if cfg.Cache.CacheEnable || cfg.Cache.CacheRedisEnable {
|
if cfg.Cache.CacheEnable || cfg.Cache.CacheRedisEnable {
|
||||||
cacheConfig := &libpack_cache.CacheConfig{
|
cacheConfig := &libpack_cache.CacheConfig{
|
||||||
@@ -133,6 +166,16 @@ func main() {
|
|||||||
StartMonitoringServer()
|
StartMonitoringServer()
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
StartHTTPProxy()
|
StartHTTPProxy()
|
||||||
|
|
||||||
|
// Cleanup tracing on exit
|
||||||
|
if tracer != nil {
|
||||||
|
if err := tracer.Shutdown(context.Background()); err != nil {
|
||||||
|
cfg.Logger.Error(&libpack_logging.LogMessage{
|
||||||
|
Message: "Error shutting down tracer",
|
||||||
|
Pairs: map[string]interface{}{"error": err.Error()},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ifNotInTest checks if the program is not running in a test environment.
|
// ifNotInTest checks if the program is not running in a test environment.
|
||||||
|
|||||||
@@ -3,17 +3,21 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/avast/retry-go/v4"
|
"github.com/avast/retry-go/v4"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/proxy"
|
"github.com/gofiber/fiber/v2/middleware/proxy"
|
||||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||||
|
libpack_tracing "github.com/lukaszraczylo/graphql-monitoring-proxy/tracing"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +40,30 @@ func createFasthttpClient(timeout int) *fasthttp.Client {
|
|||||||
|
|
||||||
// proxyTheRequest handles the request proxying logic.
|
// proxyTheRequest handles the request proxying logic.
|
||||||
func proxyTheRequest(c *fiber.Ctx, currentEndpoint string) error {
|
func proxyTheRequest(c *fiber.Ctx, currentEndpoint string) error {
|
||||||
|
var span trace.Span
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if cfg.Tracing.Enable && tracer != nil {
|
||||||
|
// Extract trace information from header
|
||||||
|
if traceHeader := c.Get("X-Trace-Span"); traceHeader != "" {
|
||||||
|
spanInfo, err := libpack_tracing.ParseTraceHeader(traceHeader)
|
||||||
|
if err != nil {
|
||||||
|
cfg.Logger.Warning(&libpack_logger.LogMessage{
|
||||||
|
Message: "Failed to parse trace header",
|
||||||
|
Pairs: map[string]interface{}{"error": err.Error()},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if spanCtx, err := tracer.ExtractSpanContext(spanInfo); err == nil {
|
||||||
|
ctx = trace.ContextWithSpanContext(ctx, spanCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new span
|
||||||
|
span, ctx = tracer.StartSpan(ctx, "proxy_request")
|
||||||
|
defer span.End()
|
||||||
|
}
|
||||||
|
|
||||||
if !checkAllowedURLs(c) {
|
if !checkAllowedURLs(c) {
|
||||||
cfg.Logger.Error(&libpack_logger.LogMessage{
|
cfg.Logger.Error(&libpack_logger.LogMessage{
|
||||||
Message: "Request blocked",
|
Message: "Request blocked",
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func processGraphQLRequest(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wasCached := false
|
wasCached := false //nolint:ineffassign
|
||||||
|
|
||||||
if parsedResult.cacheRefresh {
|
if parsedResult.cacheRefresh {
|
||||||
cfg.Logger.Debug(&libpack_logger.LogMessage{
|
cfg.Logger.Debug(&libpack_logger.LogMessage{
|
||||||
|
|||||||
+6
-2
@@ -12,8 +12,12 @@ type config struct {
|
|||||||
Logger *libpack_logging.Logger
|
Logger *libpack_logging.Logger
|
||||||
LogLevel string
|
LogLevel string
|
||||||
Monitoring *libpack_monitoring.MetricsSetup
|
Monitoring *libpack_monitoring.MetricsSetup
|
||||||
Api struct{ BannedUsersFile string }
|
Tracing struct {
|
||||||
Client struct {
|
Enable bool
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
Api struct{ BannedUsersFile string }
|
||||||
|
Client struct {
|
||||||
GQLClient *graphql.BaseClient
|
GQLClient *graphql.BaseClient
|
||||||
FastProxyClient *fasthttp.Client
|
FastProxyClient *fasthttp.Client
|
||||||
JWTUserClaimPath string
|
JWTUserClaimPath string
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TracingSetup struct {
|
||||||
|
tracerProvider *sdktrace.TracerProvider
|
||||||
|
tracer trace.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
type TraceSpanInfo struct {
|
||||||
|
TraceParent string `json:"traceparent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracing creates a new tracing setup with OTLP exporter
|
||||||
|
func NewTracing(ctx context.Context, endpoint string) (*TracingSetup, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return nil, fmt.Errorf("context cannot be nil")
|
||||||
|
}
|
||||||
|
if endpoint == "" {
|
||||||
|
return nil, fmt.Errorf("endpoint cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := grpc.DialContext(ctx, endpoint,
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithBlock(),
|
||||||
|
grpc.WithReturnConnectionError(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create trace exporter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := resource.New(ctx,
|
||||||
|
resource.WithAttributes(
|
||||||
|
semconv.ServiceName("graphql-monitoring-proxy"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create resource: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tracerProvider := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithBatcher(exporter),
|
||||||
|
sdktrace.WithResource(res),
|
||||||
|
)
|
||||||
|
otel.SetTracerProvider(tracerProvider)
|
||||||
|
otel.SetTextMapPropagator(propagation.TraceContext{})
|
||||||
|
|
||||||
|
tracer := tracerProvider.Tracer("graphql-monitoring-proxy")
|
||||||
|
|
||||||
|
return &TracingSetup{
|
||||||
|
tracerProvider: tracerProvider,
|
||||||
|
tracer: tracer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractSpanContext extracts span context from TraceSpanInfo
|
||||||
|
func (ts *TracingSetup) ExtractSpanContext(spanInfo *TraceSpanInfo) (trace.SpanContext, error) {
|
||||||
|
carrier := propagation.MapCarrier{
|
||||||
|
"traceparent": spanInfo.TraceParent,
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = otel.GetTextMapPropagator().Extract(ctx, carrier)
|
||||||
|
spanCtx := trace.SpanContextFromContext(ctx)
|
||||||
|
if !spanCtx.IsValid() {
|
||||||
|
return trace.SpanContext{}, fmt.Errorf("invalid span context")
|
||||||
|
}
|
||||||
|
return spanCtx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTraceHeader parses X-Trace-Span header content
|
||||||
|
func ParseTraceHeader(headerContent string) (*TraceSpanInfo, error) {
|
||||||
|
var spanInfo TraceSpanInfo
|
||||||
|
if err := json.Unmarshal([]byte(headerContent), &spanInfo); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse trace header: %w", err)
|
||||||
|
}
|
||||||
|
return &spanInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown cleanly shuts down the tracer provider
|
||||||
|
func (ts *TracingSetup) Shutdown(ctx context.Context) error {
|
||||||
|
if ts.tracerProvider == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ts.tracerProvider.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartSpan starts a new span with the given name and parent context
|
||||||
|
func (ts *TracingSetup) StartSpan(ctx context.Context, name string) (trace.Span, context.Context) {
|
||||||
|
if ts.tracer == nil {
|
||||||
|
return trace.SpanFromContext(ctx), ctx
|
||||||
|
}
|
||||||
|
ctx, span := ts.tracer.Start(ctx, name)
|
||||||
|
return span, ctx
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseTraceHeader(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
header string
|
||||||
|
want *TraceSpanInfo
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid trace header",
|
||||||
|
header: `{"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}`,
|
||||||
|
want: &TraceSpanInfo{
|
||||||
|
TraceParent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid json",
|
||||||
|
header: `{"traceparent": invalid}`,
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty header",
|
||||||
|
header: "",
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := ParseTraceHeader(tt.header)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ParseTraceHeader() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !tt.wantErr {
|
||||||
|
gotJSON, _ := json.Marshal(got)
|
||||||
|
wantJSON, _ := json.Marshal(tt.want)
|
||||||
|
if string(gotJSON) != string(wantJSON) {
|
||||||
|
t.Errorf("ParseTraceHeader() = %v, want %v", string(gotJSON), string(wantJSON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTracing(t *testing.T) {
|
||||||
|
// Skip actual connection tests since they require a running collector
|
||||||
|
t.Run("empty endpoint", func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := NewTracing(ctx, "")
|
||||||
|
assert.Error(t, err, "Expected error for empty endpoint")
|
||||||
|
assert.Contains(t, err.Error(), "endpoint cannot be empty")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil context", func(t *testing.T) {
|
||||||
|
_, err := NewTracing(nil, "localhost:4317")
|
||||||
|
assert.Error(t, err, "Expected error for nil context")
|
||||||
|
assert.Contains(t, err.Error(), "context cannot be nil")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTracingSetup_ExtractSpanContext(t *testing.T) {
|
||||||
|
ts := &TracingSetup{}
|
||||||
|
spanInfo := &TraceSpanInfo{
|
||||||
|
TraceParent: "invalid-traceparent",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ts.ExtractSpanContext(spanInfo)
|
||||||
|
assert.Error(t, err, "Expected error for invalid traceparent")
|
||||||
|
assert.Contains(t, err.Error(), "invalid span context")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTracingSetup_StartSpan(t *testing.T) {
|
||||||
|
ts := &TracingSetup{}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
span, newCtx := ts.StartSpan(ctx, "test-span")
|
||||||
|
assert.NotNil(t, span, "Expected non-nil span even when tracer is nil")
|
||||||
|
assert.NotNil(t, newCtx, "Expected non-nil context")
|
||||||
|
assert.Equal(t, trace.SpanFromContext(ctx), span, "Expected span from context when tracer is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTracingSetup_Shutdown(t *testing.T) {
|
||||||
|
ts := &TracingSetup{}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err := ts.Shutdown(ctx)
|
||||||
|
assert.NoError(t, err, "Expected no error when shutting down nil tracer provider")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user