Logging
Bunty includes a built-in structured logger with colored terminal output, timestamps, scopes, and configurable log levels. No external dependencies required.
Quick Start
Basic Usage
import { Logger } from '@bunty/common';
// Use the default logger
Logger.info('Application started');
Logger.success('Database connected');
Logger.warn('Cache miss detected');
Logger.error('Failed to fetch data');
Logger.debug('Request payload:', { userId: 123 });
Scoped Logger
Create loggers for specific services or modules:
import { Logger } from '@bunty/common';
// Create a scoped logger
const logger = new Logger('UserService');
logger.info('Fetching user data');
logger.success('User created:', user);
logger.warn('User not found');
logger.error('Database error:', error);
Log Levels
Bunty supports six log levels, each with distinct colors:
| Level | Method | Color | Use Case |
|---|---|---|---|
| INFO | .info() | Cyan | Informational messages |
| SUCCESS | .success() | Green | Successful operations |
| WARN | .warn() | Yellow | Warning messages |
| DEBUG | .debug() | Gray | Debug information |
| ERROR | .error() | Red | Error messages |
| LOG | .log() | White | General messages |
Terminal Output Example
Here’s what the logger looks like in your terminal:
Visual breakdown:
- 🕐 Timestamp (gray) -
[2025/10/30 19:53:14] - 🏷️ Level (colored) -
[INFO],[SUCCESS],[ERROR], etc. - 📦 Scope (cyan) -
[PaymentService],[CacheService], etc. - 💬 Message (colored) - The actual log message with data
Code Examples
Code Examples
const logger = new Logger('PaymentService');
// Info - general information
logger.info('Processing payment for order', orderId);
// Success - successful operations
logger.success('Payment processed successfully', {
amount: 99.99,
orderId: 'ORD-123'
});
// Warn - potential issues
logger.warn('Payment gateway response time slow', {
responseTime: 3500
});
// Debug - detailed debugging info
logger.debug('Payment request payload:', {
cardLast4: '4242',
amount: 99.99,
currency: 'USD'
});
// Error - failures and exceptions
logger.error('Payment failed:', new Error('Insufficient funds'));
// Log - general purpose
logger.log('Payment webhook received');
Output in terminal:
Multiple Services Example
Different scopes in action:
const dbLogger = new Logger('Database');
const cacheLogger = new Logger('Cache');
const apiLogger = new Logger('API');
const workerLogger = new Logger('SyncWorker');
dbLogger.success('Connected to PostgreSQL');
cacheLogger.success('Connected to Redis');
apiLogger.info('Server listening on port 3000');
workerLogger.info('Background sync started');
Terminal output:
Output Format
Logs are formatted with timestamp, level, scope, and message:
Format Structure
[TIMESTAMP ] [LEVEL ] [SCOPE ] MESSAGE AND DATA
└─ Gray └─ Color └─ Cyan └─ Colored with formatting
Real-World Example
const logger = new Logger('UserService');
logger.info('Fetching user data');
logger.success('User created', { id: 123, email: 'user@example.com' });
logger.warn('User not found');
logger.error('Database error:', new Error('Connection timeout'));
Terminal output:
Component Breakdown
| Component | Width | Color | Example |
|---|---|---|---|
| Timestamp | 19 chars | Gray | [2025/10/30 19:53:14] |
| Level | Variable + padding | Varies | [INFO], [SUCCESS], [ERROR] |
| Scope | Variable + padding | Cyan | [UserService], [Database] |
| Message | Variable | Varies | Colored based on log level |
Creating Loggers
Scoped Logger
import { Logger } from '@bunty/common';
const logger = new Logger('OrderService');
logger.info('Order service initialized');
logger.success('Order created:', order);
Default Logger
Use static methods for quick logging:
import { Logger } from '@bunty/common';
Logger.info('Using default logger');
Logger.success('Operation completed');
Logger.error('Something went wrong');
Multiple Scopes
Create different loggers for different parts of your application:
const dbLogger = new Logger('Database');
const cacheLogger = new Logger('Cache');
const apiLogger = new Logger('API');
dbLogger.info('Connected to PostgreSQL');
cacheLogger.info('Connected to Redis');
apiLogger.info('Server listening on port 3000');
Log Level Control
Control which log levels are enabled using bitwise flags:
LogFlags Enum
import { Logger, LogFlags } from '@bunty/common';
enum LogFlags {
None = 0, // No logging
Info = 1 << 0, // Info messages
Success = 1 << 1, // Success messages
Warn = 1 << 2, // Warning messages
Debug = 1 << 3, // Debug messages
Error = 1 << 4, // Error messages
Log = 1 << 5, // General log messages
All = Info | Success | Warn | Debug | Error | Log
}
Enable/Disable Levels
const logger = new Logger('MyService');
// Only log errors and warnings
logger.setFlags(LogFlags.Error | LogFlags.Warn);
logger.info('This will NOT be logged');
logger.error('This WILL be logged');
// Enable all levels
logger.setFlags(LogFlags.All);
// Disable all logging
logger.setFlags(LogFlags.None);
Production vs Development
import { env, Logger, LogFlags } from '@bunty/common';
const logger = new Logger('App');
// Production: only errors and warnings
if (env('NODE_ENV') === 'production') {
logger.setFlags(LogFlags.Error | LogFlags.Warn);
}
// Development: all logs including debug
else {
logger.setFlags(LogFlags.All);
}
Add/Remove Flags
const logger = new Logger('Service');
// Add debug flag
logger.addFlags(LogFlags.Debug);
// Remove info flag
logger.removeFlags(LogFlags.Info);
// Check current flags
const flags = logger.getFlags();
Logging Complex Data
The logger automatically formats different data types:
Objects
logger.info('User data:', {
id: 123,
email: 'user@example.com',
roles: ['admin', 'user'],
meta: { lastLogin: new Date() }
});
Terminal output:
Errors
try {
await riskyOperation();
} catch (error) {
logger.error('Operation failed:', error);
}
Terminal output (stack trace in red):
Primitives
logger.info('Order count:', 42);
logger.info('Is active:', true);
logger.info('Status:', null);
logger.info('User name:', 'John Doe');
Terminal output:
Mixed Types
logger.debug(
'Request processed',
{ method: 'POST', path: '/api/users' },
'in',
123,
'ms'
);
Terminal output:
Use in Services
Injectable Logger
import { Injectable, Logger } from '@bunty/common';
@Injectable()
export class UserService {
private logger = new Logger('UserService');
async createUser(data: CreateUserDto) {
this.logger.info('Creating user:', data.email);
try {
const user = await this.db.insert(usersTable, data);
this.logger.success('User created:', { id: user.id });
return user;
} catch (error) {
this.logger.error('Failed to create user:', error);
throw error;
}
}
async deleteUser(id: number) {
this.logger.warn('Deleting user:', id);
await this.db.delete(usersTable, id);
this.logger.success('User deleted');
}
}
Constructor Injection Pattern
import { Injectable, Logger } from '@bunty/common';
@Injectable()
export class OrderService {
private readonly logger: Logger;
constructor() {
this.logger = new Logger('OrderService');
}
async processOrder(orderId: number) {
this.logger.info('Processing order:', orderId);
const order = await this.fetchOrder(orderId);
this.logger.debug('Order fetched:', order);
await this.validateOrder(order);
this.logger.success('Order validated');
await this.chargeCustomer(order);
this.logger.success('Customer charged');
return order;
}
}
HTTP Request Logging
Log Incoming Requests
import { createApp, Logger } from '@bunty/common';
const app = createApp();
const logger = new Logger('HTTP');
app.use((req, res, next) => {
const start = Date.now();
logger.info(`${req.method} ${req.path}`);
res.on('finish', () => {
const duration = Date.now() - start;
logger.success(
`${req.method} ${req.path}`,
res.statusCode,
`${duration}ms`
);
});
next();
});
Terminal output:
Log Errors
app.use((error, req, res, next) => {
const logger = new Logger('ErrorHandler');
logger.error('Request failed:', {
method: req.method,
path: req.path,
error: error.message,
stack: error.stack
});
res.status(500).json({ error: 'Internal server error' });
});
Terminal output:
Worker Logging
import { createWorker, Injectable, Logger } from '@bunty/common';
@Injectable()
class SyncWorker {
private logger = new Logger('SyncWorker');
async onInit() {
this.logger.info('Worker initializing...');
}
async run() {
this.logger.info('Starting sync...');
try {
const records = await this.fetchRecords();
this.logger.debug('Fetched records:', records.length);
await this.processRecords(records);
this.logger.success('Sync completed');
} catch (error) {
this.logger.error('Sync failed:', error);
throw error;
}
}
async onShutdown() {
this.logger.warn('Worker shutting down...');
}
}
createWorker({
name: 'sync-worker',
interval: '5m',
tasks: [SyncWorker]
}).start();
Terminal output:
Database Query Logging
import { Injectable, Logger } from '@bunty/common';
@Injectable()
export class ProductService {
private logger = new Logger('ProductService');
async findProducts(filters: ProductFilters) {
this.logger.debug('Query filters:', filters);
const query = this.db
.createQueryBuilder(productsTable)
.where('category', '=', filters.category);
this.logger.debug('Generated SQL:', query.toSQL());
const products = await query.findMany();
this.logger.info('Found products:', products.length);
return products;
}
}
Custom Log Methods
Extend Logger
class CustomLogger extends Logger {
// Custom log level for API calls
api(method: string, url: string, status: number) {
this.writeLine(
'API',
Color.BrightMagenta,
method,
url,
status
);
}
// Custom log level for metrics
metric(name: string, value: number, unit: string) {
this.writeLine(
'METRIC',
Color.BrightYellow,
name,
value,
unit
);
}
}
const logger = new CustomLogger('App');
logger.api('GET', '/api/users', 200);
logger.metric('response_time', 42, 'ms');
Colors
The logger provides access to ANSI color codes:
Standard Colors
import { Color, Logger } from '@bunty/common';
const logger = new Logger('App');
logger.write(Color.Red, 'This is red', Color.Reset, '\n');
logger.write(Color.Green, 'This is green', Color.Reset, '\n');
logger.write(Color.Yellow, 'This is yellow', Color.Reset, '\n');
logger.write(Color.Cyan, 'This is cyan', Color.Reset, '\n');
Bright Colors
logger.write(Color.BrightRed, 'Bright red', Color.Reset, '\n');
logger.write(Color.BrightGreen, 'Bright green', Color.Reset, '\n');
logger.write(Color.BrightYellow, 'Bright yellow', Color.Reset, '\n');
256 Color Palette
import { Color256, Logger } from '@bunty/common';
const logger = new Logger('App');
logger.write(Color256[196], 'Custom color!', Color.Reset, '\n');
logger.write(Color256[93], 'Another color!', Color.Reset, '\n');
Performance Considerations
Conditional Logging
const logger = new Logger('Service');
// Debug logs are skipped if debug flag is disabled
logger.debug('Expensive operation:', computeExpensiveData());
// Better: only compute if debug is enabled
if (logger.getFlags() & LogFlags.Debug) {
logger.debug('Expensive operation:', computeExpensiveData());
}
Lazy Evaluation
class SmartLogger extends Logger {
debugLazy(message: string, dataFn: () => any) {
if (this.getFlags() & LogFlags.Debug) {
this.debug(message, dataFn());
}
}
}
const logger = new SmartLogger('Service');
// Data function only runs if debug is enabled
logger.debugLazy('Complex data:', () => {
return expensiveComputation();
});
Testing with Logger
Mock Logger
class MockLogger extends Logger {
logs: { level: string; messages: any[] }[] = [];
info(...messages: any[]) {
this.logs.push({ level: 'info', messages });
}
error(...messages: any[]) {
this.logs.push({ level: 'error', messages });
}
// ... other methods
clear() {
this.logs = [];
}
}
// In tests
const mockLogger = new MockLogger('Test');
const service = new UserService(mockLogger);
await service.createUser({ email: 'test@example.com' });
expect(mockLogger.logs).toContainEqual({
level: 'info',
messages: ['Creating user:', 'test@example.com']
});
Silent Logger
import { Logger, LogFlags } from '@bunty/common';
// Disable all logging in tests
const logger = new Logger('Test', LogFlags.None);
Best Practices
✅ Do
- Create scoped loggers for each service/module
- Use appropriate log levels (info, error, debug, etc.)
- Log important state changes and operations
- Include relevant context in log messages
- Use structured data (objects) for complex information
- Disable debug logs in production
- Log errors with full stack traces
- Use consistent naming for logger scopes
❌ Don’t
- Log sensitive data (passwords, tokens, API keys)
- Log excessively in hot paths (loops, frequent operations)
- Use console.log() directly (use Logger instead)
- Leave debug logs enabled in production
- Log everything (be selective)
- Ignore error logging
- Use vague log messages (“something happened”)
- Create new logger instances for every method call
Example: Complete Service
import { Injectable, Logger, LogFlags, env } from '@bunty/common';
@Injectable()
export class PaymentService {
private readonly logger: Logger;
constructor(
private stripe: StripeAPI,
private db: Database
) {
this.logger = new Logger('PaymentService');
// Configure based on environment
if (env('NODE_ENV') === 'production') {
this.logger.setFlags(LogFlags.Info | LogFlags.Success | LogFlags.Error);
} else {
this.logger.setFlags(LogFlags.All);
}
}
async processPayment(orderId: number, amount: number) {
this.logger.info('Processing payment:', { orderId, amount });
try {
// Validate order
const order = await this.db.getOrder(orderId);
this.logger.debug('Order fetched:', order);
if (!order) {
this.logger.warn('Order not found:', orderId);
throw new Error('Order not found');
}
// Charge customer
this.logger.info('Charging customer via Stripe');
const charge = await this.stripe.charge({
amount,
currency: 'usd',
source: order.paymentToken
});
this.logger.debug('Stripe response:', charge);
// Update order
await this.db.updateOrder(orderId, {
status: 'paid',
chargeId: charge.id
});
this.logger.success('Payment processed successfully', {
orderId,
chargeId: charge.id
});
return charge;
} catch (error) {
this.logger.error('Payment failed:', error);
throw error;
}
}
}
📝 Logging Summary
Bunty’s built-in logger provides structured, colored console output with timestamps, scopes, and configurable log levels. Create scoped loggers for each service, control verbosity with bitwise flags, and get automatic formatting for objects, errors, and primitives. Perfect for development debugging and production monitoring without external dependencies.
Next Steps
- Learn about Dependency Injection for service architecture
- Build Workers with proper logging
- Understand Core Concepts and design patterns