From ebfd1fe9f1fa0b5c48d55a57e256f9cfac7379b8 Mon Sep 17 00:00:00 2001 From: JurajKubrican Date: Fri, 15 Aug 2025 16:03:01 +0200 Subject: [PATCH] metrics --- .env.example | 13 ++++++++ docker-compose.yml | 58 +++++++++++++++++++++++++++++++++ docs/grafana-integration.md | 46 ++++++++++++++++++++++++++ go.mod | 13 ++------ go.sum | 26 +++++---------- prometheus.yml | 65 +++++++++++++++++++++++++++++++++++++ src/main.go | 8 +++-- 7 files changed, 198 insertions(+), 31 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.yml create mode 100644 prometheus.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..efc7639 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Environment variables for knet monitoring stack + +# Application settings +API_TOKEN=your-secure-api-token-here +API_USERNAME=api +BUILD_NUMBER=prod-v1.0 + +# Grafana settings +GRAFANA_PASSWORD=admin + +# Optional: Custom Prometheus retention +# PROMETHEUS_RETENTION_TIME=30d +# PROMETHEUS_RETENTION_SIZE=10GB diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f82d2dc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +version: '3.8' + +services: + knet: + build: . + ports: + - "54321:54321" + environment: + - API_TOKEN=${API_TOKEN:-your-default-token} + - API_USERNAME=${API_USERNAME:-api} + - BUILD_NUMBER=${BUILD_NUMBER:-dev} + networks: + - monitoring + + prometheus: + image: prom/prometheus:latest + container_name: knet-prometheus + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--storage.tsdb.retention.time=30d' + - '--storage.tsdb.retention.size=10GB' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--web.enable-lifecycle' + - '--web.enable-admin-api' + networks: + - monitoring + restart: unless-stopped + + grafana: + image: grafana/grafana:latest + container_name: knet-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin} + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + networks: + - monitoring + restart: unless-stopped + depends_on: + - prometheus + +networks: + monitoring: + driver: bridge + +volumes: + prometheus_data: + grafana_data: diff --git a/docs/grafana-integration.md b/docs/grafana-integration.md index bf7be3f..ad8f095 100644 --- a/docs/grafana-integration.md +++ b/docs/grafana-integration.md @@ -116,6 +116,52 @@ export BUILD_NUMBER="prod-v1.0" # Enables production mode 5. **Alerting Ready** - Use Prometheus AlertManager 6. **Ecosystem** - Tons of existing dashboards and tools +## Persistent Storage Setup + +### Option 1: Docker Compose (Recommended) + +For persistent metrics storage, run a Prometheus server alongside your app: + +```bash +# 1. Copy your API token to .env +cp .env.example .env +# Edit .env and set your API_TOKEN + +# 2. Start the full monitoring stack +docker-compose up -d + +# 3. Access services +# - Your app: http://localhost:54321 +# - Prometheus: http://localhost:9090 +# - Grafana: http://localhost:3000 (admin/admin) +``` + +**What this gives you:** +- ✅ **Persistent metrics** - Data survives restarts +- ✅ **30-day retention** - Configurable in docker-compose.yml +- ✅ **Grafana pre-configured** - Points to Prometheus automatically +- ✅ **Production ready** - Proper volumes and networking + +### Option 2: Standalone Prometheus + +If you prefer to run Prometheus separately: + +```bash +# 1. Update prometheus.yml with your API token +# 2. Start Prometheus +prometheus --config.file=prometheus.yml --storage.tsdb.path=./prometheus_data + +# 3. Configure Grafana to point to http://localhost:9090 +``` + +### Grafana Data Source Configuration + +With persistent Prometheus: +1. **Add Prometheus Data Source** in Grafana +2. **Set URL** to: `http://prometheus:9090` (Docker) or `http://localhost:9090` (standalone) +3. **No authentication needed** - Prometheus handles the API token +4. **Query retention**: Up to 30 days of historical data + ## Quick Start 1. **Start your server:** `./tmp/main` diff --git a/go.mod b/go.mod index 828daea..d428ee8 100644 --- a/go.mod +++ b/go.mod @@ -3,36 +3,27 @@ module knet.sk go 1.24.0 require ( + github.com/labstack/echo-contrib v0.17.4 github.com/labstack/echo/v4 v4.13.4 github.com/labstack/gommon v0.4.2 + github.com/prometheus/client_golang v1.23.0 golang.org/x/net v0.42.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/labstack/echo-contrib v0.17.4 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.40.0 // indirect - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/protobuf v1.36.6 // indirect - modernc.org/libc v1.66.3 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.38.2 // indirect ) diff --git a/go.sum b/go.sum index 1cf2c98..03561b1 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= @@ -20,8 +22,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= @@ -32,18 +32,16 @@ github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +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.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -57,11 +55,3 @@ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9x google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= -modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= -modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/prometheus.yml b/prometheus.yml new file mode 100644 index 0000000..651b6c6 --- /dev/null +++ b/prometheus.yml @@ -0,0 +1,65 @@ +# Prometheus configuration for knet application +global: + scrape_interval: 15s # How often to scrape targets + evaluation_interval: 15s # How often to evaluate rules + + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). + external_labels: + monitor: 'knet-monitor' + +# Alertmanager configuration (optional) +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: + # - "first_rules.yml" + # - "second_rules.yml" + +# A scrape configuration containing exactly one endpoint to scrape: +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'knet-app' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + # How often to scrape this target + scrape_interval: 15s + + # Timeout for scraping + scrape_timeout: 10s + + # Basic authentication for protected endpoints + basic_auth: + username: 'api' + password: 'your-default-token' # This should match your API_TOKEN env var + + static_configs: + - targets: ['knet:54321'] # Your knet application + labels: + instance: 'knet-prod' + environment: 'production' + + # Only scrape metrics endpoint + metrics_path: /metrics + + # Optional: Add custom headers + # headers: + # X-Custom-Header: value + + # Optional: Monitor Prometheus itself + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + +# Storage configuration +# Prometheus stores data in ./data by default +# You can customize with command line flags: +# --storage.tsdb.path=/prometheus +# --storage.tsdb.retention.time=15d +# --storage.tsdb.retention.size=10GB diff --git a/src/main.go b/src/main.go index b42ffb8..837b27f 100644 --- a/src/main.go +++ b/src/main.go @@ -92,8 +92,12 @@ func main() { e.Logger.SetLevel(log.DEBUG) e.Use(middleware.Logger()) if util.IsProd() { - e.Use(middleware.Gzip()) - e.Use(middleware.HTTPSRedirect()) + // Apply gzip compression but skip it for /metrics endpoint + e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Skipper: func(c echo.Context) bool { + return c.Request().URL.Path == "/metrics" + }, + })) } // Prometheus metrics endpoint (standard /metrics)