Most Developers Log Too Much or Too Little
Too much: walls of debug logs that obscure real errors.
Too little: production breaks and you have no idea why.
Here's the logging strategy that scales from prototype to production.
Structured Logging with Pino
npm install pino pino-pretty
// lib/logger.ts
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL ?? 'info',
...(process.env.NODE_ENV === 'development'
? { transport: { target: 'pino-pretty', options: { colorize: true } } }
: {})
})
export default logger
export const createLogger = (context: string) => logger.child({ context })
What to Log
const log = createLogger('stripe-webhook')
// Log structured data, not interpolated strings
log.info({ eventId: event.id, type: event.type }, 'Webhook received')
// Include request ID for tracing
log.info({
requestId,
userId: user.id,
plan: subscription.plan,
amount: invoice.amount_paid
}, 'Subscription payment processed')
// Error: always include the error object
log.error({ err, userId, eventId }, 'Failed to provision subscription')
// Debug only in development
log.debug({ query, params }, 'Database query')
What NOT to Log
// Never log:
log.info({ password: user.password }) // Credentials
log.info({ token: req.headers.authorization }) // Auth tokens
log.info({ card: payment.cardNumber }) // PCI data
log.info({ ssn: user.ssn }) // PII
log.info({ body: req.body }) // Entire request bodies
// Log IDs, not values:
log.info({ userId: user.id, email: user.email }) // Email is borderline -- check your compliance
log.info({ stripeCustomerId: customer.id }) // OK -- not the card
Request Tracing
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
export function middleware(request: NextRequest) {
const requestId = uuidv4()
const response = NextResponse.next()
// Pass request ID downstream
response.headers.set('x-request-id', requestId)
request.headers.set('x-request-id', requestId)
return response
}
// In API routes:
export async function POST(req: NextRequest) {
const requestId = req.headers.get('x-request-id') ?? uuidv4()
const log = logger.child({ requestId })
log.info({ path: req.nextUrl.pathname }, 'Request started')
// All subsequent logs include requestId automatically
}
Error Tracking with Sentry
npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config')
}
}
// Capture errors with context
import * as Sentry from '@sentry/nextjs'
try {
await processPayment(userId, amount)
} catch (err) {
Sentry.captureException(err, {
tags: { component: 'payment', userId },
extra: { amount, currency }
})
throw err // Re-throw -- don't swallow errors
}
Log Levels by Environment
Development: debug (everything)
Staging: info (requests, events, errors)
Production: warn + error only (reduce volume, reduce cost)
// .env.production
LOG_LEVEL=warn
// .env.development
LOG_LEVEL=debug
Alerting on Error Spikes
Sentry alerting rules:
- Error rate > 1% of requests: page on-call
- New error type first seen: Slack notification
- Payment errors any rate: immediate alert
Pre-Built Logging in the AI SaaS Starter Kit
Comes with Pino configured, request ID middleware, Sentry integration, and structured logging patterns throughout.
$99 one-time at whoffagents.com
Top comments (0)