Skip to content

Export Cloudflare metrics to Prometheus. Built on Cloudflare Workers with Durable Objects for stateful metric accumulation.

License

Notifications You must be signed in to change notification settings

cloudflare/cloudflare-prometheus-exporter

Cloudflare Prometheus Exporter

Cloudflare Prometheus Exporter

Export Cloudflare metrics to Prometheus. Built on Cloudflare Workers with Durable Objects for stateful metric accumulation.

Deploy to Cloudflare Workers

Features

  • 58 Prometheus metrics - requests, bandwidth, threats, workers, load balancers, SSL certs, and more
  • Cloudflare Workers - serverless edge deployment
  • Durable Objects - stateful counter accumulation for proper Prometheus semantics
  • Background refresh - alarms fetch data every 60s; scrapes return cached data instantly
  • Rate limiting - 40 req/10s with exponential backoff
  • Multi-account - automatically discovers and exports all accessible accounts/zones
  • Runtime config API - change settings without redeployment via REST endpoints
  • Configurable - zone filtering, metric denylist, label exclusion, custom metrics path, and more

Quick Start

One-Click Deploy

Click the deploy button above. Configure CLOUDFLARE_API_TOKEN as a secret after deployment.

Manual Deployment

git clone https://github.com/cloudflare/cloudflare-prometheus-exporter.git
cd cloudflare-prometheus-exporter
bun install
wrangler secret put CLOUDFLARE_API_TOKEN
bun run deploy

Configuration

Configuration is resolved in order: KV overrides β†’ env vars β†’ defaults. Use the Runtime Config API for dynamic changes without redeployment.

Environment Variables

Set in wrangler.jsonc or via wrangler secret put:

Variable Default Description
CLOUDFLARE_API_TOKEN - Cloudflare API token (secret)
QUERY_LIMIT 10000 Max results per GraphQL query
SCRAPE_DELAY_SECONDS 300 Delay before fetching metrics (data propagation)
TIME_WINDOW_SECONDS 60 Query time window
METRIC_REFRESH_INTERVAL_SECONDS 60 Background refresh interval
LOG_LEVEL info Log level (debug/info/warn/error)
LOG_FORMAT json Log format (pretty/json)
ACCOUNT_LIST_CACHE_TTL_SECONDS 600 Account list cache TTL
ZONE_LIST_CACHE_TTL_SECONDS 1800 Zone list cache TTL
SSL_CERTS_CACHE_TTL_SECONDS 1800 SSL cert cache TTL
HEALTH_CHECK_CACHE_TTL_SECONDS 10 Health check cache TTL
EXCLUDE_HOST false Exclude host labels from metrics
CF_HTTP_STATUS_GROUP false Group HTTP status codes (2xx, 4xx, etc.)
DISABLE_UI false Disable landing page (returns 404)
DISABLE_CONFIG_API false Disable config API endpoints (returns 404)
METRICS_DENYLIST - Comma-separated list of metrics to exclude
CF_ACCOUNTS - Comma-separated account IDs to include (default: all)
CF_ZONES - Comma-separated zone IDs to include (default: all)
CF_FREE_TIER_ACCOUNTS - Comma-separated account IDs using free tier (skips paid-tier metrics)
METRICS_PATH /metrics Custom path for metrics endpoint

Creating an API Token

Quick setup: Create token with pre-filled permissions

Manual setup:

Permission Access Required
Zone > Analytics Read Yes
Account > Account Analytics Read Yes
Account > Workers Scripts Read Yes
Zone > SSL and Certificates Read Optional
Zone > Firewall Services Read Optional
Zone > Load Balancers Read Optional
Account > Logpush Read Optional
Account > Magic Transit Read Optional

Endpoints

Path Method Description
/ GET Landing page (disable: DISABLE_UI)
/metrics GET Prometheus metrics
/health GET Health check ({"status":"healthy"})
/config GET Get all runtime config (disable: DISABLE_CONFIG_API)
/config DELETE Reset all config to env defaults (disable: DISABLE_CONFIG_API)
/config/:key GET Get single config value (disable: DISABLE_CONFIG_API)
/config/:key PUT Set config override (persisted in KV) (disable: DISABLE_CONFIG_API)
/config/:key DELETE Reset config key to env default (disable: DISABLE_CONFIG_API)

Prometheus Configuration

scrape_configs:
  - job_name: 'cloudflare'
    scrape_interval: 60s
    scrape_timeout: 30s
    static_configs:
      - targets: ['your-worker.your-subdomain.workers.dev']

Runtime Config API

Override configuration at runtime without redeployment. Overrides persist in KV and take precedence over wrangler.jsonc env vars.

Config Keys

Key Type Description
queryLimit number Max results per GraphQL query
scrapeDelaySeconds number Delay before fetching metrics
timeWindowSeconds number Query time window
metricRefreshIntervalSeconds number Background refresh interval
accountListCacheTtlSeconds number Account list cache TTL
zoneListCacheTtlSeconds number Zone list cache TTL
sslCertsCacheTtlSeconds number SSL cert cache TTL
healthCheckCacheTtlSeconds number Health check cache TTL
logFormat "json" | "pretty" Log format
logLevel "debug" | "info" | "warn" | "error" Log level
cfAccounts string | null Comma-separated account IDs (null = all)
cfZones string | null Comma-separated zone IDs (null = all)
cfFreeTierAccounts string Comma-separated free tier account IDs
metricsDenylist string Comma-separated metrics to exclude
excludeHost boolean Exclude host labels
httpStatusGroup boolean Group HTTP status codes

Examples

# Get all config
curl https://your-worker.workers.dev/config

# Get single value
curl https://your-worker.workers.dev/config/logLevel

# Set override
curl -X PUT https://your-worker.workers.dev/config/logLevel \
  -H "Content-Type: application/json" \
  -d '{"value": "debug"}'

# Filter to specific zones
curl -X PUT https://your-worker.workers.dev/config/cfZones \
  -H "Content-Type: application/json" \
  -d '{"value": "zone-id-1,zone-id-2"}'

# Reset to env default
curl -X DELETE https://your-worker.workers.dev/config/logLevel

# Reset all overrides
curl -X DELETE https://your-worker.workers.dev/config

Available Metrics

Zone Request Metrics

Metric Type Labels
cloudflare_zone_requests_total counter zone
cloudflare_zone_requests_cached gauge zone
cloudflare_zone_requests_ssl_encrypted counter zone
cloudflare_zone_requests_content_type counter zone, content_type
cloudflare_zone_requests_country counter zone, country, region
cloudflare_zone_requests_status counter zone, status
cloudflare_zone_requests_browser_map_page_views_count counter zone, family
cloudflare_zone_requests_ip_class counter zone, ip_class
cloudflare_zone_requests_ssl_protocol counter zone, ssl_protocol
cloudflare_zone_requests_http_version counter zone, http_version
cloudflare_zone_requests_origin_status_country_host counter zone, origin_status, country, host
cloudflare_zone_requests_status_country_host counter zone, edge_status, country, host
cloudflare_zone_request_method_count counter zone, method

Zone Bandwidth Metrics

Metric Type Labels
cloudflare_zone_bandwidth_total counter zone
cloudflare_zone_bandwidth_cached counter zone
cloudflare_zone_bandwidth_ssl_encrypted counter zone
cloudflare_zone_bandwidth_content_type counter zone, content_type
cloudflare_zone_bandwidth_country counter zone, country

Zone Threat Metrics

Metric Type Labels
cloudflare_zone_threats_total counter zone
cloudflare_zone_threats_country counter zone, country
cloudflare_zone_threats_type counter zone, type

Zone Page/Unique Metrics

Metric Type Labels
cloudflare_zone_pageviews_total counter zone
cloudflare_zone_uniques_total counter zone

Colocation Metrics

Metric Type Labels
cloudflare_zone_colocation_visits counter zone, colo, host
cloudflare_zone_colocation_edge_response_bytes counter zone, colo, host
cloudflare_zone_colocation_requests_total counter zone, colo, host
cloudflare_zone_colocation_visits_error counter zone, colo, host, status
cloudflare_zone_colocation_edge_response_bytes_error counter zone, colo, host, status
cloudflare_zone_colocation_requests_total_error counter zone, colo, host, status

Firewall Metrics

Metric Type Labels
cloudflare_zone_firewall_events_count counter zone, action, source, rule, host, country
cloudflare_zone_firewall_bots_detected counter zone, bot_score, detection_ids

Health Check Metrics

Metric Type Labels
cloudflare_zone_health_check_events_origin_count counter zone, health_status, origin_ip, region, fqdn, failure_reason
cloudflare_zone_health_check_events_avg gauge zone
cloudflare_zone_health_check_rtt_ms gauge zone, origin_ip, fqdn
cloudflare_zone_health_check_ttfb_ms gauge zone, origin_ip, fqdn
cloudflare_zone_health_check_tcp_conn_ms gauge zone, origin_ip, fqdn
cloudflare_zone_health_check_tls_handshake_ms gauge zone, origin_ip, fqdn

Worker Metrics

Metric Type Labels
cloudflare_worker_requests_count counter script_name
cloudflare_worker_errors_count counter script_name
cloudflare_worker_cpu_time gauge script_name, quantile
cloudflare_worker_duration gauge script_name, quantile

Load Balancer Metrics

Metric Type Labels
cloudflare_zone_pool_health_status gauge zone, lb_name, pool_name
cloudflare_zone_pool_requests_total counter zone, lb_name, pool_name, origin_name
cloudflare_zone_lb_pool_rtt_ms gauge zone, lb_name, pool_name
cloudflare_zone_lb_steering_policy_info gauge zone, lb_name, policy
cloudflare_zone_lb_origins_selected_count gauge zone, lb_name, pool_name
cloudflare_zone_lb_origin_weight gauge zone, lb_name, pool_name, origin_name

Logpush Metrics

Metric Type Labels
cloudflare_logpush_failed_jobs_account_count counter account, job_id, destination_type
cloudflare_logpush_failed_jobs_zone_count counter zone, job_id, destination_type

Error Rate Metrics

Metric Type Labels
cloudflare_zone_customer_error_4xx_rate counter zone, status, country, host
cloudflare_zone_customer_error_5xx_rate counter zone, status, country, host
cloudflare_zone_edge_error_rate gauge zone, status
cloudflare_zone_origin_error_rate gauge zone, status
cloudflare_zone_origin_response_duration_ms gauge zone, status, country, host

Cache Metrics

Metric Type Labels
cloudflare_zone_cache_hit_ratio gauge zone
cloudflare_zone_cache_miss_origin_duration_ms gauge zone, country, host

Bot Metrics

Metric Type Labels
cloudflare_zone_bot_request_by_country counter zone, country

Magic Transit Metrics

Metric Type Labels
cloudflare_magic_transit_active_tunnels gauge account
cloudflare_magic_transit_healthy_tunnels gauge account
cloudflare_magic_transit_tunnel_failures gauge account
cloudflare_magic_transit_edge_colo_count gauge account

SSL Certificate Metrics

Metric Type Labels
cloudflare_zone_certificate_validation_status gauge zone, type, issuer, status

Exporter Info Metrics

Metric Type Labels
cloudflare_exporter_up gauge -
cloudflare_exporter_errors_total counter account_id, error_code
cloudflare_accounts_total gauge -
cloudflare_zones_total gauge -
cloudflare_zones_filtered gauge -
cloudflare_zones_processed gauge -

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              WORKER ISOLATE                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                            β”‚
β”‚  β”‚  Worker.fetch  │◄─── HTTP /metrics, /health, /config                        β”‚
β”‚  β”‚ (HTTP handler) β”‚                                                            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                            β”‚
β”‚          β”‚                                                                     β”‚
β”‚          β”‚ RPC (stub.export())                                                 β”‚
β”‚          β–Ό                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚ CONFIG_KV: Runtime config overrides (merged with env defaults)         β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         DURABLE OBJECT ISOLATES                                β”‚
β”‚                                                                                β”‚
β”‚  Each DO runs in its own V8 isolate with:                                      β”‚
β”‚  - Own CloudflareMetricsClient instance (per-isolate singleton)                β”‚
β”‚  - Own persistent storage                                                      β”‚
β”‚  - Own alarm scheduler                                                         β”‚
β”‚                                                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚              MetricCoordinator (1 global instance)                      β”‚   β”‚
β”‚  β”‚  ID: "metric-coordinator"                                               β”‚   β”‚
β”‚  β”‚  State: accounts[], lastAccountFetch                                    β”‚   β”‚
β”‚  β”‚  Cache TTL: 600s (account list)                                         β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                               β”‚ RPC                                            β”‚
β”‚                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                   β”‚
β”‚                  β–Ό            β–Ό            β–Ό                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚  β”‚ AccountMetric   β”‚ β”‚ AccountMetric   β”‚ β”‚ AccountMetric   β”‚                   β”‚
β”‚  β”‚ Coordinator     β”‚ β”‚ Coordinator     β”‚ β”‚ Coordinator     β”‚                   β”‚
β”‚  β”‚ account:acct1   β”‚ β”‚ account:acct2   β”‚ β”‚ account:acct3   β”‚                   β”‚
β”‚  β”‚ Alarm: 60s      β”‚ β”‚ Alarm: 60s      β”‚ β”‚ Alarm: 60s      β”‚                   β”‚
β”‚  β”‚ Zone TTL: 1800s β”‚ β”‚ Zone TTL: 1800s β”‚ β”‚ Zone TTL: 1800s β”‚                   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚          β”‚ RPC               β”‚                   β”‚                             β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”                       β”‚
β”‚   β–Ό            β–Ό      β–Ό            β–Ό      β–Ό            β–Ό                       β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”                     β”‚
β”‚ β”‚Exprtβ”‚    β”‚Exprtβ”‚  β”‚Exprtβ”‚    β”‚Exprtβ”‚  β”‚Exprtβ”‚    β”‚Exprtβ”‚                     β”‚
β”‚ β”‚(13) β”‚ .. β”‚(N)  β”‚  β”‚(13) β”‚ .. β”‚(N)  β”‚  β”‚(13) β”‚ .. β”‚(N)  β”‚                     β”‚
β”‚ β”‚acct β”‚    β”‚zone β”‚  β”‚acct β”‚    β”‚zone β”‚  β”‚acct β”‚    β”‚zone β”‚                     β”‚
β”‚ β””β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”˜                     β”‚
β”‚                                                                                β”‚
β”‚  MetricExporter DOs (per account):                                             β”‚
β”‚  - Account-scoped (13): worker-totals, logpush-account, magic-transit,         β”‚
β”‚    http-metrics, adaptive-metrics, edge-country-metrics, colo-metrics,         β”‚
β”‚    colo-error-metrics, request-method-metrics, health-check-metrics,           β”‚
β”‚    load-balancer-metrics, logpush-zone, origin-status-metrics                  β”‚
β”‚  - Zone-scoped (N per account, 1 per zone): ssl-certificates                   β”‚
β”‚                                                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚              CloudflareMetricsClient (per-isolate)                      β”‚   β”‚
β”‚  β”‚  - urql Client (GraphQL)                                                β”‚   β”‚
β”‚  β”‚  - Cloudflare SDK (REST)                                                β”‚   β”‚
β”‚  β”‚  - DataLoader: firewallRulesLoader (batches Promise.all calls)          β”‚   β”‚
β”‚  β”‚  - Global Rate limiter: 40 req/10s with exponential backoff             β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Request Path: Prometheus Scrape (GET /metrics)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  GET /metrics   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Prometheus│────────────────▢│ Worker β”‚
β”‚  Server  β”‚                 β”‚ .fetch β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
                                β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚            MetricCoordinator                β”‚
          β”‚                                             β”‚
          β”‚  1. Check account cache (TTL: 600s)         β”‚
          β”‚  2. If stale β†’ getAccounts()                β”‚
          β”‚  3. Fan out to AccountMetricCoordinators    β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                        β”‚                        β”‚
       β–Ό                        β–Ό                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ AccountMetric  β”‚    β”‚ AccountMetric  β”‚    β”‚ AccountMetric  β”‚
β”‚ Coordinator    β”‚    β”‚ Coordinator    β”‚    β”‚ Coordinator    β”‚
β”‚ (Account A)    β”‚    β”‚ (Account B)    β”‚    β”‚ (Account C)    β”‚
β”‚                β”‚    β”‚                β”‚    β”‚                β”‚
β”‚ 1. Check if    β”‚    β”‚                β”‚    β”‚                β”‚
β”‚    refresh()   β”‚    β”‚  (parallel)    β”‚    β”‚  (parallel)    β”‚
β”‚    needed      β”‚    β”‚                β”‚    β”‚                β”‚
β”‚ 2. Fan out to  β”‚    β”‚                β”‚    β”‚                β”‚
β”‚    exporters   β”‚    β”‚                β”‚    β”‚                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                     β”‚                     β”‚
  β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
  β–Ό           β–Ό         β–Ό           β–Ό         β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”
β”‚Exprtβ”‚...β”‚Exprtβ”‚    β”‚Exprtβ”‚...β”‚Exprtβ”‚    β”‚Exprtβ”‚...β”‚Exprtβ”‚
β”‚13+N β”‚   β”‚     β”‚    β”‚13+N β”‚   β”‚     β”‚    β”‚13+N β”‚   β”‚     β”‚
β”‚     β”‚   β”‚     β”‚    β”‚     β”‚   β”‚     β”‚    β”‚     β”‚   β”‚     β”‚
β”‚ ret β”‚   β”‚ ret β”‚    β”‚ ret β”‚   β”‚ ret β”‚    β”‚ ret β”‚   β”‚ ret β”‚
β”‚cacheβ”‚   β”‚cacheβ”‚    β”‚cacheβ”‚   β”‚cacheβ”‚    β”‚cacheβ”‚   β”‚cacheβ”‚
β””β”€β”€β”¬β”€β”€β”˜   β””β”€β”€β”¬β”€β”€β”˜    β””β”€β”€β”¬β”€β”€β”˜   β””β”€β”€β”¬β”€β”€β”˜    β””β”€β”€β”¬β”€β”€β”˜   β””β”€β”€β”¬β”€β”€β”˜
   β”‚         β”‚          β”‚         β”‚          β”‚         β”‚
   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
        β”‚                    β”‚                    β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β–Ό
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚  FAN-IN: Merge  β”‚
                   β”‚  all metrics +  β”‚
                   β”‚  serialize to   β”‚
                   β”‚  Prometheus fmt β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚  HTTP Response  β”‚
                   β”‚  text/plain     β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ NOTE: Request path is FAST - just reads cached metrics   β”‚
β”‚ No network calls to Cloudflare API during scrape         β”‚
β”‚ (unless account list cache is stale)                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Background Refresh Path: Alarm-Driven Metric Fetching

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           ALARM TRIGGERS                     β”‚
β”‚ AccountMetricCoordinator: every 60s          β”‚
β”‚ MetricExporter: every 60s + 1-5s fixed jitterβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

AccountMetricCoordinator.alarm()

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 AccountMetricCoordinator.refresh()                     β”‚
β”‚                                                                        β”‚
β”‚  1. Check zone cache (TTL: 1800s / 30 min)                             β”‚
β”‚                                                                        β”‚
β”‚  2. If stale:                                                          β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚     β”‚  REST: getZones(accountId)                                     β”‚ β”‚
β”‚     β”‚           └─► DataLoader batches if multiple calls same tick   β”‚ β”‚
β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚     β”‚  REST: getFirewallRules(zoneId) Γ— N zones (parallel)           β”‚ β”‚
β”‚     β”‚           └─► DataLoader batches parallel calls                β”‚ β”‚
β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                        β”‚
β”‚  3. Push context to MetricExporter DOs:                                β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚     β”‚ Account-scoped (13 exporters):                                 β”‚ β”‚
β”‚     β”‚   exporter.updateZoneContext(accountId, accountName, zones)    β”‚ β”‚
β”‚     β”‚                                                                β”‚ β”‚
β”‚     β”‚ Zone-scoped (N exporters, 1 per zone):                         β”‚ β”‚
β”‚     β”‚   exporter.initializeZone(zone, accountId, accountName)        β”‚ β”‚
β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                        β”‚
β”‚  4. Schedule next alarm (60s)                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MetricExporter.alarm()

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           MetricExporter.refresh() for account-scoped queries          β”‚
β”‚                                                                        β”‚
β”‚  Query Types (13 total):                                               β”‚
β”‚  β”œβ”€β”€ ACCOUNT-LEVEL (single account per query, 3):                      β”‚
β”‚  β”‚   β”œβ”€β”€ worker-totals                                                 β”‚
β”‚  β”‚   β”œβ”€β”€ logpush-account                                               β”‚
β”‚  β”‚   └── magic-transit                                                 β”‚
β”‚  β”‚                                                                     β”‚
β”‚  └── ZONE-LEVEL (all zones batched in one query, 10):                  β”‚
β”‚      β”œβ”€β”€ http-metrics                                                  β”‚
β”‚      β”œβ”€β”€ adaptive-metrics                                              β”‚
β”‚      β”œβ”€β”€ edge-country-metrics                                          β”‚
β”‚      β”œβ”€β”€ colo-metrics                                                  β”‚
β”‚      β”œβ”€β”€ colo-error-metrics                                            β”‚
β”‚      β”œβ”€β”€ request-method-metrics                                        β”‚
β”‚      β”œβ”€β”€ health-check-metrics                                          β”‚
β”‚      β”œβ”€β”€ load-balancer-metrics                                         β”‚
β”‚      β”œβ”€β”€ logpush-zone                                                  β”‚
β”‚      └── origin-status-metrics                                         β”‚
β”‚                                                                        β”‚
β”‚  After fetch: Process counters β†’ Cache metrics β†’ Schedule next alarm   β”‚
β”‚  Jitter: 1-5s fixed (tighter clustering for time range alignment)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Development

bun install          # Install dependencies
bun run dev          # Run locally (port 8787)
bun run check        # Lint + format check
bun run deploy       # Deploy to Cloudflare

Tech Stack

License

MIT

About

Export Cloudflare metrics to Prometheus. Built on Cloudflare Workers with Durable Objects for stateful metric accumulation.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published