mirror of
				https://github.com/dcarrillo/whatismyip.git
				synced 2025-11-04 05:49:09 +00:00 
			
		
		
		
	New feature: prometheus metrics endpoint (#46)
This commit is contained in:
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -52,6 +52,7 @@ docker-run: docker-build-dev
 | 
			
		||||
	docker run --tty --interactive --rm \
 | 
			
		||||
		--publish 8080:8080/tcp \
 | 
			
		||||
		--publish 8081:8081/tcp \
 | 
			
		||||
		--publish 9100:9100/tcp \
 | 
			
		||||
		--publish 8081:8081/udp \
 | 
			
		||||
		--volume ${PWD}/test:/test \
 | 
			
		||||
		${DOCKER_URL}:${VERSION} \
 | 
			
		||||
@@ -61,4 +62,5 @@ docker-run: docker-build-dev
 | 
			
		||||
		-tls-bind :8081 \
 | 
			
		||||
		-tls-crt /test/server.pem \
 | 
			
		||||
		-tls-key /test/server.key \
 | 
			
		||||
		-enable-http3
 | 
			
		||||
		-enable-http3 \
 | 
			
		||||
		-metrics-bind :9100
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								README.md
									
									
									
									
									
								
							@@ -6,25 +6,27 @@
 | 
			
		||||
[](https://github.com/dcarrillo/whatismyip/releases/)
 | 
			
		||||
[](./LICENSE)
 | 
			
		||||
 | 
			
		||||
- [What is my IP address](#what-is-my-ip-address)
 | 
			
		||||
  - [Features](#features)
 | 
			
		||||
  - [Endpoints](#endpoints)
 | 
			
		||||
  - [DNS discovery](#dns-discovery)
 | 
			
		||||
  - [Build](#build)
 | 
			
		||||
  - [Usage](#usage)
 | 
			
		||||
  - [Examples](#examples)
 | 
			
		||||
    - [Run a default TCP server](#run-a-default-tcp-server)
 | 
			
		||||
    - [Run a default TCP server with geo information enabled](#run-a-default-tcp-server-with-geo-information-enabled)
 | 
			
		||||
    - [Run a TLS (HTTP/2) and enable "what is my DNS" with geo information](#run-a-tls-http2-and-enable-what-is-my-dns-with-geo-information)
 | 
			
		||||
    - [Run an HTTP/3 server](#run-an-http3-server)
 | 
			
		||||
    - [Run a default TCP server with a custom template and trust a pair of custom headers set by an upstream proxy](#run-a-default-tcp-server-with-a-custom-template-and-trust-a-pair-of-custom-headers-set-by-an-upstream-proxy)
 | 
			
		||||
  - [Download](#download)
 | 
			
		||||
  - [Docker](#docker)
 | 
			
		||||
    - [Run a container locally using test databases](#run-a-container-locally-using-test-databases)
 | 
			
		||||
    - [From Docker Hub](#from-docker-hub)
 | 
			
		||||
- [Features](#features)
 | 
			
		||||
- [Endpoints](#endpoints)
 | 
			
		||||
- [DNS discovery](#dns-discovery)
 | 
			
		||||
- [Build](#build)
 | 
			
		||||
- [Usage](#usage)
 | 
			
		||||
- [Examples](#examples)
 | 
			
		||||
  - [Run a default TCP server](#run-a-default-tcp-server)
 | 
			
		||||
  - [Run a default TCP server with geo information enabled](#run-a-default-tcp-server-with-geo-information-enabled)
 | 
			
		||||
  - [Run a TLS (HTTP/2) and enable "what is my DNS" with geo information](#run-a-tls-http2-and-enable-what-is-my-dns-with-geo-information)
 | 
			
		||||
  - [Run an HTTP/3 server](#run-an-http3-server)
 | 
			
		||||
  - [Run a default TCP server with a custom template and trust a pair of custom headers set by an upstream proxy](#run-a-default-tcp-server-with-a-custom-template-and-trust-a-pair-of-custom-headers-set-by-an-upstream-proxy)
 | 
			
		||||
- [Download](#download)
 | 
			
		||||
- [Docker](#docker)
 | 
			
		||||
  - [Run a container locally using test databases](#run-a-container-locally-using-test-databases)
 | 
			
		||||
  - [From Docker Hub](#from-docker-hub)
 | 
			
		||||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> Since version 3.2.0, an optional prometheus metrics endpoint is available.
 | 
			
		||||
>
 | 
			
		||||
> Since version 3.0.0, the geodb database is not mandatory; not adding the flags will disable the geo feature.
 | 
			
		||||
>
 | 
			
		||||
> Since version 2.3.0, the application includes an optional client [DNS discovery](#dns-discovery) feature.
 | 
			
		||||
 | 
			
		||||
Just another "what is my IP address" service, including geolocation, TCP open port checking, and headers information. Written in Go with high performance in mind,
 | 
			
		||||
@@ -53,12 +55,13 @@ curl -L dns.ifconfig.es
 | 
			
		||||
 | 
			
		||||
- TLS and HTTP/2.
 | 
			
		||||
- Experimental HTTP/3 support. HTTP/3 requires a TLS server running (`-tls-bind`), as HTTP/3 starts as a TLS connection that then gets upgraded to UDP. The UDP port is the same as the one used for the TLS server.
 | 
			
		||||
- Beta DNS discovery: A best-effort approach to discovering the DNS server that is resolving the client's requests.
 | 
			
		||||
- DNS discovery: A best-effort approach to discovering the DNS server that is resolving the client's requests.
 | 
			
		||||
- Can run behind a proxy by trusting a custom header (usually `X-Real-IP`) to figure out the source IP address. It also supports a custom header to resolve the client port, if the proxy can only add a header for the IP (for example a fixed header from CDNs) the client port is shown as unknown.
 | 
			
		||||
- IPv4 and IPv6.
 | 
			
		||||
- Geolocation info including ASN. This feature is possible thanks to [maxmind](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en) GeoLite2 databases. In order to use these databases, a license key is needed. Please visit Maxmind site for further instructions and get a free license.
 | 
			
		||||
- Checking TCP open ports.
 | 
			
		||||
- High performance.
 | 
			
		||||
- Prometheus metrics endpoint: Exports metrics (on a separete process/port) for HTTP requests, request duration, geo lookups, port scans, and DNS queries.
 | 
			
		||||
- Self-contained server that can reload GeoLite2 databases and/or SSL certificates without stop/start. The `hup` signal is honored.
 | 
			
		||||
- HTML templates for the landing page.
 | 
			
		||||
- Text plain and JSON output.
 | 
			
		||||
@@ -117,7 +120,7 @@ curl $(cat /proc/sys/kernel/random/uuid).dns.ifconfig.es
 | 
			
		||||
 | 
			
		||||
## Build
 | 
			
		||||
 | 
			
		||||
Golang >= 1.22 is required.
 | 
			
		||||
Golang >= 1.24 is required.
 | 
			
		||||
 | 
			
		||||
`make build`
 | 
			
		||||
 | 
			
		||||
@@ -126,33 +129,35 @@ Golang >= 1.22 is required.
 | 
			
		||||
```text
 | 
			
		||||
Usage of whatismyip:
 | 
			
		||||
  -bind string
 | 
			
		||||
   	Listening address (see https://pkg.go.dev/net?#Listen) (default ":8080")
 | 
			
		||||
    Listening address (see https://pkg.go.dev/net?#Listen) (default ":8080")
 | 
			
		||||
  -disable-scan
 | 
			
		||||
   	Disable TCP port scanning functionality
 | 
			
		||||
    Disable TCP port scanning functionality
 | 
			
		||||
  -enable-http3
 | 
			
		||||
   	Enable HTTP/3 protocol. HTTP/3 requires --tls-bind set, as HTTP/3 starts as a TLS connection that then gets upgraded to UDP. The UDP port is the same as the one used for the TLS server.
 | 
			
		||||
    Enable HTTP/3 protocol. HTTP/3 requires --tls-bind set, as HTTP/3 starts as a TLS connection that then gets upgraded to UDP. The UDP port is the same as the one used for the TLS server.
 | 
			
		||||
  -enable-secure-headers
 | 
			
		||||
   	Add sane security-related headers to every response
 | 
			
		||||
    Add sane security-related headers to every response
 | 
			
		||||
  -geoip2-asn string
 | 
			
		||||
   	Path to GeoIP2 ASN database. Enables ASN information. (--geoip2-city becomes mandatory)
 | 
			
		||||
    Path to GeoIP2 ASN database. Enables ASN information. (--geoip2-city becomes mandatory)
 | 
			
		||||
  -geoip2-city string
 | 
			
		||||
   	Path to GeoIP2 city database. Enables geo information (--geoip2-asn becomes mandatory)
 | 
			
		||||
    Path to GeoIP2 city database. Enables geo information (--geoip2-asn becomes mandatory)
 | 
			
		||||
  -metrics-bind string
 | 
			
		||||
    Listening address for Prometheus metrics endpoint (see https://pkg.go.dev/net?#Listen). It enables the metrics available at the given address/port via the /metrics endpoint.
 | 
			
		||||
  -resolver string
 | 
			
		||||
   	Path to the resolver configuration. It actually enables the resolver for DNS client discovery.
 | 
			
		||||
    Path to the resolver configuration. It actually enables the resolver for DNS client discovery.
 | 
			
		||||
  -template string
 | 
			
		||||
   	Path to the template file
 | 
			
		||||
    Path to the template file
 | 
			
		||||
  -tls-bind string
 | 
			
		||||
   	Listening address for TLS (see https://pkg.go.dev/net?#Listen)
 | 
			
		||||
    Listening address for TLS (see https://pkg.go.dev/net?#Listen)
 | 
			
		||||
  -tls-crt string
 | 
			
		||||
   	When using TLS, path to certificate file
 | 
			
		||||
    When using TLS, path to certificate file
 | 
			
		||||
  -tls-key string
 | 
			
		||||
   	When using TLS, path to private key file
 | 
			
		||||
    When using TLS, path to private key file
 | 
			
		||||
  -trusted-header string
 | 
			
		||||
   	Trusted request header for remote IP (e.g. X-Real-IP). When using this feature if -trusted-port-header is not set the client port is shown as 'unknown'
 | 
			
		||||
    Trusted request header for remote IP (e.g. X-Real-IP). When using this feature if -trusted-port-header is not set the client port is shown as 'unknown'
 | 
			
		||||
  -trusted-port-header string
 | 
			
		||||
   	Trusted request header for remote client port (e.g. X-Real-Port). When this parameter is set -trusted-header becomes mandatory
 | 
			
		||||
    Trusted request header for remote client port (e.g. X-Real-Port). When this parameter is set -trusted-header becomes mandatory
 | 
			
		||||
  -version
 | 
			
		||||
   	Output version information and exit
 | 
			
		||||
    Output version information and exit
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
@@ -197,7 +202,7 @@ Download the latest version from [github](https://github.com/dcarrillo/whatismyi
 | 
			
		||||
 | 
			
		||||
## Docker
 | 
			
		||||
 | 
			
		||||
An ultra-light (~4MB) image is available on [docker hub](https://hub.docker.com/r/dcarrillo/whatismyip). Since version `2.1.2`, the binary is compressed using [upx](https://github.com/upx/upx).
 | 
			
		||||
An ultra-light (~6MB) image is available on [docker hub](https://hub.docker.com/r/dcarrillo/whatismyip). Since version `2.1.2`, the binary is compressed using [upx](https://github.com/upx/upx).
 | 
			
		||||
 | 
			
		||||
### Run a container locally using test databases
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/httputils"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/metrics"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/setting"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/resolver"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/server"
 | 
			
		||||
@@ -54,6 +55,11 @@ func main() {
 | 
			
		||||
	router.Setup(engine, geoSvc)
 | 
			
		||||
	servers = slices.Concat(servers, setupHTTPServers(context.Background(), engine.Handler()))
 | 
			
		||||
 | 
			
		||||
	if setting.App.PrometheusAddress != "" {
 | 
			
		||||
		prometheusServer := server.NewPrometheusServer(context.Background())
 | 
			
		||||
		servers = append(servers, prometheusServer)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	whatismyip := server.Setup(servers, geoSvc)
 | 
			
		||||
	whatismyip.Run()
 | 
			
		||||
}
 | 
			
		||||
@@ -65,6 +71,10 @@ func setupEngine() *gin.Engine {
 | 
			
		||||
	}
 | 
			
		||||
	engine := gin.New()
 | 
			
		||||
	engine.Use(gin.LoggerWithFormatter(httputils.GetLogFormatter), gin.Recovery())
 | 
			
		||||
	if setting.App.PrometheusAddress != "" {
 | 
			
		||||
		metrics.Enable()
 | 
			
		||||
		engine.Use(metrics.GinMiddleware())
 | 
			
		||||
	}
 | 
			
		||||
	if setting.App.EnableSecureHeaders {
 | 
			
		||||
		engine.Use(secure.New(secure.Config{
 | 
			
		||||
			BrowserXssFilter:   true,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								go.mod
									
									
									
									
									
								
							@@ -10,20 +10,25 @@ require (
 | 
			
		||||
	github.com/miekg/dns v1.1.68
 | 
			
		||||
	github.com/oschwald/maxminddb-golang v1.13.1
 | 
			
		||||
	github.com/patrickmn/go-cache v2.1.0+incompatible
 | 
			
		||||
	github.com/quic-go/quic-go v0.54.1
 | 
			
		||||
	github.com/prometheus/client_golang v1.23.2
 | 
			
		||||
	github.com/quic-go/quic-go v0.55.0
 | 
			
		||||
	github.com/stretchr/testify v1.11.1
 | 
			
		||||
	github.com/testcontainers/testcontainers-go v0.36.0
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	codeberg.org/chavacava/garif v0.2.0 // indirect
 | 
			
		||||
	dario.cat/mergo v1.0.1 // indirect
 | 
			
		||||
	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 | 
			
		||||
	github.com/BurntSushi/toml v1.5.0 // indirect
 | 
			
		||||
	github.com/Microsoft/go-winio v0.6.2 // indirect
 | 
			
		||||
	github.com/beorn7/perks v1.0.1 // indirect
 | 
			
		||||
	github.com/bytedance/gopkg v0.1.3 // indirect
 | 
			
		||||
	github.com/bytedance/sonic v1.14.1 // indirect
 | 
			
		||||
	github.com/bytedance/sonic/loader v0.3.0 // indirect
 | 
			
		||||
	github.com/bytedance/sonic v1.14.2 // indirect
 | 
			
		||||
	github.com/bytedance/sonic/loader v0.4.0 // indirect
 | 
			
		||||
	github.com/cenkalti/backoff/v4 v4.2.1 // indirect
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 | 
			
		||||
	github.com/cloudwego/base64x v0.1.6 // indirect
 | 
			
		||||
	github.com/containerd/log v0.1.0 // indirect
 | 
			
		||||
	github.com/containerd/platforms v0.2.1 // indirect
 | 
			
		||||
@@ -33,25 +38,32 @@ require (
 | 
			
		||||
	github.com/docker/go-connections v0.5.0 // indirect
 | 
			
		||||
	github.com/docker/go-units v0.5.0 // indirect
 | 
			
		||||
	github.com/ebitengine/purego v0.8.2 // indirect
 | 
			
		||||
	github.com/fatih/color v1.18.0 // indirect
 | 
			
		||||
	github.com/fatih/structtag v1.2.0 // indirect
 | 
			
		||||
	github.com/felixge/httpsnoop v1.0.4 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.10 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.11 // indirect
 | 
			
		||||
	github.com/gin-contrib/sse v1.1.0 // indirect
 | 
			
		||||
	github.com/go-logr/logr v1.4.3 // indirect
 | 
			
		||||
	github.com/go-logr/stdr v1.2.2 // indirect
 | 
			
		||||
	github.com/go-ole/go-ole v1.2.6 // indirect
 | 
			
		||||
	github.com/go-playground/locales v0.14.1 // indirect
 | 
			
		||||
	github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.27.0 // indirect
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.28.0 // indirect
 | 
			
		||||
	github.com/goccy/go-json v0.10.5 // indirect
 | 
			
		||||
	github.com/goccy/go-yaml v1.18.0 // indirect
 | 
			
		||||
	github.com/gogo/protobuf v1.3.2 // indirect
 | 
			
		||||
	github.com/hashicorp/go-version v1.7.0 // indirect
 | 
			
		||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.17.4 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.18.0 // indirect
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 | 
			
		||||
	github.com/kylelemons/godebug v1.1.0 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
			
		||||
	github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
 | 
			
		||||
	github.com/magiconair/properties v1.8.9 // indirect
 | 
			
		||||
	github.com/mattn/go-colorable v0.1.14 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
			
		||||
	github.com/mgechev/dots v1.0.0 // indirect
 | 
			
		||||
	github.com/mgechev/revive v1.12.0 // indirect
 | 
			
		||||
	github.com/moby/docker-image-spec v1.3.1 // indirect
 | 
			
		||||
	github.com/moby/patternmatcher v0.6.0 // indirect
 | 
			
		||||
	github.com/moby/sys/sequential v0.5.0 // indirect
 | 
			
		||||
@@ -61,19 +73,24 @@ require (
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/morikuni/aec v1.0.0 // indirect
 | 
			
		||||
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 | 
			
		||||
	github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
			
		||||
	github.com/opencontainers/image-spec v1.1.1 // indirect
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 | 
			
		||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.6.2 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.67.2 // indirect
 | 
			
		||||
	github.com/prometheus/procfs v0.19.2 // indirect
 | 
			
		||||
	github.com/quic-go/qpack v0.5.1 // indirect
 | 
			
		||||
	github.com/shirou/gopsutil/v4 v4.25.1 // indirect
 | 
			
		||||
	github.com/sirupsen/logrus v1.9.3 // indirect
 | 
			
		||||
	github.com/spf13/afero v1.15.0 // indirect
 | 
			
		||||
	github.com/tklauser/go-sysconf v0.3.12 // indirect
 | 
			
		||||
	github.com/tklauser/numcpus v0.6.1 // indirect
 | 
			
		||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
			
		||||
	github.com/ugorji/go/codec v1.3.0 // indirect
 | 
			
		||||
	github.com/ugorji/go/codec v1.3.1 // indirect
 | 
			
		||||
	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 | 
			
		||||
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 | 
			
		||||
	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
 | 
			
		||||
@@ -84,14 +101,15 @@ require (
 | 
			
		||||
	go.opentelemetry.io/otel/trace v1.35.0 // indirect
 | 
			
		||||
	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
 | 
			
		||||
	go.uber.org/mock v0.6.0 // indirect
 | 
			
		||||
	golang.org/x/arch v0.21.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.42.0 // indirect
 | 
			
		||||
	golang.org/x/mod v0.28.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.44.0 // indirect
 | 
			
		||||
	go.yaml.in/yaml/v2 v2.4.3 // indirect
 | 
			
		||||
	golang.org/x/arch v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.43.0 // indirect
 | 
			
		||||
	golang.org/x/mod v0.29.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.46.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.17.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.36.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.29.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.37.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.37.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.30.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.38.0 // indirect
 | 
			
		||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.36.9 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.36.10 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										105
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,19 +1,27 @@
 | 
			
		||||
codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=
 | 
			
		||||
codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=
 | 
			
		||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
 | 
			
		||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 | 
			
		||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
 | 
			
		||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
 | 
			
		||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 | 
			
		||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 | 
			
		||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
 | 
			
		||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 | 
			
		||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 | 
			
		||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 | 
			
		||||
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/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
 | 
			
		||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
 | 
			
		||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
 | 
			
		||||
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
 | 
			
		||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
 | 
			
		||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 | 
			
		||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
 | 
			
		||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
 | 
			
		||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
 | 
			
		||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
 | 
			
		||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
 | 
			
		||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 | 
			
		||||
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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
 | 
			
		||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
 | 
			
		||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
 | 
			
		||||
@@ -37,10 +45,14 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
 | 
			
		||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 | 
			
		||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
 | 
			
		||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
 | 
			
		||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
 | 
			
		||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 | 
			
		||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
 | 
			
		||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
 | 
			
		||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 | 
			
		||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
 | 
			
		||||
github.com/gin-contrib/secure v1.1.2 h1:6G8/NCOTSywWY7TeaH/0Yfaa6bfkE5ukkqtIm7lK11U=
 | 
			
		||||
github.com/gin-contrib/secure v1.1.2/go.mod h1:xI3jI5/BpOYMCBtjgmIVrMA3kI7y9LwCFxs+eLf5S3w=
 | 
			
		||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
 | 
			
		||||
@@ -60,8 +72,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 | 
			
		||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
 | 
			
		||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
 | 
			
		||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 | 
			
		||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
 | 
			
		||||
@@ -76,26 +88,36 @@ 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/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/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
 | 
			
		||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 | 
			
		||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 | 
			
		||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 | 
			
		||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
 | 
			
		||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 | 
			
		||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 | 
			
		||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 | 
			
		||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
			
		||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
			
		||||
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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 | 
			
		||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 | 
			
		||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
 | 
			
		||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
 | 
			
		||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
 | 
			
		||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
github.com/mgechev/dots v1.0.0 h1:o+4OJ3OjWzgQHGJXKfJ8rbH4dqDugu5BiEy84nxg0k4=
 | 
			
		||||
github.com/mgechev/dots v1.0.0/go.mod h1:rykuMydC9t3wfkM+ccYH3U3ss03vZGg6h3hmOznXLH0=
 | 
			
		||||
github.com/mgechev/revive v1.12.0 h1:Q+/kkbbwerrVYPv9d9efaPGmAO/NsxwW/nE6ahpQaCU=
 | 
			
		||||
github.com/mgechev/revive v1.12.0/go.mod h1:VXsY2LsTigk8XU9BpZauVLjVrhICMOV3k1lpB3CXrp8=
 | 
			
		||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
 | 
			
		||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
 | 
			
		||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
 | 
			
		||||
@@ -117,6 +139,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 | 
			
		||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 | 
			
		||||
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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 | 
			
		||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
 | 
			
		||||
@@ -133,16 +157,26 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
 | 
			
		||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
 | 
			
		||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
 | 
			
		||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
 | 
			
		||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 | 
			
		||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 | 
			
		||||
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
 | 
			
		||||
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
 | 
			
		||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
 | 
			
		||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
 | 
			
		||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
 | 
			
		||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
 | 
			
		||||
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
 | 
			
		||||
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
 | 
			
		||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
 | 
			
		||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 | 
			
		||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
 | 
			
		||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
 | 
			
		||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 | 
			
		||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 | 
			
		||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
 | 
			
		||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
			
		||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
			
		||||
@@ -152,7 +186,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
			
		||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 | 
			
		||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 | 
			
		||||
github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00=
 | 
			
		||||
@@ -163,8 +198,8 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
 | 
			
		||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
 | 
			
		||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 | 
			
		||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
			
		||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
 | 
			
		||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
 | 
			
		||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
 | 
			
		||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
 | 
			
		||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
 | 
			
		||||
@@ -187,25 +222,29 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
 | 
			
		||||
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=
 | 
			
		||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
 | 
			
		||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
 | 
			
		||||
golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
 | 
			
		||||
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
 | 
			
		||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
 | 
			
		||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
 | 
			
		||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
 | 
			
		||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
 | 
			
		||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
 | 
			
		||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
 | 
			
		||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
 | 
			
		||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
 | 
			
		||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
 | 
			
		||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
 | 
			
		||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
 | 
			
		||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
 | 
			
		||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
 | 
			
		||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
@@ -221,22 +260,22 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
 | 
			
		||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
			
		||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
 | 
			
		||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
 | 
			
		||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
 | 
			
		||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
			
		||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
 | 
			
		||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
 | 
			
		||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
 | 
			
		||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
 | 
			
		||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
 | 
			
		||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
 | 
			
		||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
 | 
			
		||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
 | 
			
		||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
 | 
			
		||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
@@ -247,8 +286,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:
 | 
			
		||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 | 
			
		||||
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.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
 | 
			
		||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
 | 
			
		||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
 | 
			
		||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 | 
			
		||||
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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,7 @@ func TestContainerIntegration(t *testing.T) {
 | 
			
		||||
				"8000:8000",
 | 
			
		||||
				"8001:8001",
 | 
			
		||||
				"8001:8001/udp",
 | 
			
		||||
				"9100:9100",
 | 
			
		||||
				"53531:53/udp",
 | 
			
		||||
			},
 | 
			
		||||
			Cmd: []string{
 | 
			
		||||
@@ -110,6 +111,7 @@ func TestContainerIntegration(t *testing.T) {
 | 
			
		||||
				"-trusted-header", "X-Real-IP",
 | 
			
		||||
				"-enable-secure-headers",
 | 
			
		||||
				"-enable-http3",
 | 
			
		||||
				"-metrics-bind", ":9100",
 | 
			
		||||
				"-resolver", "/resolver.yml",
 | 
			
		||||
			},
 | 
			
		||||
			Files: []tc.ContainerFile{
 | 
			
		||||
@@ -232,6 +234,25 @@ func TestContainerIntegration(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("RequestMetricsEndpoint", func(t *testing.T) {
 | 
			
		||||
		req, err := http.NewRequest("GET", "http://localhost:9100/metrics", nil)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		client := &http.Client{}
 | 
			
		||||
		resp, err := client.Do(req)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, http.StatusOK, resp.StatusCode)
 | 
			
		||||
 | 
			
		||||
		body, err := io.ReadAll(resp.Body)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		bodyStr := string(body)
 | 
			
		||||
 | 
			
		||||
		assert.Contains(t, bodyStr, "whatismyip_http_requests_total")
 | 
			
		||||
		assert.Contains(t, bodyStr, "whatismyip_http_request_duration_seconds")
 | 
			
		||||
		assert.Contains(t, bodyStr, "# HELP")
 | 
			
		||||
		assert.Contains(t, bodyStr, "# TYPE")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	testWhatIsMyDNS(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										122
									
								
								internal/metrics/metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								internal/metrics/metrics.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
package metrics
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/prometheus/client_golang/prometheus"
 | 
			
		||||
	"github.com/prometheus/client_golang/prometheus/promauto"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	enabled          bool
 | 
			
		||||
	initOnce         sync.Once
 | 
			
		||||
	requestsTotal    *prometheus.CounterVec
 | 
			
		||||
	requestDuration  *prometheus.HistogramVec
 | 
			
		||||
	requestsInFlight prometheus.Gauge
 | 
			
		||||
	geoLookups       *prometheus.CounterVec
 | 
			
		||||
	portScans        prometheus.Counter
 | 
			
		||||
	dnsQueries       *prometheus.CounterVec
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Enable() {
 | 
			
		||||
	initOnce.Do(func() {
 | 
			
		||||
		enabled = true
 | 
			
		||||
 | 
			
		||||
		requestsTotal = promauto.NewCounterVec(
 | 
			
		||||
			prometheus.CounterOpts{
 | 
			
		||||
				Name: "whatismyip_http_requests_total",
 | 
			
		||||
				Help: "Total number of HTTP requests",
 | 
			
		||||
			},
 | 
			
		||||
			[]string{"method", "path", "status"},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		requestDuration = promauto.NewHistogramVec(
 | 
			
		||||
			prometheus.HistogramOpts{
 | 
			
		||||
				Name:    "whatismyip_http_request_duration_seconds",
 | 
			
		||||
				Help:    "HTTP request latency in seconds",
 | 
			
		||||
				Buckets: prometheus.DefBuckets,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{"method", "path"},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		requestsInFlight = promauto.NewGauge(
 | 
			
		||||
			prometheus.GaugeOpts{
 | 
			
		||||
				Name: "whatismyip_http_requests_in_flight",
 | 
			
		||||
				Help: "Current number of HTTP requests being processed",
 | 
			
		||||
			},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		geoLookups = promauto.NewCounterVec(
 | 
			
		||||
			prometheus.CounterOpts{
 | 
			
		||||
				Name: "whatismyip_geo_lookups_total",
 | 
			
		||||
				Help: "Total number of geo lookups",
 | 
			
		||||
			},
 | 
			
		||||
			[]string{"type"},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		portScans = promauto.NewCounter(
 | 
			
		||||
			prometheus.CounterOpts{
 | 
			
		||||
				Name: "whatismyip_port_scans_total",
 | 
			
		||||
				Help: "Total number of port scan requests",
 | 
			
		||||
			},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		dnsQueries = promauto.NewCounterVec(
 | 
			
		||||
			prometheus.CounterOpts{
 | 
			
		||||
				Name: "whatismyip_dns_queries_total",
 | 
			
		||||
				Help: "Total number of DNS queries",
 | 
			
		||||
			},
 | 
			
		||||
			[]string{"query_type", "rcode"},
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GinMiddleware() gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		if !enabled {
 | 
			
		||||
			c.Next()
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		path := c.FullPath()
 | 
			
		||||
		if path == "" {
 | 
			
		||||
			path = "/404" // group 404s
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		requestsInFlight.Inc()
 | 
			
		||||
		defer requestsInFlight.Dec()
 | 
			
		||||
 | 
			
		||||
		c.Next()
 | 
			
		||||
 | 
			
		||||
		duration := time.Since(start).Seconds()
 | 
			
		||||
		status := c.Writer.Status()
 | 
			
		||||
 | 
			
		||||
		requestsTotal.WithLabelValues(c.Request.Method, path, fmt.Sprintf("%dxx", status/100)).Inc()
 | 
			
		||||
		requestDuration.WithLabelValues(c.Request.Method, path).Observe(duration)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RecordGeoLookup(lookupType string) {
 | 
			
		||||
	if !enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	geoLookups.WithLabelValues(lookupType).Inc()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RecordPortScan() {
 | 
			
		||||
	if !enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	portScans.Inc()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RecordDNSQuery(queryType string, rcode string) {
 | 
			
		||||
	if !enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dnsQueries.WithLabelValues(queryType, rcode).Inc()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										200
									
								
								internal/metrics/metrics_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								internal/metrics/metrics_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
			
		||||
package metrics
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/prometheus/client_golang/prometheus/testutil"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDisabledMetrics_Middleware(t *testing.T) {
 | 
			
		||||
	if enabled {
 | 
			
		||||
		t.Skip("Skipping disabled test - metrics already enabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gin.SetMode(gin.TestMode)
 | 
			
		||||
	w := httptest.NewRecorder()
 | 
			
		||||
	c, _ := gin.CreateTestContext(w)
 | 
			
		||||
 | 
			
		||||
	c.Request = httptest.NewRequest("GET", "/test", nil)
 | 
			
		||||
 | 
			
		||||
	middleware := GinMiddleware()
 | 
			
		||||
 | 
			
		||||
	assert.NotPanics(t, func() {
 | 
			
		||||
		middleware(c)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDisabledMetrics_GeoLookup(t *testing.T) {
 | 
			
		||||
	if enabled {
 | 
			
		||||
		t.Skip("Skipping disabled test - metrics already enabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.NotPanics(t, func() {
 | 
			
		||||
		RecordGeoLookup("city")
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDisabledMetrics_PortScan(t *testing.T) {
 | 
			
		||||
	if enabled {
 | 
			
		||||
		t.Skip("Skipping disabled test - metrics already enabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.NotPanics(t, func() {
 | 
			
		||||
		RecordPortScan()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDisabledMetrics_DNSQuery(t *testing.T) {
 | 
			
		||||
	if enabled {
 | 
			
		||||
		t.Skip("Skipping disabled test - metrics already enabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.NotPanics(t, func() {
 | 
			
		||||
		RecordDNSQuery("A", "NOERROR")
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnable(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	assert.True(t, enabled, "Enable() should set enabled to true")
 | 
			
		||||
	assert.NotNil(t, requestsTotal, "requestsTotal should be initialized")
 | 
			
		||||
	assert.NotNil(t, requestDuration, "requestDuration should be initialized")
 | 
			
		||||
	assert.NotNil(t, requestsInFlight, "requestsInFlight should be initialized")
 | 
			
		||||
	assert.NotNil(t, geoLookups, "geoLookups should be initialized")
 | 
			
		||||
	assert.NotNil(t, portScans, "portScans should be initialized")
 | 
			
		||||
	assert.NotNil(t, dnsQueries, "dnsQueries should be initialized")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnableIdempotent(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
	firstRequestsTotal := requestsTotal
 | 
			
		||||
 | 
			
		||||
	Enable()
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, firstRequestsTotal, requestsTotal, "Enable() should be idempotent")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGinMiddleware_StatusCategories(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		status   int
 | 
			
		||||
		category string
 | 
			
		||||
	}{
 | 
			
		||||
		{200, "2xx"},
 | 
			
		||||
		{201, "2xx"},
 | 
			
		||||
		{301, "3xx"},
 | 
			
		||||
		{404, "4xx"},
 | 
			
		||||
		{500, "5xx"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		gin.SetMode(gin.TestMode)
 | 
			
		||||
		router := gin.New()
 | 
			
		||||
		router.Use(GinMiddleware())
 | 
			
		||||
		router.GET("/test-status", func(c *gin.Context) {
 | 
			
		||||
			c.Status(tc.status)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		initialCount := testutil.ToFloat64(requestsTotal.WithLabelValues("GET", "/test-status", tc.category))
 | 
			
		||||
 | 
			
		||||
		w := httptest.NewRecorder()
 | 
			
		||||
		req := httptest.NewRequest("GET", "/test-status", nil)
 | 
			
		||||
		router.ServeHTTP(w, req)
 | 
			
		||||
 | 
			
		||||
		count := testutil.ToFloat64(requestsTotal.WithLabelValues("GET", "/test-status", tc.category))
 | 
			
		||||
		assert.Equal(t, initialCount+1, count, "Expected count for category %s to increase by 1", tc.category)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGinMiddleware_404(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	gin.SetMode(gin.TestMode)
 | 
			
		||||
	router := gin.New()
 | 
			
		||||
	router.Use(GinMiddleware())
 | 
			
		||||
 | 
			
		||||
	initialCount := testutil.ToFloat64(requestsTotal.WithLabelValues("GET", "/404", "4xx"))
 | 
			
		||||
 | 
			
		||||
	w := httptest.NewRecorder()
 | 
			
		||||
	req := httptest.NewRequest("GET", "/nonexistent-path", nil)
 | 
			
		||||
	router.ServeHTTP(w, req)
 | 
			
		||||
 | 
			
		||||
	count := testutil.ToFloat64(requestsTotal.WithLabelValues("GET", "/404", "4xx"))
 | 
			
		||||
	assert.Equal(t, initialCount+1, count, "Expected count to increase by 1 for empty path (404)")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGinMiddleware_RecordsDuration(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	gin.SetMode(gin.TestMode)
 | 
			
		||||
	router := gin.New()
 | 
			
		||||
	router.Use(GinMiddleware())
 | 
			
		||||
	router.GET("/test-duration", func(c *gin.Context) {
 | 
			
		||||
		c.Status(http.StatusOK)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	w := httptest.NewRecorder()
 | 
			
		||||
	req := httptest.NewRequest("GET", "/test-duration", nil)
 | 
			
		||||
	router.ServeHTTP(w, req)
 | 
			
		||||
 | 
			
		||||
	metric := requestDuration.WithLabelValues("GET", "/test-duration")
 | 
			
		||||
	assert.NotNil(t, metric, "Expected histogram metric to exist")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecordGeoLookup(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	initialCityCount := testutil.ToFloat64(geoLookups.WithLabelValues("city"))
 | 
			
		||||
	initialCountryCount := testutil.ToFloat64(geoLookups.WithLabelValues("asn"))
 | 
			
		||||
 | 
			
		||||
	RecordGeoLookup("city")
 | 
			
		||||
	RecordGeoLookup("city")
 | 
			
		||||
	RecordGeoLookup("asn")
 | 
			
		||||
 | 
			
		||||
	cityCount := testutil.ToFloat64(geoLookups.WithLabelValues("city"))
 | 
			
		||||
	assert.Equal(t, initialCityCount+2, cityCount, "Expected city lookups to increase by 2")
 | 
			
		||||
 | 
			
		||||
	countryCount := testutil.ToFloat64(geoLookups.WithLabelValues("asn"))
 | 
			
		||||
	assert.Equal(t, initialCountryCount+1, countryCount, "Expected country lookups to increase by 1")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecordPortScan(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	initialCount := testutil.ToFloat64(portScans)
 | 
			
		||||
 | 
			
		||||
	RecordPortScan()
 | 
			
		||||
	RecordPortScan()
 | 
			
		||||
 | 
			
		||||
	count := testutil.ToFloat64(portScans)
 | 
			
		||||
	assert.Equal(t, initialCount+2, count, "Expected port scans to increase by 2")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecordDNSQuery(t *testing.T) {
 | 
			
		||||
	Enable()
 | 
			
		||||
 | 
			
		||||
	initialACount := testutil.ToFloat64(dnsQueries.WithLabelValues("A", "NOERROR"))
 | 
			
		||||
	initialAAAACount := testutil.ToFloat64(dnsQueries.WithLabelValues("AAAA", "NOERROR"))
 | 
			
		||||
	initialNXDOMAINCount := testutil.ToFloat64(dnsQueries.WithLabelValues("A", "NXDOMAIN"))
 | 
			
		||||
 | 
			
		||||
	RecordDNSQuery("A", "NOERROR")
 | 
			
		||||
	RecordDNSQuery("A", "NOERROR")
 | 
			
		||||
	RecordDNSQuery("AAAA", "NOERROR")
 | 
			
		||||
	RecordDNSQuery("A", "NXDOMAIN")
 | 
			
		||||
 | 
			
		||||
	aCount := testutil.ToFloat64(dnsQueries.WithLabelValues("A", "NOERROR"))
 | 
			
		||||
	assert.Equal(t, initialACount+2, aCount, "Expected A NOERROR queries to increase by 2")
 | 
			
		||||
 | 
			
		||||
	aaaaCount := testutil.ToFloat64(dnsQueries.WithLabelValues("AAAA", "NOERROR"))
 | 
			
		||||
	assert.Equal(t, initialAAAACount+1, aaaaCount, "Expected AAAA NOERROR queries to increase by 1")
 | 
			
		||||
 | 
			
		||||
	nxdomainCount := testutil.ToFloat64(dnsQueries.WithLabelValues("A", "NXDOMAIN"))
 | 
			
		||||
	assert.Equal(t, initialNXDOMAINCount+1, nxdomainCount, "Expected A NXDOMAIN queries to increase by 1")
 | 
			
		||||
}
 | 
			
		||||
@@ -37,6 +37,7 @@ type settings struct {
 | 
			
		||||
	TLSAddress          string
 | 
			
		||||
	TLSCrtPath          string
 | 
			
		||||
	TLSKeyPath          string
 | 
			
		||||
	PrometheusAddress   string
 | 
			
		||||
	TrustedHeader       string
 | 
			
		||||
	TrustedPortHeader   string
 | 
			
		||||
	EnableSecureHeaders bool
 | 
			
		||||
@@ -87,6 +88,12 @@ func Setup(args []string) (output string, err error) {
 | 
			
		||||
	)
 | 
			
		||||
	flags.StringVar(&App.TLSCrtPath, "tls-crt", "", "When using TLS, path to certificate file")
 | 
			
		||||
	flags.StringVar(&App.TLSKeyPath, "tls-key", "", "When using TLS, path to private key file")
 | 
			
		||||
	flags.StringVar(
 | 
			
		||||
		&App.PrometheusAddress,
 | 
			
		||||
		"metrics-bind",
 | 
			
		||||
		"",
 | 
			
		||||
		"Listening address for Prometheus metrics endpoint (see https://pkg.go.dev/net?#Listen). It enables the metrics available at the given address/port via the /metrics endpoint.",
 | 
			
		||||
	)
 | 
			
		||||
	flags.StringVar(
 | 
			
		||||
		&App.TrustedHeader,
 | 
			
		||||
		"trusted-header",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/metrics"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/setting"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/validator/uuid"
 | 
			
		||||
	"github.com/miekg/dns"
 | 
			
		||||
@@ -59,6 +60,7 @@ func (rsv *Resolver) blackHole(w dns.ResponseWriter, r *dns.Msg) {
 | 
			
		||||
	msg.SetRcode(r, dns.RcodeRefused)
 | 
			
		||||
	w.WriteMsg(msg)
 | 
			
		||||
	logger(w, r.Question[0], msg.Rcode)
 | 
			
		||||
	metrics.RecordDNSQuery(dns.TypeToString[r.Question[0].Qtype], dns.RcodeToString[msg.Rcode])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rsv *Resolver) resolve(w dns.ResponseWriter, r *dns.Msg) {
 | 
			
		||||
@@ -78,6 +80,7 @@ func (rsv *Resolver) resolve(w dns.ResponseWriter, r *dns.Msg) {
 | 
			
		||||
				logger(w, q, msg.Rcode)
 | 
			
		||||
			}
 | 
			
		||||
			w.WriteMsg(msg)
 | 
			
		||||
			metrics.RecordDNSQuery(dns.TypeToString[q.Qtype], dns.RcodeToString[msg.Rcode])
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -96,6 +99,7 @@ func (rsv *Resolver) resolve(w dns.ResponseWriter, r *dns.Msg) {
 | 
			
		||||
 | 
			
		||||
	w.WriteMsg(msg)
 | 
			
		||||
	logger(w, q, msg.Rcode)
 | 
			
		||||
	metrics.RecordDNSQuery(dns.TypeToString[q.Qtype], dns.RcodeToString[msg.Rcode])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rsv *Resolver) getIP(question dns.Question, msg *dns.Msg) int {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								server/prometheus.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								server/prometheus.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/setting"
 | 
			
		||||
	"github.com/prometheus/client_golang/prometheus/promhttp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Prometheus struct {
 | 
			
		||||
	server *http.Server
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewPrometheusServer(ctx context.Context) *Prometheus {
 | 
			
		||||
	return &Prometheus{
 | 
			
		||||
		ctx: ctx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Prometheus) Start() {
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
	mux.Handle("/metrics", promhttp.Handler())
 | 
			
		||||
 | 
			
		||||
	p.server = &http.Server{
 | 
			
		||||
		Addr:         setting.App.PrometheusAddress,
 | 
			
		||||
		Handler:      mux,
 | 
			
		||||
		ReadTimeout:  setting.App.Server.ReadTimeout,
 | 
			
		||||
		WriteTimeout: setting.App.Server.WriteTimeout,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Printf("Starting Prometheus server listening on %s", setting.App.PrometheusAddress)
 | 
			
		||||
	go func() {
 | 
			
		||||
		if err := p.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Prometheus) Stop() {
 | 
			
		||||
	log.Print("Stopping Prometheus server...")
 | 
			
		||||
	if err := p.server.Shutdown(p.ctx); err != nil {
 | 
			
		||||
		log.Printf("Prometheus server forced to shutdown: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/metrics"
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +42,7 @@ func (g *Geo) LookUpCity(ip net.IP) *models.GeoRecord {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	metrics.RecordGeoLookup("city")
 | 
			
		||||
	return record
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -51,6 +53,7 @@ func (g *Geo) LookUpASN(ip net.IP) *models.ASNRecord {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	metrics.RecordGeoLookup("asn")
 | 
			
		||||
	return record
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package service
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/dcarrillo/whatismyip/internal/metrics"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const scannerTimeOut = 3 * time.Second
 | 
			
		||||
@@ -20,5 +22,6 @@ func (p *PortScanner) IsPortOpen() (bool, error) {
 | 
			
		||||
		defer conn.Close()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	metrics.RecordPortScan()
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user