Overview
The Logger module is Vercube's logging system. As of v1 it is a thin, dependency-injected wrapper around evlog - a structured, wide-event logger with pretty development output, JSON production output, PII redaction, sampling and pluggable drains/adapters.
You keep the familiar Logger DI token and the debug / info / warn / error methods; underneath, every call becomes an evlog event.
Installation
$ pnpm add @vercube/logger
$ npm install @vercube/logger
$ bun install @vercube/logger
@vercube/core already depends on @vercube/logger and binds it automatically - you only install it directly when using it standalone.
Quick Start
The logger is bound for you
createApp binds the evlog-backed Logger and configures it from your app config. You normally don't bind anything yourself:
import { defineConfig } from '@vercube/core';
export default defineConfig({
// forwarded to evlog's `minLevel`
logLevel: 'debug',
});
If you bootstrap a container manually, bind it explicitly:
import { Container } from '@vercube/di';
import { Logger, BaseLogger } from '@vercube/logger';
export function setupContainer(container: Container): void {
container.bind(Logger, BaseLogger);
container.get(Logger).configure({
logLevel: 'info',
pretty: process.env.NODE_ENV !== 'production',
});
}
Inject Logger into Services
import { Inject } from '@vercube/di';
import { Logger } from '@vercube/logger';
export class UserService {
@Inject(Logger)
private logger!: Logger;
async createUser(data: CreateUserDto) {
this.logger.debug('UserService::createUser', 'creating user');
try {
const user = await this.database.createUser(data);
this.logger.info('UserService::createUser', { userId: user.id });
return user;
} catch (error) {
this.logger.error('UserService::createUser', error as Error);
throw error;
}
}
}
Use Logger in Controllers
import { Controller, Get, Post } from '@vercube/core';
import { Inject } from '@vercube/di';
import { Logger } from '@vercube/logger';
@Controller('/users')
export class UserController {
@Inject(Logger)
private logger!: Logger;
@Post('/')
async create(req: Request) {
this.logger.info('POST /users', 'creating new user');
// ...
return Response.json({ ok: true });
}
}
Core Concepts
Logger
Logger is the DI token injected throughout your application. It exposes the four classic level methods plus a small wide-event API:
- debug() / info() / warn() / error() - fire-and-forget logs, one evlog event per call.
- set(context) - merge structured fields into every subsequent event.
- getContext() - read the accumulated context.
- child(context) - derive a logger that inherits context plus extra fields.
- emit(overrides?) - flush the accumulated context as a single wide event, then reset.
How arguments map to events
The variadic arguments are translated into an evlog event:
| Call | Resulting event |
|---|---|
info('tag', 'message') | tagged log tag → message |
warn('something happened') | { message: 'something happened' } |
error(err) | { error: { name, message, stack } } |
error('tag', err) | { message: 'tag', error: {...} } |
info('tag', { userId: 1 }) | { message: 'tag', userId: 1 } |
Any object argument is merged into the event; any Error is captured under error.
Log Levels
Levels are hierarchical and identical to evlog's:
debug → info → warn → error
logLevel maps to evlog's minLevel (a hard threshold for the simple log API). Order: debug < info < warn < error.
Wide events
A "wide event" is a single, richly-structured log line that accumulates context over an operation, then is emitted once. Use set() to add context and emit() to flush it:
logger.set({ jobId: 'sync-001', queue: 'emails' });
logger.set({ processed: 120 });
logger.emit({ outcome: 'success' });
// → one event: { jobId, queue, processed, outcome }
For per-request wide events (method, path, status, duration), Vercube ships the EvlogMiddleware in @vercube/core, enabled by default. Disable it with requestLogging: false in your app config.
Configuration
configure() accepts evlog's full LoggerConfig plus the logLevel alias:
container.get(Logger).configure({
logLevel: 'info', // alias for evlog `minLevel`
pretty: true, // human-readable output (auto: true in dev)
silent: false, // suppress console output (drains still run)
stringify: true, // emit JSON strings when pretty is off
redact: true, // PII redaction (auto: true in production)
env: { service: 'api' }, // environment context
sampling: { rates: { info: 0.5 } },
drain: (ctx) => sendToBackend(ctx.event),
});
Sending logs elsewhere (drains & adapters)
evlog's drains and ready-made adapters (Axiom, OTLP, Sentry, Datadog, …) are available directly from the evlog package and re-exported from @vercube/logger. See the Drains & Adapters page.