diff --git a/README.md b/README.md index 71c919b..fb71361 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,43 @@ Configuration is managed through `.env`. Environment variables can override thes API documentation is generated using Swagger. The documentation is available at `http://localhost:8080/swagger/index.html`. +## 📊 Monitoring & Observability + +The template includes a comprehensive monitoring stack with Grafana and Prometheus: + +### Monitoring Stack + +- **Prometheus**: Time-series database for metrics collection +- **Grafana**: Visualization and dashboard platform +- **Jaeger**: Distributed tracing +- **Application Metrics**: Built-in Prometheus metrics middleware + +### Quick Start for Monitoring + +1. Start the monitoring stack: + +```bash +make docker_up +``` + +2. Access the monitoring tools: + +- **Grafana**: http://localhost:3000 (admin/admin) +- **Prometheus**: http://localhost:9090 +- **Jaeger**: http://localhost:16686 +- **Application Metrics**: http://localhost:8080/metrics + +### Pre-configured Dashboards + +The template includes a pre-configured Grafana dashboard with: + +- Request rate and response time metrics +- Error rates by status code +- HTTP method distribution +- 95th percentile response times + +For detailed monitoring setup instructions, see [monitoring/README.md](monitoring/README.md). + ## Prometheus Metrics Prometheus metrics are exposed at `http://localhost:8080/metrics`. diff --git a/docker-compose.yml b/docker-compose.yml index 0765944..25aae7d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,6 +108,50 @@ services: networks: - app_network + # ─────────────────────────────────────────────────────────── + # 🌟 PROMETHEUS + # ─────────────────────────────────────────────────────────── + prometheus: + image: prom/prometheus:latest + container_name: prometheus + restart: always + ports: + - "9090:9090" + volumes: + - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--storage.tsdb.retention.time=200h' + - '--web.enable-lifecycle' + networks: + - app_network + + # ─────────────────────────────────────────────────────────── + # 🌟 GRAFANA + # ─────────────────────────────────────────────────────────── + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: always + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards + depends_on: + - prometheus + networks: + - app_network + # ─────────────────────────────────────────────────────────── # 🌟 NETWORK & VOLUME CONFIGURATION @@ -118,3 +162,5 @@ networks: volumes: db_data: redis_data: + prometheus_data: + grafana_data: diff --git a/monitoring/README.md b/monitoring/README.md new file mode 100644 index 0000000..58b17ba --- /dev/null +++ b/monitoring/README.md @@ -0,0 +1,189 @@ +# Monitoring Setup with Grafana and Prometheus + +This directory contains the monitoring configuration for the Go REST API template using Grafana and Prometheus. + +## Overview + +The monitoring stack consists of: + +- **Prometheus**: Time-series database for storing metrics +- **Grafana**: Visualization and dashboard platform +- **Go Application**: Exposes metrics at `/metrics` endpoint + +## Architecture + +```sh +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Grafana │◄───│ Prometheus │◄───│ Go App │ +│ :3000 │ │ :9090 │ │ :8080 │ +└─────────────┘ └─────────────┘ └─────────────┘ +``` + +## Metrics Collected + +The Go application exposes the following Prometheus metrics: + +- `http_requests_total`: Total number of HTTP requests (counter) +- `http_request_duration_seconds`: HTTP request duration (histogram) + +These metrics are labeled with: + +- `code`: HTTP status code +- `method`: HTTP method (GET, POST, etc.) +- `path`: Request path + +## Getting Started + +### 1. Start the Monitoring Stack + +```bash +# Start all services including monitoring +docker-compose up -d + +# Or start only monitoring services +docker-compose up -d prometheus grafana +``` + +### 2. Access the Services + +- **Grafana**: http://localhost:3000 + - Username: `admin` + - Password: `admin` +- **Prometheus**: http://localhost:9090 +- **Go Application**: http://localhost:8080 +- **Application Metrics**: http://localhost:8080/metrics + +### 3. Import Dashboard + +The Go REST API dashboard is automatically provisioned and includes: + +- Request rate over time +- 95th percentile response time +- Success/error rates by status code +- Requests by HTTP method +- Requests by status code + +## Configuration Files + +### Prometheus Configuration (`monitoring/prometheus/prometheus.yml`) + +- Scrapes metrics from the Go application every 5 seconds +- Stores data for 200 hours +- Includes Redis monitoring (optional) + +### Grafana Configuration + +- **Datasources**: Automatically configured to connect to Prometheus +- **Dashboards**: Pre-configured dashboard for Go REST API monitoring +- **Provisioning**: Automatic setup of datasources and dashboards + +## Customization + +### Adding Custom Metrics + +To add custom metrics to your Go application: + +```go +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + customCounter = promauto.NewCounter(prometheus.CounterOpts{ + Name: "my_custom_counter", + Help: "Description of my custom counter", + }) +) +``` + +### Creating Custom Dashboards + +1. Access Grafana at http://localhost:3000 +2. Create a new dashboard +3. Add panels with Prometheus queries +4. Export the dashboard JSON and place it in `monitoring/grafana/dashboards/` + +### Alerting + +To set up alerts: + +1. Configure alerting rules in Prometheus +2. Set up alert manager +3. Configure notification channels in Grafana + +## Useful Prometheus Queries + +### Request Rate + +```promql +rate(http_requests_total[5m]) +``` + +### Error Rate + +```promql +rate(http_requests_total{code=~"5.."}[5m]) +``` + +### 95th Percentile Response Time + +```promql +histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) +``` + +### Success Rate + +```promql +sum(rate(http_requests_total{code=~"2.."}[5m])) / sum(rate(http_requests_total[5m])) +``` + +## Troubleshooting + +### Prometheus Not Scraping Metrics + +1. Check if the Go application is running: `docker-compose ps` +2. Verify metrics endpoint: `curl http://localhost:8080/metrics` +3. Check Prometheus targets: http://localhost:9090/targets + +### Grafana Can't Connect to Prometheus + +1. Verify Prometheus is running: `docker-compose ps prometheus` +2. Check Prometheus logs: `docker-compose logs prometheus` +3. Verify network connectivity between containers + +### Dashboard Not Loading + +1. Check if the dashboard JSON is valid +2. Verify the datasource is properly configured +3. Check Grafana logs: `docker-compose logs grafana` + +## Production Considerations + +### Security + +- Change default Grafana credentials +- Use environment variables for sensitive configuration +- Consider using reverse proxy with authentication +- Enable HTTPS for production deployments + +### Performance + +- Adjust Prometheus retention period based on storage requirements +- Configure appropriate scrape intervals +- Monitor Prometheus and Grafana resource usage +- Consider using Prometheus federation for large deployments + +### High Availability + +- Use external storage for Prometheus data +- Set up Prometheus federation +- Configure Grafana with external database +- Use load balancers for multiple instances + +## Additional Resources + +- [Prometheus Documentation](https://prometheus.io/docs/) +- [Grafana Documentation](https://grafana.com/docs/) +- [Prometheus Client Go](https://github.com/prometheus/client_golang) +- [Grafana Dashboards](https://grafana.com/grafana/dashboards/) diff --git a/monitoring/grafana/dashboards/golang-rest-api-dashboard.json b/monitoring/grafana/dashboards/golang-rest-api-dashboard.json new file mode 100644 index 0000000..64a50c6 --- /dev/null +++ b/monitoring/grafana/dashboards/golang-rest-api-dashboard.json @@ -0,0 +1,593 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "vis": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "title": "Request Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "vis": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))", + "refId": "A" + } + ], + "title": "95th Percentile Response Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(http_requests_total{code=~\"2..\"}[5m]))", + "refId": "A" + } + ], + "title": "Success Rate (2xx)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 8 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(http_requests_total{code=~\"4..\"}[5m]))", + "refId": "A" + } + ], + "title": "Client Error Rate (4xx)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 8 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(http_requests_total{code=~\"5..\"}[5m]))", + "refId": "A" + } + ], + "title": "Server Error Rate (5xx)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 8 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(http_requests_total{code=\"404\"}[5m]))", + "refId": "A" + } + ], + "title": "404 Error Rate", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "vis": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(http_requests_total[5m])) by (method)", + "refId": "A" + } + ], + "title": "Requests by HTTP Method", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "vis": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 8, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(http_requests_total[5m])) by (code)", + "refId": "A" + } + ], + "title": "Requests by Status Code", + "type": "piechart" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "golang", + "rest-api", + "monitoring" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Go REST API Dashboard", + "uid": "golang-rest-api", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/monitoring/grafana/provisioning/dashboards/dashboard.yml b/monitoring/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000..5c88846 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /var/lib/grafana/dashboards \ No newline at end of file diff --git a/monitoring/grafana/provisioning/datasources/prometheus.yml b/monitoring/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..38fb02c --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: true \ No newline at end of file diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000..99a1344 --- /dev/null +++ b/monitoring/prometheus/prometheus.yml @@ -0,0 +1,31 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + # - "first_rules.yml" + # - "second_rules.yml" + +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ['localhost:9090'] + + # Scrape the Go application metrics + - job_name: 'golang-rest-api' + static_configs: + - targets: ['app:8080'] + metrics_path: '/metrics' + scrape_interval: 5s + scrape_timeout: 3s + + # Scrape Redis metrics (if you want to monitor Redis) + - job_name: 'redis' + static_configs: + - targets: ['redis:6379'] + scrape_interval: 10s \ No newline at end of file diff --git a/scripts/test-monitoring.sh b/scripts/test-monitoring.sh new file mode 100755 index 0000000..5618fe7 --- /dev/null +++ b/scripts/test-monitoring.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# Test script for monitoring setup +# This script verifies that Prometheus and Grafana are working correctly + +set -e + +echo "🔍 Testing Monitoring Setup..." +echo "================================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to check if a service is responding +check_service() { + local name=$1 + local url=$2 + local timeout=${3:-10} + + echo -n "Checking $name... " + + if curl -s --max-time $timeout "$url" > /dev/null 2>&1; then + echo -e "${GREEN}✓ OK${NC}" + return 0 + else + echo -e "${RED}✗ FAILED${NC}" + return 1 + fi +} + +# Function to check if metrics endpoint is accessible +check_metrics() { + echo -n "Checking application metrics endpoint... " + + if curl -s --max-time 10 "http://localhost:8080/metrics" | grep -q "http_requests_total"; then + echo -e "${GREEN}✓ OK${NC}" + return 0 + else + echo -e "${RED}✗ FAILED${NC}" + return 1 + fi +} + +# Function to check if Prometheus is scraping metrics +check_prometheus_targets() { + echo -n "Checking Prometheus targets... " + + if curl -s --max-time 10 "http://localhost:9090/api/v1/targets" | grep -q "UP"; then + echo -e "${GREEN}✓ OK${NC}" + return 0 + else + echo -e "${YELLOW}⚠ WARNING - No UP targets found${NC}" + return 1 + fi +} + +# Function to check if Grafana datasource is configured +check_grafana_datasource() { + echo -n "Checking Grafana datasource... " + + # This is a basic check - in a real scenario you'd need authentication + if curl -s --max-time 10 "http://localhost:3000/api/health" | grep -q "ok"; then + echo -e "${GREEN}✓ OK${NC}" + return 0 + else + echo -e "${RED}✗ FAILED${NC}" + return 1 + fi +} + +# Main test execution +echo "Starting monitoring tests..." +echo "" + +# Check if services are running +echo "1. Service Health Checks:" +check_service "Go Application" "http://localhost:8080/health" +check_service "Prometheus" "http://localhost:9090" +check_service "Grafana" "http://localhost:3000" +check_service "Jaeger" "http://localhost:16686" + +echo "" +echo "2. Metrics and Monitoring:" +check_metrics +check_prometheus_targets +check_grafana_datasource + +echo "" +echo "3. Quick Metrics Sample:" +echo "Fetching current metrics from application..." +curl -s "http://localhost:8080/metrics" | grep -E "(http_requests_total|http_request_duration_seconds)" | head -5 + +echo "" +echo "4. Access URLs:" +echo -e "${YELLOW}Grafana Dashboard:${NC} http://localhost:3000 (admin/admin)" +echo -e "${YELLOW}Prometheus:${NC} http://localhost:9090" +echo -e "${YELLOW}Jaeger Tracing:${NC} http://localhost:16686" +echo -e "${YELLOW}Application Metrics:${NC} http://localhost:8080/metrics" +echo -e "${YELLOW}API Documentation:${NC} http://localhost:8080/swagger/" + +echo "" +echo "5. Useful Commands:" +echo -e "${YELLOW}View all containers:${NC} docker-compose ps" +echo -e "${YELLOW}View logs:${NC} docker-compose logs -f [service_name]" +echo -e "${YELLOW}Restart services:${NC} docker-compose restart" +echo -e "${YELLOW}Stop all:${NC} docker-compose down" + +echo "" +echo "✅ Monitoring setup test completed!" +echo "" +echo "📊 Next Steps:" +echo "1. Open Grafana at http://localhost:3000" +echo "2. Login with admin/admin" +echo "3. The Go REST API dashboard should be automatically loaded" +echo "4. Generate some traffic to see metrics: curl http://localhost:8080/api/v1/health" \ No newline at end of file