Fastify best practices skill
93
97%
Does it follow best practices?
Impact
85%
1.37xAverage score across 4 eval scenarios
Passed
No known issues
Fastify uses Pino for high-performance logging:
import Fastify from 'fastify';
const app = Fastify({
logger: true, // Enable default logging
});
// Or with configuration
const app = Fastify({
logger: {
level: 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
},
});Available log levels (in order of severity):
app.log.trace('Detailed debugging');
app.log.debug('Debugging information');
app.log.info('General information');
app.log.warn('Warning messages');
app.log.error('Error messages');
app.log.fatal('Fatal errors');Each request has its own logger with request context:
app.get('/users/:id', async (request) => {
// Logs include request ID automatically
request.log.info('Fetching user');
const user = await db.users.findById(request.params.id);
if (!user) {
request.log.warn({ userId: request.params.id }, 'User not found');
return { error: 'Not found' };
}
request.log.info({ userId: user.id }, 'User fetched');
return user;
});Always use structured logging with objects:
// GOOD - structured, searchable
request.log.info({
action: 'user_created',
userId: user.id,
email: user.email,
}, 'User created successfully');
request.log.error({
err: error,
userId: request.params.id,
operation: 'fetch_user',
}, 'Failed to fetch user');
// BAD - unstructured, hard to parse
request.log.info(`User ${user.id} created with email ${user.email}`);
request.log.error(`Failed to fetch user: ${error.message}`);function getLoggerConfig() {
if (process.env.NODE_ENV === 'production') {
return {
level: 'info',
// JSON output for log aggregation
};
}
if (process.env.NODE_ENV === 'test') {
return false; // Disable logging in tests
}
// Development
return {
level: 'debug',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
};
}
const app = Fastify({
logger: getLoggerConfig(),
});Customize how objects are serialized:
const app = Fastify({
logger: {
level: 'info',
serializers: {
// Customize request serialization
req: (request) => ({
method: request.method,
url: request.url,
headers: {
host: request.headers.host,
'user-agent': request.headers['user-agent'],
},
remoteAddress: request.ip,
}),
// Customize response serialization
res: (response) => ({
statusCode: response.statusCode,
}),
// Custom serializer for users
user: (user) => ({
id: user.id,
email: user.email,
// Exclude sensitive fields
}),
},
},
});
// Use custom serializer
request.log.info({ user: request.user }, 'User action');Prevent logging sensitive information:
import Fastify from 'fastify';
const app = Fastify({
logger: {
level: 'info',
redact: {
paths: [
'req.headers.authorization',
'req.headers.cookie',
'body.password',
'body.creditCard',
'*.password',
'*.secret',
'*.token',
],
censor: '[REDACTED]',
},
},
});Create child loggers with additional context:
app.addHook('onRequest', async (request) => {
// Add user context to all logs for this request
if (request.user) {
request.log = request.log.child({
userId: request.user.id,
userRole: request.user.role,
});
}
});
// Service-level child logger
const userService = {
log: app.log.child({ service: 'UserService' }),
async create(data) {
this.log.info({ email: data.email }, 'Creating user');
// ...
},
};Customize automatic request logging:
const app = Fastify({
logger: true,
disableRequestLogging: true, // Disable default request/response logs
});
// Custom request logging
app.addHook('onRequest', async (request) => {
request.log.info({
method: request.method,
url: request.url,
query: request.query,
}, 'Request received');
});
app.addHook('onResponse', async (request, reply) => {
request.log.info({
statusCode: reply.statusCode,
responseTime: reply.elapsedTime,
}, 'Request completed');
});Properly log errors with stack traces:
app.setErrorHandler((error, request, reply) => {
// Log error with full details
request.log.error({
err: error, // Pino serializes error objects properly
url: request.url,
method: request.method,
body: request.body,
query: request.query,
}, 'Request error');
reply.code(error.statusCode || 500).send({
error: error.message,
});
});
// In handlers
app.get('/data', async (request) => {
try {
return await fetchData();
} catch (error) {
request.log.error({ err: error }, 'Failed to fetch data');
throw error;
}
});Configure where logs are sent:
import { createWriteStream } from 'node:fs';
// File output
const app = Fastify({
logger: {
level: 'info',
stream: createWriteStream('./app.log'),
},
});
// Multiple destinations with pino.multistream
import pino from 'pino';
const streams = [
{ stream: process.stdout },
{ stream: createWriteStream('./app.log') },
{ level: 'error', stream: createWriteStream('./error.log') },
];
const app = Fastify({
logger: pino({ level: 'info' }, pino.multistream(streams)),
});Use pino-roll for log rotation:
node app.js | pino-roll --frequency daily --extension .logOr configure programmatically:
import { createStream } from 'rotating-file-stream';
const stream = createStream('app.log', {
size: '10M', // Rotate every 10MB
interval: '1d', // Rotate daily
compress: 'gzip',
path: './logs',
});
const app = Fastify({
logger: {
level: 'info',
stream,
},
});Format logs for aggregation services:
// For ELK Stack, Datadog, etc. - use default JSON format
const app = Fastify({
logger: {
level: 'info',
// Default JSON output works with most log aggregators
},
});
// Add service metadata
const app = Fastify({
logger: {
level: 'info',
base: {
service: 'user-api',
version: process.env.APP_VERSION,
environment: process.env.NODE_ENV,
},
},
});Use request IDs for distributed tracing:
const app = Fastify({
logger: true,
requestIdHeader: 'x-request-id', // Use incoming header
genReqId: (request) => {
// Generate ID if not provided
return request.headers['x-request-id'] || crypto.randomUUID();
},
});
// Forward request ID to downstream services
app.addHook('onRequest', async (request) => {
request.requestId = request.id;
});
// Include in outgoing requests
const response = await fetch('http://other-service/api', {
headers: {
'x-request-id': request.id,
},
});Pino is fast, but consider:
// Avoid string concatenation in log calls
// BAD
request.log.info('User ' + user.id + ' did ' + action);
// GOOD
request.log.info({ userId: user.id, action }, 'User action');
// Use appropriate log levels
// Don't log at info level in hot paths
if (app.log.isLevelEnabled('debug')) {
request.log.debug({ details: expensiveToCompute() }, 'Debug info');
}