📝 Logging Utilities
The Nodeblocks SDK provides pre-configured logging setup with Pino for structured logging. These utilities offer consistent logging across your application with pretty formatting and HTTP request/response logging.
🎯 Overview
Logging utilities provide a standardized logging setup using Pino. Outside test mode, nodeblocksLogger uses pino-pretty for colorized output in all environments (including production). For handler-level logging with redaction, see withLogging.
Key Features
- Pre-configured Logger: Ready-to-use Pino logger with
pino-prettyformatting (non-test) - HTTP Logging: Express middleware via
pino-httpwithautoLogging: true - TypeScript Support: Exported
Loggertype alias (pino.Logger)
The module exports three symbols: nodeblocksLogger, nodeblocksHTTPLogger, and Logger (type).
📝 Basic Logging
nodeblocksLogger
Pre-configured Pino logger with pretty formatting and colorized output.
import { utils } from '@nodeblocks/backend-sdk';
const { nodeblocksLogger } = utils;
// Basic logging
nodeblocksLogger.info('Application started');
nodeblocksLogger.warn('Deprecated feature used');
nodeblocksLogger.error('An error occurred', { error: 'details' });
// Structured logging
nodeblocksLogger.info({
message: 'User created',
userId: 'user-123',
timestamp: new Date().toISOString()
});
Log Levels
import { utils } from '@nodeblocks/backend-sdk';
const { nodeblocksLogger } = utils;
// Available log levels
nodeblocksLogger.trace('Trace level - most detailed');
nodeblocksLogger.debug('Debug level - development info');
nodeblocksLogger.info('Info level - general information');
nodeblocksLogger.warn('Warn level - warnings');
nodeblocksLogger.error('Error level - errors');
nodeblocksLogger.fatal('Fatal level - critical errors');
🌐 HTTP Logging
nodeblocksHTTPLogger
HTTP request/response logging middleware for Express applications.
import express from 'express';
import { utils } from '@nodeblocks/backend-sdk';
const { nodeblocksHTTPLogger } = utils;
const app = express();
// Add HTTP logging middleware
app.use(nodeblocksHTTPLogger);
// Your routes
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
HTTP Log Output
With default pino-http serializers, the HTTP logger typically records:
- Request: method, URL, remote address, selected headers (e.g.
user-agent) - Response: status code, response time
- Message: e.g.
"request completed"
Request body is not logged by default. Use custom serializers if you need body logging.
Example output (approximate):
{
"req": {
"id": "req-1",
"method": "GET",
"url": "/api/users",
"headers": {
"user-agent": "Mozilla/5.0...",
"accept": "application/json"
}
},
"res": {
"statusCode": 200,
"responseTime": 45
},
"msg": "request completed"
}
🔧 Advanced Usage
The examples below are illustrative patterns built on SDK utilities — they are not additional SDK exports. For handler logging with automatic redaction, use withLogging from Handler Wrappers.
Custom Logger Configuration
import pino from 'pino';
import { utils } from '@nodeblocks/backend-sdk';
const { nodeblocksLogger } = utils;
// Extend the default logger
const customLogger = nodeblocksLogger.child({
service: 'user-service',
version: '1.0.0'
});
// Use custom logger
customLogger.info('Service started', {
port: 3000,
environment: process.env.NODE_ENV
});
Logger in Handlers
Handler examples use symbols from other SDK modules (RouteHandlerPayload from primitives, ok from neverthrow, etc.).
import { ok } from 'neverthrow';
import { handlers, primitives, utils } from '@nodeblocks/backend-sdk';
const { nodeblocksLogger } = utils;
const { mergeData } = handlers;
type RouteHandlerPayload = primitives.RouteHandlerPayload;
const createUserHandler = async (payload: RouteHandlerPayload) => {
const { params, logger } = payload;
// Use logger from payload or fallback to default
const log = logger || nodeblocksLogger;
log.info('Creating user', {
email: params.requestBody?.email,
timestamp: new Date().toISOString()
});
try {
// Handler logic
const user = await createUser(params.requestBody);
log.info('User created successfully', {
userId: user.id,
email: user.email
});
return ok(mergeData(payload, { user }));
} catch (error) {
log.error('Failed to create user', {
error: error.message,
email: params.requestBody?.email
});
throw error;
}
};
Conditional Logging
import { utils } from '@nodeblocks/backend-sdk';
const { nodeblocksLogger } = utils;
const getConditionalLogger = (isDevelopment: boolean) => {
// A child logger adds bindings; it does not change the logger's level.
return nodeblocksLogger.child({
environment: isDevelopment ? 'development' : 'production',
});
};
// Usage
const logger = getConditionalLogger(process.env.NODE_ENV === 'development');
logger.debug('Debug info, if enabled by the logger level');
Performance Logging
import { utils } from '@nodeblocks/backend-sdk';
const { nodeblocksLogger } = utils;
const performanceHandler = async (payload: RouteHandlerPayload) => {
const startTime = Date.now();
const log = payload.logger || nodeblocksLogger;
log.info('Starting performance-critical operation');
try {
// Expensive operation
const result = await expensiveOperation();
const duration = Date.now() - startTime;
log.info('Operation completed', {
duration,
resultSize: result.length
});
return ok(mergeData(payload, { result }));
} catch (error) {
const duration = Date.now() - startTime;
log.error('Operation failed', {
duration,
error: error.message
});
throw error;
}
};
📊 Logger Configuration
Default Configuration (non-test)
When not running under Vitest or NODE_ENV === 'test', nodeblocksLogger is configured as:
{
enabled: !process.env.DISABLE_LOGGING,
level: 'trace',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
singleLine: false,
translateTime: 'SYS:standard',
},
},
}
There is no separate production branch — non-test environments always use the pino-pretty transport worker.
Test Environment
When process.env.VITEST is set or NODE_ENV === 'test', the logger switches to a test-friendly setup:
pino(
{
base: null,
enabled: !process.env.DISABLE_LOGGING,
level: 'error',
},
pretty({
colorize: true,
singleLine: false,
translateTime: 'SYS:standard',
})
)
This writes pretty output directly to stdout (visible in Vitest) instead of using a transport worker thread.
Disabling Logging
Set the DISABLE_LOGGING environment variable to disable logger output in either branch (enabled: false).
Custom Logger (example pattern)
The SDK does not switch config by NODE_ENV for production. To use structured JSON logging in production, create your own pino instance:
import pino from 'pino';
const customLogger = pino({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
// Production: structured JSON to stdout (no pino-pretty)
});
🔗 Logger Types
Logger Type
TypeScript type alias for pino.Logger. It is a type export, not a runtime value — do not destructure it from utils.
import { utils } from '@nodeblocks/backend-sdk';
type Logger = utils.Logger;
// Or import directly from pino (as SDK internals often do):
import type { Logger } from 'pino';
const useLogger = (logger: Logger) => {
logger.info('Using typed logger');
logger.error('Error with logger', { error: 'details' });
};
// Usage
useLogger(utils.nodeblocksLogger);
Logger in Payload
Handlers receive a logger in the payload:
interface RouteHandlerPayload {
// ... other properties
logger?: Logger;
}
📐 Best Practices
1. Structured Logging
// ✅ Good: Structured logging with context
nodeblocksLogger.info({
message: 'User action performed',
userId: 'user-123',
action: 'login',
timestamp: new Date().toISOString(),
metadata: { ip: '192.168.1.1' }
});
// ❌ Avoid: Simple string logging
nodeblocksLogger.info('User logged in'); // Missing context
2. Error Logging
// ✅ Good: Comprehensive error logging
try {
await riskyOperation();
} catch (error) {
nodeblocksLogger.error({
message: 'Operation failed',
error: error.message,
stack: error.stack,
context: { userId: 'user-123' }
});
throw error;
}
// ❌ Avoid: Minimal error logging
try {
await riskyOperation();
} catch (error) {
nodeblocksLogger.error('Error'); // Missing details
}
3. Performance Logging
// ✅ Good: Performance monitoring
const startTime = Date.now();
const result = await expensiveOperation();
const duration = Date.now() - startTime;
nodeblocksLogger.info({
message: 'Operation completed',
duration,
resultSize: result.length,
operation: 'expensiveOperation'
});
4. Conditional Logging
// ✅ Good: Environment-aware logging
import pino from 'pino';
const logLevel = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
// Configure the level on a logger instance; `child({ level })` would only add a binding.
const logger = pino({ level: logLevel });
logger.debug('Debug info only in development');
logger.info('Info in all environments');
🔗 See Also
- Handler Wrappers -
withLoggingfor handler-level logging with redaction - Handler Component - Using loggers in handlers
- Error Handling - Error logging patterns