Documentation
Getting data in.
Northwind ingests OpenTelemetry, Prometheus remote-write and syslog. If you already run an OTel collector, the integration is one exporter block.
Quickstart
Five minutes from nothing to a chart. You need an API key, which the free tier issues without a card.
# 1. Install the collector
curl -fsSL https://get.northwind.example/install.sh | sh
# 2. Point it at your account
northwind configure --api-key "${NORTHWIND_API_KEY}"
# 3. Send something
northwind test --signal metrics
# → 200 OK · 1 metric accepted · view at app.northwind.example
Install the collector
The collector is the standard OpenTelemetry Collector with one extra exporter. If you have an existing deployment, add the exporter rather than replacing anything.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 5s
redact:
# Redaction happens here, before anything leaves your network
blocked_key_patterns: [".*_ssn", "patient_.*", "authorization"]
exporters:
northwind:
endpoint: "ingest.northwind.example:443"
api_key: "${NORTHWIND_API_KEY}"
compression: zstd
sending_queue:
# Buffer to disk so our outage is not your outage
storage: file_storage
service:
pipelines:
metrics:
receivers: [otlp]
processors: [redact, batch]
exporters: [northwind]
On Kubernetes, the Helm chart wraps all of the above: helm install northwind northwind/collector --set apiKey=…
Your first query
NWQL reads left to right as a pipeline. Every stage takes the previous stage's result.
metric("http.server.duration")
| where service == "checkout-api"
| percentile(95) by endpoint
| over(1h)
Metrics
Accepts OTLP, Prometheus remote-write and StatsD. Existing Prometheus setups need only a remote-write URL change — recording rules, alerting rules and Grafana dashboards are unaffected.
remote_write:
- url: "https://ingest.northwind.example/api/v1/write"
authorization:
credentials_file: /etc/northwind/api-key
Traces
OTLP over gRPC or HTTP. Sampling policy is set server-side, so you do not redeploy services to change what is kept.
policies:
- name: keep-all-errors
type: status_code
status_codes: [ERROR]
- name: keep-slow
type: latency
threshold_ms: 800
- name: baseline
type: probabilistic
sampling_percentage: 5
Logs
Structured JSON is preferred but not required — unstructured lines are parsed with the rules you configure and stored either way.
log()
| where service == "checkout-api" and level >= "warn"
| where body ~ "timeout"
| count() by host
| over(15m)
NWQL reference
| metric(name) | Select a metric series by name |
|---|---|
| trace() | Select spans |
| log() | Select log records |
| where expr | Filter; supports ==, !=, >, <, ~ (regex) and in |
| percentile(n) by k | Percentile aggregate, grouped |
| count() by k | Count, grouped |
| over(window) | Bucket into a time window |
| above() | Reference the previous query’s result set |
Limits and quotas
| Max log line | 256 KB |
|---|---|
| Max span size | 2 MB |
| Trace assembly window | 60 seconds |
| Metric resolution | 10 seconds |
| Ingest rate limit | None — bursts are absorbed and billed |
| Query timeout | 120 seconds |
| API rate limit | 600 requests/minute/key |
These docs are an excerpt written to demonstrate documentation design. The endpoints are not real — see book a demo if you want to talk about the real thing.