Log Drains
A log drain forwards your services' runtime logs to your own log tools as they are written: Datadog, Grafana Loki (including Grafana Cloud) or any HTTPS endpoint. Log drains are included in the Pro plan and up.
Add a drain
Team owners and admins open Account settings → Log Drains (for the team selected in the sidebar) and click Add log drain:
| Drain | What you need |
|---|---|
| Datadog | An API key (Datadog: Organization Settings → API Keys) and your account's site, such as datadoghq.com or datadoghq.eu. |
| Loki | The push URL, such as https://logs-prod-012.grafana.net/loki/api/v1/push; a URL without a path gets /loki/api/v1/push. For Grafana Cloud, the user (the stack's numeric ID) and a token with logs:write. A tenant for multi-tenant Loki is sent as X-Scope-OrgID. |
| HTTP | Any https:// URL. Zenavo POSTs JSON to it, signed (see below). |
Choose All projects, including new ones, or only some projects. Test sends one line right away and shows whether it arrived. Up to 5 drains per team.
URLs, API keys and passwords are secrets: they are stored encrypted, and the console only shows the destination's host and path. Zenavo posts only to hosts whose addresses are all public, and does not follow redirects. Adding, changing, testing and deleting drains is recorded in the audit log.
What is sent
Every line a running service prints, with variable values redacted ([REDACTED]) as everywhere else, labeled with the team, project and service. Build logs are not forwarded.
| Drain | Format |
|---|---|
| Datadog | Sent to https://http-intake.logs.<site>/api/v2/logs with ddsource: zenavo, service the service name, hostname the project name and the tags project, project_id, service_id and team_id. |
| Loki | One stream per service, labeled source="zenavo", project, project_id, service, service_id and team_id. |
| HTTP | The JSON below. |
Lines go out in batches of up to 500 lines, or every 2 seconds. A batch that fails is retried after 1, 5 and 15 seconds; requests the drain rejects (such as a wrong API key, any 4xx answer other than 408 and 429) are not retried. After that the batch is dropped. Each drain buffers up to 10,000 lines; when a drain cannot keep up, newer lines are dropped. The console shows each drain's last delivery or why it failed, and how many lines were dropped. Logs in the console, live and stored, are never affected by a drain.
Delivery is at least once: when the control plane restarts, the last couple of minutes may be sent again.
HTTP payload
{
"logs": [
{
"time": "2026-10-22T08:00:00.123456789Z",
"message": "GET /cart 200 12ms",
"team_id": "team_…",
"project_id": "proj_…",
"project": "shop",
"service_id": "svc-…",
"service": "web"
}
]
}Any 2xx answer counts as delivered. Every request carries X-Zenavo-Timestamp (Unix seconds) and X-Zenavo-Signature: v1=<hex>, the HMAC-SHA256 of <timestamp>.<raw body> with the drain's signing secret, the same scheme as notification webhooks. The secret (whsec_…) is shown once when you add the drain; New secret replaces it and shows the new one once.
Plans
Log drains need the Pro, Team or Enterprise plan. When a team moves to a plan without them, its drains stop forwarding; they can still be turned off, edited or deleted, and start again after an upgrade.
Self-hosting
The control plane tails each running service once, across all its instances, for stored logs and drains alike. Without a log storage bucket (ZENAVO_LOG_S3_BUCKET), only projects with a drain are tailed. The control plane needs outbound HTTPS to the drains' hosts.