Drains & Adapters

Control output format and ship logs to external backends with evlog

The Vercube logger is backed by evlog, so output formatting and shipping logs to external systems is handled by evlog's output modes, drains and adapters rather than by custom provider classes.

Output modes

evlog has two built-in output modes, selected automatically by environment and controllable via configure().

Pretty (development)

Human-readable, colorized output. Enabled automatically outside production.

container.get(Logger).configure({ logLevel: 'debug', pretty: true });
12:34:56.789 [UserService::createUser] creating user
12:34:56.801 INFO  [app]
  ├─ userId: abc-123
  └─ message: UserService::createUser

Disable colors with the standard NO_COLOR environment variable.

JSON (production)

Structured JSON, ideal for log aggregation (ELK, Splunk, CloudWatch, …). Used automatically in production, or force it by disabling pretty:

container.get(Logger).configure({ logLevel: 'info', pretty: false });
{"level":"info","userId":"abc-123","message":"UserService::createUser","timestamp":"2026-01-01T12:34:56.801Z"}

When pretty is false, stringify: true (the default) emits JSON strings; set stringify: false for raw objects (e.g. Cloudflare Workers).

Drains

A drain is a callback invoked with every emitted event - use it to forward logs anywhere. Configure it globally via configure():

container.get(Logger).configure({
  logLevel: 'info',
  drain: async (ctx) => {
    await fetch('https://logs.example.com/ingest', {
      method: 'POST',
      body: JSON.stringify(ctx.event),
    });
  },
});

For request-scoped drains/enrichment, pass options to the EvlogMiddleware (see the core middleware).

Adapters

evlog ships first-class adapters for popular backends. They are plain drains, so they plug straight into configure({ drain }):

import { createAxiomDrain } from 'evlog/axiom';

container.get(Logger).configure({
  drain: createAxiomDrain({ dataset: 'logs', token: process.env.AXIOM_TOKEN! }),
});

Available adapters include Axiom, OTLP, Sentry, Datadog, PostHog, Better Stack, HyperDX, filesystem and memory - see the evlog adapters overview.

Batching & retries

Wrap any drain with evlog's pipeline for batching and retry:

import { createDrainPipeline } from 'evlog/pipeline';
import { createAxiomDrain } from 'evlog/axiom';

const pipeline = createDrainPipeline({ batch: { size: 25 } });
const drain = pipeline(createAxiomDrain({ dataset: 'logs', token: '...' }));

container.get(Logger).configure({ drain });

Sampling & redaction

Reduce volume and protect PII with evlog's built-ins:

container.get(Logger).configure({
  // probabilistic sampling on top of `minLevel`
  sampling: { rates: { info: 0.25, debug: 0.05 } },
  // redaction: `true` enables built-in patterns (email, JWT, card, …)
  redact: { paths: ['user.password', 'headers.authorization'] },
});

Per-request wide events

@vercube/core registers EvlogMiddleware by default, emitting one wide event per request (method, path, status, duration). Configure or disable it:

vercube.config.ts
import { defineConfig } from '@vercube/core';

export default defineConfig({
  // set to false to turn off per-request wide events
  requestLogging: true,
});

For advanced integrations (custom request loggers, route filtering, enrich/keep callbacks) the evlog toolkit is re-exported from @vercube/logger/toolkit:

import { createMiddlewareLogger, extractSafeHeaders } from '@vercube/logger/toolkit';
Previous

API

Complete API reference for the evlog-backed Logger module

Next