dnswizdocs

Metrics endpoint

dnswiz exposes your account’s DNS metrics as a Prometheus scrape target. Point your own Prometheus, Grafana Agent, or OpenTelemetry Collector at it and DNS lands on the same dashboard as everything else you run, with your alerting rules, your retention and your on-call routing.

GET https://console.dnswiz.app/api/v1/metrics
Authorization: Bearer <api key>

Set it up

Create an API key with the Metrics endpoint scope and nothing else. Custom access, metrics: read. A key like that can read these numbers and cannot touch a zone, a record or a certificate, which is what you want sitting in a monitoring config.

scrape_configs:
  - job_name: dnswiz
    scheme: https
    metrics_path: /api/v1/metrics
    scrape_interval: 60s
    static_configs:
      - targets: ['console.dnswiz.app']
    authorization:
      credentials: dnswiz_your_key_here

A minute is a sensible interval. The underlying counters roll up hourly, so scraping faster costs you requests without telling you anything new.

Using it from OpenTelemetry

There is no OTLP endpoint and you do not need one. The OpenTelemetry Collector’s prometheus receiver scrapes this directly, and everything downstream of it is ordinary OTLP.

receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: dnswiz
          scheme: https
          metrics_path: /api/v1/metrics
          static_configs:
            - targets: ['console.dnswiz.app']
          authorization:
            credentials: dnswiz_your_key_here

What you get

Metric Type Labels
dnswiz_queries_total counter zone, qtype, rcode
dnswiz_query_duration_seconds histogram zone
dnswiz_record_answers_total counter zone, record, endpoint
dnswiz_endpoint_up gauge endpoint, pool
dnswiz_zone_records gauge zone
dnswiz_certificate_expiry_seconds gauge name, issuer
dnswiz_series_dropped gauge none

dnswiz_record_answers_total is the one worth knowing about if you use load balancing. Health tells you a member could be answered with; this tells you whether it was. A weight that is not doing what you meant, or a member that is healthy and receiving nothing, both look correct on a config screen and are obvious here.

Things worth knowing

Labels come from your configuration, never from what was asked for. There is no label carrying the queried name. That is deliberate: a typical account has a handful of zones and a few dozen records, but tens or hundreds of thousands of distinct queried names, almost all of it subdomain scanning by strangers. A label on the query name would mean anyone with dig could inflate your metrics bill on demand.

dnswiz_endpoint_up omits endpoints that have never been probed rather than reporting them as 0. Not measured and down are different statements, and alerting on the second when you mean the first pages somebody for nothing.

Counters reset when old data ages out of retention. Prometheus understands counter resets and rate() handles them correctly. A raw dnswiz_queries_total graph will show a step down; a rate() graph will not.

The latency _sum is approximate. The edges keep bucketed counts rather than a running total, so the sum is estimated from bucket midpoints and any average derived from it is accurate to within a bucket width. Quantiles from histogram_quantile() are unaffected, and are what you want to graph anyway.

dnswiz_series_dropped should be zero. Non-zero means your account produced more series than one response carries and the scrape you are looking at is incomplete. Tell us if you see it.

Alerts worth having

groups:
  - name: dnswiz
    rules:
      - alert: DNSZoneWentQuiet
        expr: rate(dnswiz_queries_total[15m]) == 0
        for: 30m
        annotations:
          summary: "{{ $labels.zone }} is answering no queries"
          description: >
            A zone going quiet is how a broken delegation announces
            itself. It keeps answering for anyone who reaches it, and
            fewer and fewer resolvers do.

      - alert: DNSNoDataRate
        expr: >
          sum by (zone) (rate(dnswiz_queries_total{rcode="NXDOMAIN"}[15m]))
          / sum by (zone) (rate(dnswiz_queries_total[15m])) > 0.8
        for: 15m
        annotations:
          summary: "{{ $labels.zone }} is mostly answering NXDOMAIN"
          description: >
            Usually somebody walking your zone with a wordlist. Worth
            looking at what they found rather than what they tried.

      - alert: GSLBMemberStarved
        expr: rate(dnswiz_record_answers_total[1h]) == 0
        for: 2h
        annotations:
          summary: "{{ $labels.endpoint }} is getting no traffic for {{ $labels.record }}"

      - alert: CertificateExpiringSoon
        expr: dnswiz_certificate_expiry_seconds < 14 * 24 * 3600
        annotations:
          summary: "{{ $labels.name }} expires in under two weeks"