Set Up Logs
Send structured logs from your Next.js application to Sentry for debugging and observability.
Sentry Logs let you send structured log data from your Next.js application. Unlike traditional string-based logging, structured logs include queryable attributes that help you debug issues faster by filtering on specific users, orders, or any business context you include.
Add enableLogs: true to your Sentry configuration in all three runtime files.
Logs work across all Next.js runtimes:
- Client — Browser-side logging
- Server — Node.js server-side logging
- Edge — Edge runtime logging
instrumentation-client.tsimport * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "___PUBLIC_DSN___",
enableLogs: true,
});
Use the appropriate level for each message:
| Level | When to Use |
|---|---|
trace | Fine-grained debugging |
debug | Development diagnostics |
info | Normal operations, milestones |
warn | Potential issues, degraded state |
error | Failures that need attention |
fatal | Critical failures, system down |
import * as Sentry from "@sentry/nextjs";
// Different log levels
Sentry.logger.trace("Entering function", { fn: "processOrder" });
Sentry.logger.debug("Cache lookup", { key: "user:123" });
Sentry.logger.info("Order created", { orderId: "order_456" });
Sentry.logger.warn("Rate limit approaching", { current: 95, max: 100 });
Sentry.logger.error("Payment failed", { reason: "card_declined" });
Sentry.logger.fatal("Database unavailable", { host: "primary" });
Pass structured data as the second argument. These attributes become queryable columns in Sentry.
Use the fmt helper for parameterized messages — values are extracted as searchable attributes.
// Pass attributes directly
Sentry.logger.info("User signed up", {
userId: user.id,
plan: "pro",
referrer: "google",
});
// Use fmt for parameterized messages
Sentry.logger.info(
Sentry.logger.fmt`User ${userId} purchased ${productName}`,
);
Set attributes on a scope to automatically include them in all logs within that context.
Scopes don't propagate between Next.js runtimes. To include attributes on logs from all three runtimes, call getIsolationScope().setAttribute() in each (client, edge, server). Use isolation scope (not global) to prevent data leaking between concurrent requests. For server-side code, set context before any try/catch blocks so errors include it.
See Different Kinds of Scopes to learn more.
// setAttribute() or setAttributes()
// Global scope - shared across entire app
Sentry.getGlobalScope().setAttributes({
service: "checkout",
version: "2.1.0",
});
// Isolation scope - unique per request
Sentry.getIsolationScope().setAttributes({
org_id: user.orgId,
user_tier: user.tier,
});
// Current scope - single operation
Sentry.withScope((scope) => {
scope.setAttribute("request_id", req.id);
Sentry.logger.info("Processing order");
});
Instead of many thin logs that are hard to correlate, emit one comprehensive log per operation with all relevant context.
This makes debugging dramatically faster — one query returns everything about a specific order, user, or request.
// ❌ Scattered thin logs
Sentry.logger.info("Starting checkout");
Sentry.logger.info("Validating cart");
Sentry.logger.info("Processing payment");
Sentry.logger.info("Checkout complete");
// ✅ One wide event with full context
Sentry.logger.info("Checkout completed", {
orderId: order.id,
userId: user.id,
userTier: user.subscription,
cartValue: cart.total,
itemCount: cart.items.length,
paymentMethod: "stripe",
duration: Date.now() - startTime,
});
Add attributes that help you prioritize and debug:
- User context — tier, account age, lifetime value
- Transaction data — order value, item count
- Feature state — active feature flags
- Request metadata — endpoint, method, duration
This lets you filter logs by high-value customers or specific features.
Sentry.logger.info("API request completed", {
// User context
userId: user.id,
userTier: user.plan, // "free" | "pro" | "enterprise"
accountAgeDays: user.ageDays,
// Request data
endpoint: "/api/orders",
method: "POST",
duration: 234,
// Business context
orderValue: 149.99,
featureFlags: ["new-checkout", "discount-v2"],
});
Pick a naming convention and stick with it across your codebase. Inconsistent names make queries impossible.
Recommended: Use snake_case for custom attributes to match common conventions.
// ❌ Inconsistent naming
{ user: "123" }
{ userId: "123" }
{ user_id: "123" }
{ UserID: "123" }
// ✅ Consistent snake_case
{
user_id: "123",
order_id: "456",
cart_value: 99.99,
item_count: 3,
}
Capture console.log, console.warn, and console.error calls as structured logs.
The integration parses multiple arguments as searchable attributes.
Sentry.init({
dsn: "___PUBLIC_DSN___",
enableLogs: true,
integrations: [
Sentry.consoleLoggingIntegration({
levels: ["log", "warn", "error"],
}),
],
});
// Arguments become searchable attributes
console.log("User action:", userId, success);
// -> message.parameter.0: userId
// -> message.parameter.1: success
Sentry.init({
dsn: "___PUBLIC_DSN___",
enableLogs: true,
integrations: [Sentry.pinoIntegration()],
});
See Pino integration docs for configuration options.
Send logs from the Consola logging library to Sentry.
Requires SDK version 10.12.0 or higher and enableLogs: true in your Sentry.init call.
import { consola } from "consola";
const sentryReporter = Sentry.createConsolaReporter({
levels: ["error", "warn"], // optional filter
});
consola.addReporter(sentryReporter);
Send logs from the Winston logging library to Sentry.
Requires SDK version 9.13.0 or higher and enableLogs: true in your Sentry.init call.
import winston from "winston";
import Transport from "winston-transport";
const SentryTransport = Sentry.createSentryWinstonTransport(Transport, {
levels: ["error", "warn"], // optional filter
});
const logger = winston.createLogger({
transports: [new SentryTransport()],
});
Filter or modify logs before they're sent. Return null to drop a log.
Use this to:
- Remove sensitive data
- Filter noisy logs
- Add computed attributes
instrumentation-client.tsSentry.init({
dsn: "___PUBLIC_DSN___",
enableLogs: true,
beforeSendLog(log) {
// Drop debug logs in production
if (log.level === "debug") {
return null;
}
// Remove sensitive attributes
if (log.attributes?.password) {
delete log.attributes.password;
}
return log;
},
});
Sentry automatically adds these attributes to every log:
{
"message": "Order completed",
"level": "info",
"attributes": {
// Your custom attributes
"order_id": "order_123",
"user_tier": "pro",
// Core (always present)
"sentry.environment": "production",
"sentry.release": "my-app@1.2.3",
"sentry.sdk.name": "sentry.javascript.nextjs",
"sentry.sdk.version": "9.0.0",
// User (if set via Sentry.setUser)
"user.id": "user_456",
"user.email": "jane@example.com",
"user.name": "Jane",
// Browser (client-side only)
"browser.name": "Chrome",
"browser.version": "120.0.0",
// Server (server-side only)
"server.address": "api-server-1",
// Trace (if tracing enabled)
"sentry.trace.parent_span_id": "abc123",
"sentry.replay_id": "def456",
// Message (when using fmt)
"sentry.message.template": "Order {} completed",
"sentry.message.parameter.0": "order_123",
// Payload
"payload_size": 342
}
}
Make sure enableLogs: true is set in all Sentry config files:
instrumentation-client.ts(client)sentry.server.config.ts(server)sentry.edge.config.ts(edge)
Logs larger than 1 MB are dropped. Check your org stats to see if logs are being rate limited or dropped.
If attributes show [Filtered], they're being removed by server-side data scrubbing. Check your project's data scrubbing settings to adjust what gets filtered.
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").