Skip to main content
Version: 🚧 Canary

📝 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-pretty formatting (non-test)
  • HTTP Logging: Express middleware via pino-http with autoLogging: true
  • TypeScript Support: Exported Logger type alias (pino.Logger)

The module exports three symbols: nodeblocksLogger, nodeblocksHTTPLogger, and Logger (type).

SDK 0.15.0 uses Pino 10 and pino-http 11; the exported Logger alias comes from Pino 10. Pino 10 drops Node.js 18 support. The quickstart targets Node.js 22.x or 24.x.


📝 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({ error: 'details' }, 'An error occurred');

// 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​

When upgrading to SDK 0.15.0, upgrade an application-supplied logger to pino@^10 so it matches the SDK's Logger type:

npm install pino@^10

An SDK logger child already uses the SDK's Pino version. See the 0.15.0 migration notes for the related runtime and dependency changes.

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({
port: 3000,
environment: process.env.NODE_ENV
}, 'Service started');

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({
email: params.requestBody?.email,
timestamp: new Date().toISOString()
}, 'Creating user');

try {
// Handler logic
const user = await createUser(params.requestBody);

log.info({
userId: user.id,
email: user.email
}, 'User created successfully');

return ok(mergeData(payload, { user }));
} catch (error) {
log.error({
error: error instanceof Error ? error.message : String(error),
email: params.requestBody?.email
}, 'Failed to create user');

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({
duration,
resultSize: result.length
}, 'Operation completed');

return ok(mergeData(payload, { result }));
} catch (error) {
const duration = Date.now() - startTime;
log.error({
duration,
error: error instanceof Error ? error.message : String(error)
}, 'Operation failed');

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: 'details' }, 'Error with logger');
};

// Usage
useLogger(utils.nodeblocksLogger);

Logger in Payload​

Handlers may receive a logger in the payload. withLogging injects it; defService does not:

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 instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
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​