Skip to main content

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:

  1. CommandRegistry.loggerPath defaults to "src/logger.ts" (configurable, like commandsPattern)
  2. Before executing a command, the CLI dynamically imports the file and looks for a named logger export (a pino instance)
  3. If found, it calls CommandRegistry.setLogger() internally — all commands use that logger
  4. 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

LevelNumeric ValueUse Case
trace10Very detailed debugging (not recommended in production)
debug20Debugging information
info30General informational messages
warn30Warning messages
error40Error messages
fatal60Critical errors that require immediate attention
silentInfinityDisable 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