← All posts

Structured logging in Next.js without the noise

A complete guide to logging from Next.js App Router, Route Handlers, and server actions with LoggerMan — tokens, metadata, environments, and alerts.

Next.jsSDKStructured logging

Why structure matters

Unstructured `console.log` output breaks down the moment you add a second service or a second developer. Search becomes guesswork, alerts fire on the wrong strings, and post-mortems turn into archaeology.

LoggerMan treats each event as a **typed log line** with an optional **metadata** object — the same model as our REST ingest API and the @marvink02/loggerman-sdk. If you are new to the product, start with the quick start guide before wiring production traffic.

One project per deployable

A common mistake is sharing one ingest token across staging, preview, and production. That collapses environments in Analytics and makes alert rules meaningless.

Instead:

  1. Create a project per environment (or pass `environment` from the SDK — see integrations).
  2. Copy **project ID**, **token**, and **base URL** from Settings → Integration in the dashboard.
  3. Store them in deployment secrets:
LOGGERMAN_PROJECT_ID=proj_...
LOGGERMAN_TOKEN=lg_...
LOGGERMAN_BASE_URL=https://your-app.example

Never commit tokens to git. Rotate via API keys if a preview deployment leaks.

Install the SDK

npm install @marvink02/loggerman-sdk

For App Router middleware correlation, import from `@marvink02/loggerman-sdk/next`. Full surface area is documented under Logger SDK.

Log at boundaries, not everywhere

High-signal logging focuses on **edges**:

  • **HTTP in / out** — method, path, status, `durationMs`, authenticated user id (hashed if needed).
  • **Errors** — one ERROR line with a stable `code` and serialized `err` metadata; avoid duplicate stack traces in three log levels.
  • **Background work** — `job.started` / `job.completed` pairs with the same `jobId`.
  • **External calls** — provider name, latency, and outcome — not full response bodies (PII risk; see security).

Example: Route Handler

import { createLogger } from "@marvink02/loggerman-sdk";

const logger = createLogger({ projectId: process.env.LOGGERMAN_PROJECT_ID!, token: process.env.LOGGERMAN_TOKEN!, baseUrl: process.env.LOGGERMAN_BASE_URL!, source: "api", environment: process.env.VERCEL_ENV ?? "development", });

export async function POST(req: Request) { const started = Date.now(); try { // ... work await logger.info("checkout.completed", { orderId: "ord_1" }); return Response.json({ ok: true }); } catch (err) { await logger.error("checkout.failed", { code: "CHECKOUT_FAILED", err: err instanceof Error ? err.message : String(err), durationMs: Date.now() - started, }); throw err; } } ```

Metadata rules that keep ingest reliable

Our API rejects payloads that violate shape limits (documented in API reference):

  • `metadata` must be a **plain JSON object**, not an array or string.
  • Keep messages human-readable; put IDs and metrics in metadata.
  • Stay under size limits — large blobs belong in object storage, not logs.

Environment tags vs separate projects

Passing `environment: "preview"` lets Analytics compare preview vs production inside one project. Separate projects are better when retention, alerts, or team access must differ. Read projects & settings for the trade-off.

Wire alerts after the first ERROR

Once logs flow, create a spike rule on ERROR rate (alert fatigue guide) and send a test line from Integration. Confirm **one** notification, then tune cooldowns.

Related reading