Sinks

Custom drains

defineDrain and defineHttpDrain — write your own drain to ship wide events to any backend without a built-in adapter.

A drain is a function that receives wide events and ships them somewhere terminal: an HTTP API, a message queue, a database, a webhook. evlog ships built-in drains for popular providers (Adapters overview). When you need a destination that isn't covered, write your own.

Canonical guide

Full reference at Custom adapters:

  • defineDrain() for arbitrary transports (queue, DB, native SDK)
  • defineHttpDrain() for HTTP-based backends (auto timeouts + retries + identity headers)
  • Configuration resolution patterns (runtimeConfig, env vars, overrides)
  • Error handling and retries

This page exists in the build-on-top section as a pointer — same content, classified by axis.

A 5-minute example — internal Loki drain

import { defineHttpDrain } from 'evlog/toolkit'

export function createLokiDrain(overrides?: { url?: string; token?: string }) {
  return defineHttpDrain<{ url: string; token: string }>({
    name: 'loki',
    resolve: () => ({
      url: overrides?.url ?? process.env.LOKI_URL!,
      token: overrides?.token ?? process.env.LOKI_TOKEN!,
    }),
    encode: (events, config) => ({
      url: `${config.url}/loki/api/v1/push`,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${config.token}`,
      },
      body: JSON.stringify({
        streams: events.map(e => ({
          stream: { service: e.service, level: e.level },
          values: [[String(Date.parse(e.timestamp) * 1e6), JSON.stringify(e)]],
        })),
      }),
    }),
  })
}

defineHttpDrain handles abort timeouts, exponential backoff on 5xx / network errors, and automatic identity headers — you only describe how to encode events into the request.

Going further