cloudflare-log-collector

module
v0.1.20 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2026 License: MIT

README

cloudflare-log-collector

Cloudflare Log Collector

CI Coverage Quality Gate

Project Website

Grafana Dashboard

A lightweight Go service that polls Cloudflare's Analytics, Audit, and Web Analytics APIs for firewall events, HTTP traffic, account audit logs, and browser-side RUM (page views and Core Web Vitals), ships them into a self-hosted observability stack, and traces every poll cycle with OpenTelemetry.

  • Firewall and audit events are pushed to Loki as structured JSON log lines for querying in Grafana
  • HTTP traffic stats are accumulated into Prometheus counters (by method, status, and country) and also pushed to Loki for raw detail
  • RUM / Web Analytics captures real browser page views and Core Web Vitals (LCP, INP, CLS, FCP, TTFB) per site
  • Every poll cycle gets its own OpenTelemetry trace with child spans for API calls and Loki pushes, exported to Tempo via OTLP gRPC
  • Log-trace correlation is automatic - trace_id and span_id are injected into every structured log line via a custom slog handler, enabling one-click navigation between Loki logs and Tempo traces in Grafana
         Cloudflare GraphQL API
                   |
                   v
      +---------------------------+
      | cloudflare-log-collector  |
      +---------------------------+
         |         |          |
         v         v          v
       Loki    Prometheus   Tempo
     (events)  (metrics)   (traces)
         \         |          /
          '--- Grafana ------'

Table of Contents

Datasets

Firewall Events (firewallEventsAdaptive)

Individual WAF/firewall events with full request detail. Each event becomes a JSON log line in Loki under {job="cloudflare", type="firewall", zone="example.com"}.

Fields captured: action, clientIP, clientRequestHTTPHost, clientRequestHTTPMethodName, clientRequestPath, clientRequestQuery, datetime, rayName, ruleId, source, userAgent, clientCountryName.

HTTP Traffic (httpRequestsAdaptiveGroups)

Aggregated HTTP traffic statistics grouped by method, status code, and country. Pushed to both Prometheus (cumulative counters) and Loki (structured JSON under {job="cloudflare", type="http_traffic", zone="example.com"}).

Fields captured: count, datetime, clientRequestHTTPMethodName, edgeResponseStatus, clientCountryName, edgeResponseBytes.

RUM / Web Analytics (rumPageloadEventsAdaptiveGroups, rumWebVitalsEventsAdaptiveGroups)

Browser-side Real User Monitoring, scoped per Web Analytics site. Page loads are pushed to Loki under {job="cloudflare", type="rum_pageload", site="example.com"} and counted (page views and sessions by country and device) in Prometheus. Core Web Vitals (LCP, INP, FID, FCP, TTFB, CLS) p75 are exposed as Prometheus gauges, sliced by device.

The datasets are account-scoped and identified by a site tag. Web Analytics must be enabled on the zone, and the API token needs Account Analytics Read. RUM collection is disabled by default.

Fields captured: pageloads - count, sum.visits, dimensions requestPath, countryName, deviceType, refererHost; web vitals - per-device p75 quantiles of each Core Web Vital (values in microseconds, except CLS which is a unitless score).

Account Audit Logs

Account-level audit logs capturing administrative actions across your Cloudflare account. Each event becomes a JSON log line in Loki under {job="cloudflare", type="audit", account="my-account"}.

Fields captured: id, account (id, name), action (description, result, time, type), actor (id, context, email, ip_address, token_id, token_name, type), raw (cf_ray_id, method, status_code, uri, user_agent), resource (id, product, type), zone (id, name).

Audit log collection is disabled by default and requires explicit configuration with account IDs.

Prometheus Metrics

Exposed on the configured metrics listen address (default :9101).

Metric Type Labels Description
cflog_poll_total counter dataset, zone, status Poll attempts by dataset, zone, and outcome
cflog_poll_duration_seconds histogram dataset, zone Poll latency
cflog_last_poll_timestamp gauge dataset, zone Unix timestamp of last successful poll
cflog_firewall_events_total counter action, zone Firewall events by action type
cflog_audit_events_total counter action, account Audit log events by action type
cflog_http_requests_total counter method, status, country, zone Cumulative HTTP requests
cflog_http_bytes_total counter type, zone Cumulative edge response bytes
cflog_rum_pageviews_total counter country, device, site RUM page views
cflog_rum_sessions_total counter country, device, site RUM sessions (entry page loads)
cflog_rum_web_vital_seconds gauge vital, device, site Core Web Vitals p75 in seconds (lcp, inp, fid, fcp, ttfb)
cflog_rum_cumulative_layout_shift gauge device, site Cumulative Layout Shift p75 (unitless)
cflog_loki_push_total counter status Loki push attempts by outcome
cflog_loki_push_duration_seconds histogram Loki push latency
cflog_build_info gauge version, go_version Build metadata

Loki Streams

Log streams pushed to Loki:

Stream Labels Content
Firewall events {job="cloudflare", type="firewall", zone="example.com"} One JSON log line per firewall event
HTTP traffic {job="cloudflare", type="http_traffic", zone="example.com"} One JSON log line per traffic group
Audit logs {job="cloudflare", type="audit", account="my-account"} One JSON log line per audit event
RUM page loads {job="cloudflare", type="rum_pageload", site="example.com"} One JSON log line per page-load group

All streams include the X-Scope-OrgID header for multi-tenant Loki deployments (configurable via tenant_id).

Configuration

Configuration is loaded from a YAML file. Environment variables are expanded with ${VAR_NAME} syntax.

cloudflare:
  api_token: "${CF_API_TOKEN}"        # Cloudflare API token (Analytics Read)
  zones:                              # One or more zones to monitor
    - id: "${CF_ZONE_ID}"
      name: "example.com"
  audit_logs:                         # Account audit log collection (optional)
    enabled: false                    # Disabled by default
    accounts:
      - id: "${CF_ACCOUNT_ID}"
        name: "my-account"
  web_analytics:                      # Browser-side RUM collection (optional)
    enabled: false                    # Disabled by default
    account_id: "${CF_ACCOUNT_ID}"    # Account that owns the Web Analytics sites
    sites:
      - site_tag: "${CF_RUM_SITE_TAG}" # Web Analytics site identifier
        name: "example.com"
  poll_interval: 5m                   # How often to poll (default: 5m)
  backfill_window: 1h                 # On startup, fetch this far back (default: 1h)

loki:
  endpoint: "http://loki:3100"        # Loki push API base URL
  tenant_id: "fake"                   # X-Scope-OrgID header (default: fake)
  batch_size: 100                     # Max entries per push request (default: 100)

metrics:
  listen: ":9101"                     # Prometheus /metrics endpoint (default: :9101)

tracing:
  enabled: true                       # Enable OTLP tracing (default: false)
  endpoint: "tempo:4317"              # OTLP gRPC endpoint
  sample_rate: 1.0                    # Sampling rate 0.0-1.0 (default: 1.0)
  insecure: true                      # Disable TLS for OTLP (default: false)

logging:
  level: "info"                       # Log level: debug, info, warn, error
  format: "json"                      # Log format: json, text (default: json)
Cloudflare API Token

The API token requires the following permissions:

  • Zone Analytics - Read (for firewall and HTTP zone analytics)
  • Account Analytics - Read (required for RUM / Web Analytics)
  • Account Settings - Read (required for audit logs)

RUM also requires Web Analytics to be enabled on the zone.

Create one at Cloudflare Dashboard > API Tokens.

Deployment

Docker
make push VERSION=v0.1.1

Builds and pushes multi-arch images (linux/amd64, linux/arm64) to the configured registry.

Nomad

The service deploys as a standard Nomad job with Vault integration for secret injection. The Nomad job template renders the config file with Cloudflare credentials from Vault at secret/data/cloudflare.

nomad job run cloudflare-log-collector.nomad.hcl
Vault Secret

Store at secret/data/cloudflare:

vault kv put secret/cloudflare api_token="<token>" zone_id="<zone_id>" zone_name="<zone_name>"

Development

# --- Build ---
make build                  # local platform binary
make docker                 # Docker image for local arch
make push                   # build and push multi-arch images to registry

# --- Run locally ---
make run                    # requires config.yaml in project root

# --- Test & Lint ---
make test                   # unit tests with race detector and coverage
make vet                    # Go vet static analysis
make lint                   # golangci-lint
make govulncheck            # Go vulnerability scanner

# --- Release ---
# Releases are automated by release-please: merge the release PR it maintains
# on main to bump the version, update CHANGELOG.md, tag, and run GoReleaser.
make release-local          # dry-run GoReleaser locally (no publish)
make deb                    # build .deb packages via GoReleaser snapshot
make publish-deb            # publish .deb packages to Aptly repository

# --- Website ---
make web-serve              # serve project website locally with live reload
make web-build              # build static site (minified)
make web-docker             # build website Docker image for local arch
make web-push               # build and push multi-arch website image

# --- Cleanup ---
make clean                  # remove build artifacts

Project Structure

|-- .goreleaser.yaml                  # GoReleaser release configuration
|-- release-please-config.json        # release-please configuration
|-- .release-please-manifest.json     # release-please version manifest
|-- Dockerfile                        # Multi-stage Alpine build
|-- Makefile                          # Build, test, push targets
|-- cmd/
|   `-- cloudflare-log-collector/
|       `-- main.go                   # Entry point, config, signal handling
|-- internal/
|   |-- cloudflare/
|   |   |-- client.go                 # Core API client, HTTP transport + retry
|   |   |-- graphql.go                # Shared GraphQL envelope + executor
|   |   |-- firewall.go               # firewallEventsAdaptive query
|   |   |-- http.go                   # httpRequestsAdaptiveGroups query
|   |   |-- rum.go                     # RUM / Web Analytics queries
|   |   |-- audit.go                  # REST audit logs query
|   |   |-- client_test.go, rum_test.go, transport_test.go
|   |-- collector/
|   |   |-- firewall.go               # Firewall event poller, Loki shipper
|   |   |-- http.go                   # HTTP traffic poller, counters + Loki
|   |   |-- audit.go                  # Audit log poller, Loki shipper
|   |   |-- rum.go                     # RUM poller: counters, vitals, Loki
|   |   |-- consumer_interfaces.go    # Narrow consumer-declared interfaces
|   |   |-- dataset.go                # Typed dataset name constants
|   |   |-- cursor.go                 # Shared seek-cursor helpers
|   |   `-- *_test.go
|   |-- config/
|   |   |-- config.go                 # YAML config with env var expansion
|   |   `-- config_test.go
|   |-- lifecycle/
|   |   |-- manager.go                # Background service lifecycle
|   |   `-- manager_test.go
|   |-- loki/
|   |   |-- client.go                 # Loki push API client
|   |   `-- client_test.go
|   |-- metrics/
|   |   `-- metrics.go                # Prometheus metric definitions
|   `-- telemetry/
|       |-- tracing.go                # OTel tracer init, span helpers
|       |-- tracehandler.go           # slog handler for trace correlation
|       `-- tracehandler_test.go
|-- web/
|   |-- hugo.toml                     # Hugo site configuration
|   |-- Dockerfile                    # Multi-stage Hugo + nginx build
|   |-- content/                      # Site content (Markdown)
|   |-- layouts/                      # Custom templates and shortcodes
|   |-- assets/css/                   # Custom theme variant
|   `-- themes/hugo-theme-relearn/    # Documentation theme (submodule)
|-- packaging/
|   |-- cloudflare-log-collector.service  # Systemd unit file
|   |-- config.example.yaml           # Example configuration
|   |-- postinst, prerm, postrm       # Debian package scripts
|   |-- copyright                     # License for Debian packaging
|   `-- changelog                     # Release notes for Debian packaging
`-- docs/
    |-- images/
    |   `-- grafana.png               # Grafana dashboard screenshot
    `-- style-guide.md                # Code style conventions

Rate Limits and Retry

The Cloudflare GraphQL Analytics API allows 300 queries per 5 minutes. With two queries per poll cycle (firewall + HTTP) per zone at the default 5-minute interval, usage stays well within limits. Free plan adaptive datasets support approximately 24 hours of lookback; the default backfill window is capped at 1 hour.

Both the Cloudflare and Loki clients automatically retry on transient failures (HTTP 429, 502, 503, 504) with exponential backoff up to 3 retries. The Retry-After header is honored when present. If a query returns the maximum number of results (10,000 firewall events or 5,000 HTTP groups), a warning is logged indicating potential truncation.

License

MIT

Directories

Path Synopsis
cmd
internal
cloudflare
Package cloudflare is a client for the Cloudflare GraphQL Analytics and REST Audit Logs APIs, with query building, response parsing, and retry logic.
Package cloudflare is a client for the Cloudflare GraphQL Analytics and REST Audit Logs APIs, with query building, response parsing, and retry logic.
collector
Package collector runs the firewall, HTTP traffic, and audit log poll loops, orchestrating Cloudflare queries, Prometheus metrics, and Loki shipping.
Package collector runs the firewall, HTTP traffic, and audit log poll loops, orchestrating Cloudflare queries, Prometheus metrics, and Loki shipping.
config
Package config loads the YAML configuration with environment-variable expansion, validates required fields, and applies defaults.
Package config loads the YAML configuration with environment-variable expansion, validates required fields, and applies defaults.
lifecycle
Package lifecycle supervises background services with panic recovery, automatic restart, and ordered shutdown.
Package lifecycle supervises background services with panic recovery, automatic restart, and ordered shutdown.
loki
Package loki is a client for the Loki push API, with batching, retry, and multi-tenant support.
Package loki is a client for the Loki push API, with batching, retry, and multi-tenant support.
metrics
Package metrics defines and registers the Prometheus metrics exported by the collector: poll operations, Loki pushes, and Cloudflare event counts.
Package metrics defines and registers the Prometheus metrics exported by the collector: poll operations, Loki pushes, and Cloudflare event counts.
telemetry
Package telemetry wires OpenTelemetry tracing and trace-correlated structured logging, including span helpers for Cloudflare and Loki operations.
Package telemetry wires OpenTelemetry tracing and trace-correlated structured logging, including span helpers for Cloudflare and Loki operations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL