Logger
Balda provides built-in structured logging powered by Pino - a fast, low-overhead logger for Node.js, Bun, and Deno.
Overview
- Library: Pino ^10.1.0
- Default level:
info - Format: JSON (structured logs)
- Runtimes: Node.js, Bun, Deno
Basic Usage
import { logger } from 'balda';
logger.info('Server starting');
logger.error('Something went wrong', { error: err });
logger.warn('High memory usage');
logger.debug('Debug information', { context: data });
Custom Logger for the Server
Pass your own Pino logger instance directly to the Server constructor — just like you can with ajvInstance:
import pino from 'pino';
import { Server } from 'balda';
const logger = pino({ level: 'debug' });
const server = new Server({
port: 3000,
logger,
});
If no logger is provided, the server uses the default internal logger (info level, JSON output).
Pretty-Printed Logs (Development)
import pino from 'pino';
import { Server } from 'balda';
const logger = pino({
level: 'debug',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
});
const server = new Server({ port: 3000, logger });
JSON Logs (Production)
import pino from 'pino';
import { Server } from 'balda';
const logger = pino({
level: 'warn',
formatters: {
level: (label) => ({ level: label }),
},
timestamp: pino.stdTimeFunctions.isoTime,
});
const server = new Server({ port: 3000, logger });
Environment-Based Configuration
import pino from 'pino';
import { Server } from 'balda';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
...(process.env.NODE_ENV === 'development' && {
transport: {
target: 'pino-pretty',
options: { colorize: true },
},
}),
});
const server = new Server({ port: 3000, logger });
Scoped Loggers
Create child loggers with additional context using logger.child():
import { logger } from 'balda';
const serviceLogger = logger.child({ scope: 'UserService' });
serviceLogger.info('User created', { userId: '123' });
// Output: {"level":"info","time":...,"scope":"UserService","userId":"123","msg":"User created"}
Scoped Logger Pattern in Classes
import { logger } from 'balda';
export class UserService {
private readonly log = logger.child({ scope: 'UserService' });
async createUser(email: string) {
this.log.info('Creating user', { email });
// ...
}
}
CLI Logger Resolution
Commands run standalone via npx balda <command> — there is no user-controlled entrypoint. To use a custom logger for CLI commands, Balda resolves the logger automatically from a file path:
CommandRegistry.loggerPathdefaults to"src/logger.ts"(configurable, likecommandsPattern)- Before executing a command, the CLI dynamically imports the file and looks for a named
loggerexport (a pino instance) - If found, it calls
CommandRegistry.setLogger()internally — all commands use that logger - Per-command override is possible via
CommandOptions.loggerPath
Since the scaffolded src/logger.ts exports a pino instance that is also imported by server.ts, both the server and CLI commands share the same logger definition.
Default Behavior (zero config)
The init command scaffolds src/logger.ts:
// src/logger.ts
import pino from 'pino';
export const logger = pino({ level: 'info' });
And src/server.ts imports it:
import { Server } from 'balda';
import { logger } from './logger.js';
const server = new Server({
port: 80,
logger,
});
CLI commands automatically pick up the same logger from src/logger.ts — no extra configuration needed.
Custom Logger Path
To change the global logger path for all commands:
import { CommandRegistry } from 'balda';
CommandRegistry.loggerPath = 'src/custom-logger.ts';
Or override per-command:
import { Command, CommandOptions } from 'balda';
export default class MyCommand extends Command {
static commandName = 'mycommand';
static options: CommandOptions = {
loggerPath: 'src/custom-logger.ts',
};
}
Programmatic Override
You can also call CommandRegistry.setLogger() directly with a pino instance:
import pino from 'pino';
import { CommandRegistry } from 'balda';
CommandRegistry.setLogger(pino({ level: 'debug' }));
This overrides the logger for CommandRegistry and all Command subclasses.
Log Levels
| Level | Numeric Value | Use Case |
|---|---|---|
trace | 10 | Very detailed debugging (not recommended in production) |
debug | 20 | Debugging information |
info | 30 | General informational messages |
warn | 30 | Warning messages |
error | 40 | Error messages |
fatal | 60 | Critical errors that require immediate attention |
silent | Infinity | Disable logging |
API Reference
Server({ logger })
Pass a Pino logger instance to the server constructor.
import pino from 'pino';
import { Server } from 'balda';
const server = new Server({
logger: pino({ level: 'debug' }),
});
If omitted, the default internal logger is used.
CommandRegistry.setLogger(logger)
Override the default logger for all CLI commands programmatically.
import pino from 'pino';
import { CommandRegistry } from 'balda';
CommandRegistry.setLogger(pino({ level: 'debug' }));
CommandRegistry.loggerPath
Path to a file exporting a pino logger instance, resolved automatically by the CLI before command execution. Defaults to "src/logger.ts".
import { CommandRegistry } from 'balda';
CommandRegistry.loggerPath = 'src/custom-logger.ts';
logger
The default logger instance exported by Balda.
import { logger } from 'balda';
logger.info(message, ...args);
logger.error(message, ...args);
logger.warn(message, ...args);
logger.debug(message, ...args);
Learn More
- Pino Documentation - Full Pino API reference
- pino-pretty - Pretty printing options
- Server Configuration - Server-level configuration