Back to recipes

Logging Best Practices

Use structured logging with Pino throughout your application. Covers log levels, context, and workflow-safe logging patterns.

Skills

Install this skill

bunx skills add andrelandgraf/fullstackrecipes/skills -s logging-best-practices

Installs the skill so your agent retains these patterns for day-to-day work.

Logging

Import logger from @/lib/logging/logger. Pass a context object first, message second. For errors, put err in the context object.

typescript
import { logger } from "@/lib/logging/logger";

logger.info({ port: 3000 }, "Server started");
logger.warn({ endpoint: "/api/chat" }, "Rate limit reached");
logger.debug({ key: "user:123" }, "Cache miss");
logger.error({ err, userId: "123", endpoint: "/api/chat" }, "Request failed");

Levels

LevelWhen to Use
traceDetailed debugging (rarely used)
debugDevelopment troubleshooting
infoNormal operations, business events
warnRecoverable issues, deprecation warnings
errorFailures that need attention
fatalCritical failures, app cannot continue

Set the active threshold via LOG_LEVEL (defaults to info). Use warn in production.

env
LOG_LEVEL="debug"

In API Routes

Log on the way out with timing context.

typescript
import { logger } from "@/lib/logging/logger";

export async function POST(request: Request) {
  const start = Date.now();
  try {
    const result = await processRequest(request);
    logger.info(
      { duration: Date.now() - start, status: 200 },
      "Request completed",
    );
    return Response.json(result);
  } catch (err) {
    logger.error({ err, duration: Date.now() - start }, "Request failed");
    return Response.json({ error: "Internal error" }, { status: 500 });
  }
}

In Workflows

The workflow runtime can't import Node modules, so the logger can't be called directly. Wrap it in a "use step" function.

ts
import { logger } from "@/lib/logging/logger";

export async function log(
  level: "info" | "warn" | "error" | "debug",
  message: string,
  data?: Record<string, unknown>,
): Promise<void> {
  "use step";

  if (data) {
    logger[level](data, message);
  } else {
    logger[level](message);
  }
}
typescript
import { log } from "./steps/logger";

export async function chatWorkflow({ chatId }) {
  "use workflow";

  await log("info", "Workflow started", { chatId });
}

References