Fastify Integration
Capture errors and request context from Fastify applications.
Installation
npm install @statly/observeSetup
Register the Statly plugin:
import Fastify from 'fastify';
import { init } from '@statly/observe';
import { statlyFastifyPlugin } from '@statly/observe/fastify';
// Initialize SDK first
init({
dsn: 'https://[email protected]/your-org',
environment: 'production',
});
const fastify = Fastify();
// Register plugin
await fastify.register(statlyFastifyPlugin, {
// Options
});
// Your routes
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
await fastify.listen({ port: 3000 });Plugin Options
interface StatlyFastifyPluginOptions {
// Capture Fastify validation errors
captureValidationErrors?: boolean; // Default: true
// Custom filter for which errors to capture
shouldCapture?: (error: Error, request: FastifyRequest) => boolean;
// Status codes to skip (not capture)
skipStatusCodes?: number[]; // Default: [400, 401, 403, 404]
}Examples
// Capture everything including 4xx
await fastify.register(statlyFastifyPlugin, {
skipStatusCodes: [],
});
// Only capture 5xx errors
await fastify.register(statlyFastifyPlugin, {
skipStatusCodes: [400, 401, 403, 404, 422],
});
// Custom filter
await fastify.register(statlyFastifyPlugin, {
shouldCapture: (error, request) => {
// Skip health check errors
if (request.url === '/health') return false;
// Skip rate limiting
if (error.statusCode === 429) return false;
return true;
},
});What's Captured
Request Context
The plugin automatically captures:
- HTTP method and URL
- Query parameters
- Headers (sanitized)
- Client IP address
- Request duration
Hooks
The plugin registers these Fastify hooks:
onRequest: Records start time, adds request breadcrumb
onResponse: Adds response breadcrumb with status and duration
setErrorHandler: Captures unhandled errors
Request-Scoped Capture
Use createRequestCapture for request-specific error capture:
import { createRequestCapture } from '@statly/observe/fastify';
fastify.get('/api/orders/:id', async (request, reply) => {
const capture = createRequestCapture(request);
try {
const order = await getOrder(request.params.id);
return order;
} catch (error) {
// Capture with request context automatically
capture(error, {
orderId: request.params.id,
});
throw error;
}
});With TypeScript
Full TypeScript support:
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
import { init } from '@statly/observe';
import {
statlyFastifyPlugin,
StatlyFastifyPluginOptions,
} from '@statly/observe/fastify';
init({
dsn: process.env.STATLY_DSN!,
environment: process.env.NODE_ENV,
});
const fastify = Fastify({
logger: true,
});
const options: StatlyFastifyPluginOptions = {
captureValidationErrors: true,
skipStatusCodes: [400, 401, 403, 404],
};
await fastify.register(statlyFastifyPlugin, options);
fastify.get<{
Params: { id: string };
}>('/users/:id', async (request, reply) => {
const user = await getUser(request.params.id);
return user;
});Manual Capture
Capture errors manually with context:
import { captureException, addBreadcrumb } from '@statly/observe';
fastify.post('/api/payments', async (request, reply) => {
addBreadcrumb({
category: 'payment',
message: 'Processing payment',
data: { amount: request.body.amount },
});
try {
const result = await processPayment(request.body);
return result;
} catch (error) {
captureException(error, {
paymentMethod: request.body.method,
amount: request.body.amount,
});
reply.status(500);
return { error: 'Payment failed' };
}
});Full Example
import Fastify from 'fastify';
import { init, setUser, addBreadcrumb } from '@statly/observe';
import { statlyFastifyPlugin } from '@statly/observe/fastify';
init({
dsn: process.env.STATLY_DSN,
environment: process.env.NODE_ENV,
});
const fastify = Fastify();
await fastify.register(statlyFastifyPlugin, {
captureValidationErrors: true,
skipStatusCodes: [400, 401, 403, 404],
});
// Authentication hook
fastify.addHook('preHandler', async (request) => {
if (request.user) {
setUser({
id: request.user.id,
email: request.user.email,
});
}
});
// Routes
fastify.get('/api/users', async () => {
return await db.users.findMany();
});
fastify.post('/api/users', async (request) => {
addBreadcrumb({
category: 'user',
message: 'Creating user',
});
return await db.users.create(request.body);
});
await fastify.listen({ port: 3000 });