CentercodePorygon
DittoPorygonAPI

Logging

Structured logging with Pino

SafeReliable

Overview

Use logger from @/lib/logger for all server-side logging. Never use console.log. The logger provides structured output, automatic redaction, and log levels.

What it is

A Pino-based structured logger with automatic sensitive data redaction and environment-aware log levels.

Why we use it

Structured logs are searchable, redaction prevents leaking secrets, and log levels reduce noise in production.

When to use

Errors, authentication events, external API calls, database errors. Avoid logging success paths or in tight loops.

Key Features

  • Structured JSON logging for parsing
  • Automatic redaction of passwords, tokens, keys
  • Log levels (error, warn, info, debug)
  • Child loggers with inherited context

Quick Start

Basic Logging

Using the logger in an API route.

// Using logger in an API route
import { logger } from '@/lib/logger';

export async function POST(request: Request) {
  logger.info('Processing request', { endpoint: '/api/users' });

  try {
    const user = await createUser(data);
    logger.info('User created', { userId: user.id });
    return NextResponse.json({ ok: true, data: user });
  } catch (error) {
    logger.error('Failed to create user', { error, data });
    return handleError(error);
  }
}

Patterns

Log Levels

When to use each log level.

// Log levels and when to use them
import { logger } from '@/lib/logger';

// ERROR: Application errors that need attention
logger.error('Database connection failed', { host, error });

// WARN: Unexpected but recoverable situations
logger.warn('Rate limit approaching', { userId, remaining: 5 });

// INFO: Notable events in normal operation
logger.info('User logged in', { userId, method: 'oauth' });

// DEBUG: Detailed info for troubleshooting (dev only)
logger.debug('Query executed', { sql, duration: 45 });

Adding Context

Including structured data in logs.

// Adding structured context
logger.info('Payment processed', {
  userId: user.id,
  amount: 99.99,
  currency: 'USD',
  paymentMethod: 'stripe',
  transactionId: 'tx_123',
});

// Output (JSON in production):
{
  "level": "info",
  "message": "Payment processed",
  "userId": "usr_abc",
  "amount": 99.99,
  "currency": "USD",
  ...
}

Child Loggers

Creating loggers with inherited context.

// Child loggers inherit context
const requestLogger = logger.child({
  requestId: crypto.randomUUID(),
  userId: auth.user.id,
});

// All logs from this logger include requestId and userId
requestLogger.info('Starting operation');
requestLogger.info('Step 1 complete');
requestLogger.info('Operation finished');

Watch Out

Using console.log instead of logger

Don't

// Using console.log
console.log('User created:', user);
console.error('Something failed');

Do

// Using structured logger
logger.info('User created', { userId: user.id });
logger.error('Operation failed', { error, context });

Logging passwords, tokens, or personal data

Don't

// Logging sensitive data
logger.info('User login', {
  email: user.email,
  password: password, // NEVER!
  token: authToken,   // NEVER!
});

Do

// Log safely - avoid sensitive data entirely
logger.info('User login', {
  userId: user.id,
  method: 'credentials', // Describe action, not value
});
// Keys named password, token, secret, apiKey are auto-redacted
  • Logging every successful request
  • Logging inside loops or hot paths

Related

Error Handling

Error handling patterns

Monitoring

Observability and alerting