Menu

Pino Logging & Sentry Error Monitoring

NEXTY.DEV ships a production-grade logging and error monitoring solution built on Pino + Sentry.

Architecture Overview

Repository

  └── getLogger("module-name")

        ├── Pino (structured logging)
        │     ├── Development → colorized output (pino-pretty)
        │     ├── Production → JSON to stdout (collected by cloud platform)
        │     └── VPS + LOG_DIR → file rotation (pino-roll)

        └── Sentry (error reporting)
              ├── warn  → add Breadcrumb
              ├── error → captureException
              └── fatal → captureException (level: fatal)

Each tool has a clear responsibility:

  • Pino handles all log output — high-performance, structured JSON
  • Sentry handles error aggregation, alerting, stack traces, and user impact analysis

Environment Variables

All variables are optional. When not configured, all Sentry calls are silently ignored, and Pino defaults to info level output to stdout.

Sentry

NEXT_PUBLIC_SENTRY_DSN

How to get it:

  1. Log in to sentry.io and open your project (or create a new Next.js project)
  2. Left sidebar → SettingsProjects → select your project
  3. Left sidebar → Client Keys (DSN)
  4. Copy the DSN value — it looks like https://[email protected]/xxx
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/789
dsn
dsn

SENTRY_AUTH_TOKEN (optional, for source map uploads)

How to get it:

  1. Log in to sentry.io → left sidebar SettingsOrganization Tokens
  2. Click Create New Token
  3. Copy the generated token (it starts with sntrys_)
SENTRY_AUTH_TOKEN=sntrys_eyJ0eXBlIjoiYXV0aF90b2tlbiJ9...
org token

SENTRY_ORG and SENTRY_PROJECT (used with AUTH_TOKEN)

How to get them:

  • SENTRY_ORG: SettingsGeneral SettingsOrganization Slug (also the org name in your sentry.io URL, e.g. my-company from https://my-company.sentry.io)
  • SENTRY_PROJECT: SettingsProjects → click your project → Project Slug
SENTRY_ORG=my-company
SENTRY_PROJECT=my-nextjs-app

SENTRY_DEBUG (optional)

By default, Sentry does not report events in development (to avoid polluting your data). Set this to true to enable real event reporting in development — useful when debugging the Sentry integration itself.

SENTRY_DEBUG=true

Pino

LOG_LEVEL

Controls which log levels are emitted. Anything below this level is suppressed. Accepted values: trace | debug | info | warn | error | fatal.

LOG_LEVEL=info   # recommended for production
LOG_LEVEL=debug  # use temporarily when troubleshooting

LOG_DIR (optional, VPS only)

When set, enables pino-roll file rotation and writes logs to this directory. Leave it unset on Vercel and Cloudflare Workers — those platforms collect stdout automatically.

LOG_DIR=./logs          # relative to the project root
LOG_DIR=/var/log/myapp  # or an absolute path

Usage

Basic Usage

import { getLogger } from "@/lib/logger";
 
const logger = getLogger("payment-service");
 
// trace / debug: for development debugging, suppressed in production by default
logger.trace({ sql: "SELECT ..." }, "Executing query");
logger.debug({ params }, "Request received");
 
// info: normal business events
logger.info({ userId, planId }, "User subscribed");
 
// warn: recoverable issues (also adds a Sentry Breadcrumb)
logger.warn({ retryCount: 3, url }, "Webhook delivery failed, retrying");
 
// error: errors (automatically reported to Sentry)
logger.error(new Error("Stripe API timeout"), "Payment failed");
 
// fatal: critical failures (automatically reported to Sentry at fatal level)
logger.fatal(new Error("DB connection lost"), "Service unavailable");

Errors with Context

// Pass an Error object directly
logger.error(new Error("Invalid signature"), "Webhook verification failed");
 
// Pass an object — when it contains an `err` field, it's automatically extracted and sent to Sentry
logger.error({ err: error, webhookId, payload }, "Webhook processing error");
 
// Manual capture: equivalent to error(), but more explicit
logger.captureError(error, { userId, action: "checkout" });

Accessing the Underlying Pino Logger

When you need to create a child logger or use advanced Pino features:

const logger = getLogger("api");
const childLogger = logger.pino.child({ requestId: "req-123" });
childLogger.info("Request started");

In a Server Action

// app/actions/payment.ts
import { getLogger } from "@/lib/logger";
 
const logger = getLogger("payment-action");
 
export async function createCheckout(planId: string) {
  try {
    const session = await stripe.checkout.sessions.create({ ... });
    logger.info({ planId, sessionId: session.id }, "Checkout session created");
    return { url: session.url };
  } catch (error) {
    logger.error(error instanceof Error ? error : new Error(String(error)), "Checkout failed");
    throw error;
  }
}

In a Route Handler

// app/api/webhooks/stripe/route.ts
import { getLogger } from "@/lib/logger";
 
const logger = getLogger("stripe-webhook");
 
export async function POST(request: Request) {
  try {
    // ...
    logger.info({ event: event.type }, "Webhook processed");
    return Response.json({ ok: true });
  } catch (error) {
    logger.error(error instanceof Error ? error : new Error(String(error)), "Webhook error");
    return Response.json({ error: "Internal error" }, { status: 500 });
  }
}

Log Levels

LevelWhen to useSentry behavior
traceSQL queries, detailed execution pathsNone
debugRequest parameters, intermediate stateNone
infoUser actions, order creation, loginNone
warnRetries, degraded mode, rate limitingAdds Breadcrumb
errorAPI failures, database errors, payment failuresReports exception
fatalService unavailable, initialization failureReports exception (fatal level)

The default production level is info, so trace and debug logs are suppressed. To enable them temporarily, set LOG_LEVEL=debug.

Deployment Guide

Vercel

No additional configuration needed. Stdout from Next.js functions is automatically forwarded to Vercel's logging system.

View logs: Vercel dashboard → project → LogsFunctions

NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# Leave LOG_DIR unset

Attention: Vercel function logs are only retained for 1 hour on the free plan. For production, pair this with Sentry as your primary error tracking tool.

Cloudflare Workers

Workers run on the Edge runtime with no filesystem. The logger automatically detects the Edge environment and falls back to pure JSON stdout mode (skipping pino-roll and pino-pretty).

View logs: Cloudflare dashboard → Workers & Pages → project → Logs

NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# LOG_DIR must be left unset — Cloudflare Workers has no filesystem

Real-time logs: During development, use wrangler tail to stream logs directly in your terminal.

VPS — Coolify

Coolify runs your app in a Docker container. Stdout is captured automatically by Docker and is viewable in the dashboard.

View stdout logs: Coolify dashboard → app → Logs tab (live streaming)

NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# Leave LOG_DIR unset — use stdout, viewable directly in the Coolify dashboard

To persist logs to a file:

  1. Coolify dashboard → app → StoragesAdd Storage
  2. Fill in:
    • Name: logs
    • Source Path (Host): path on the host machine, e.g. /var/lib/coolify/myapp/logs
    • Destination Path (Container): path inside the container, e.g. /app/logs
  3. Set the environment variable:
LOG_DIR=/app/logs
  1. After redeploying, log files will be written to /var/lib/coolify/myapp/logs/ on the host. You can follow them over SSH:
tail -f /var/lib/coolify/myapp/logs/payment-service.2025-03-08.1.log

VPS — Dokploy

Dokploy also runs on Docker. Stdout logs are viewable directly in the dashboard.

View stdout logs: Dokploy dashboard → app → Logs tab

NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# Leave LOG_DIR unset — use stdout

To persist logs to a file:

  1. Dokploy dashboard → app → AdvancedMountsAdd Mount
  2. Select Volume Mount and fill in:
    • Host Path: path on the host machine, e.g. /var/dokploy/myapp/logs
    • Container Path: path inside the container, e.g. /app/logs
  3. Set the environment variable:
LOG_DIR=/app/logs
  1. After redeploying, follow the logs over SSH:
tail -f /var/dokploy/myapp/logs/payment-service.2025-03-08.1.log

VPS — Node.js / PM2

Option A: PM2 (recommended — no LOG_DIR needed)

PM2 automatically collects stdout to a file with no code changes required:

pm2 start npm --name "myapp" -- start
pm2 logs myapp                         # stream logs in real time
# Log file location: ~/.pm2/logs/myapp-out.log

Option B: LOG_DIR file rotation

For setups without PM2:

LOG_DIR=./logs
# Log files: logs/payment-service.2025-03-08.1.log (rotated daily, split at 10 MB)

Pair this with a cron job to clean up old logs:

# Keep the last 30 days
0 2 * * * find /path/to/app/logs -name "*.log" -mtime +30 -delete

Best Practices

Use structured fields — avoid string interpolation

// Recommended: fields can be indexed and filtered by your logging platform
logger.info({ userId, amount, currency }, "Payment completed");
 
// Avoid: not searchable
logger.info({}, `User ${userId} paid ${amount} ${currency}`);

Keep sensitive data out of logs

// Never log passwords, tokens, or full card numbers
logger.info({ password, stripeKey }, "User data");
 
// Only log the non-sensitive identifiers you actually need
logger.info({ userId, last4: card.last4 }, "Payment method added");

Use a separate logger per module

const logger = getLogger("stripe-webhook");   // payment webhooks
const logger = getLogger("ai-chat");          // AI features
const logger = getLogger("auth");             // authentication

Do not use in client components

The logger depends on the Node.js/Edge runtime and must only be used server-side:

// Server Component, Server Action, Route Handler, middleware
import { getLogger } from "@/lib/logger";
 
// Do NOT use inside "use client" components

Client-side errors are captured automatically by the Sentry SDK configured in sentry.client.config.ts — no manual logger calls needed.

lib/
  logger/
    index.ts              # Logger core implementation
sentry.client.config.ts   # Client-side Sentry config (includes Session Replay)
sentry.server.config.ts   # Server-side Sentry config
sentry.edge.config.ts     # Edge runtime Sentry config
instrumentation.ts        # Next.js initialization hook
next.config.mjs           # serverExternalPackages + withSentryConfig