Logging — Sentry React SDK
Minimum SDK:
@sentry/react≥9.41.0+ forSentry.loggerAPI andenableLogsconsoleLoggingIntegration(): requires ≥10.13.0+
Scope-based attributes (getGlobalScope,getIsolationScope): requires ≥10.32.0+
⚠️ Not available via CDN/loader snippet — NPM install required.
Enabling Logs
enableLogs is opt-in and must be explicitly set in Sentry.init():
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
enableLogs: true, // Required — logging is disabled by default
});Without enableLogs: true, all Sentry.logger.* calls are silently no-ops and nothing is sent to Sentry.
Logger API — Six Levels
import * as Sentry from "@sentry/react";
Sentry.logger.trace("Entering processOrder", { fn: "processOrder", orderId: "ord_1" });
Sentry.logger.debug("Cache lookup", { key: "user:123", hit: false });
Sentry.logger.info("Order created", { orderId: "order_456", total: 99.99 });
Sentry.logger.warn("Rate limit approaching", { current: 95, max: 100 });
Sentry.logger.error("Payment failed", { reason: "card_declined", userId: "u_1" });
Sentry.logger.fatal("Database unavailable", { host: "db-primary" });| Level | Method | Typical Use |
|---|---|---|
trace |
Sentry.logger.trace() |
Ultra-granular function entry/exit; high-volume — filter aggressively in production |
debug |
Sentry.logger.debug() |
Development diagnostics, cache hits/misses, local state changes |
info |
Sentry.logger.info() |
Normal business milestones, confirmations |
warn |
Sentry.logger.warn() |
Degraded state, approaching limits, recoverable issues |
error |
Sentry.logger.error() |
Failures requiring attention |
fatal |
Sentry.logger.fatal() |
Critical failures, system unavailable |
Attribute value types: string, number, boolean only — undefined, arrays, and objects are not accepted.
Parameterized Messages — Sentry.logger.fmt
The fmt tagged template literal binds each interpolated variable as a structured, searchable attribute in Sentry:
const userId = "user_123";
const productName = "Widget Pro";
const amount = 49.99;
Sentry.logger.info(
Sentry.logger.fmt`User ${userId} purchased ${productName} for $${amount}`
);This produces:
message.template: "User %s purchased %s for $%s"
message.parameter.0: "user_123"
message.parameter.1: "Widget Pro"
message.parameter.2: 49.99Each parameter is independently searchable in Sentry's log explorer. You can filter by message.parameter.0 = "user_123" without matching the full message string.
⚠️
logger.fmtmust be used as a tagged template literal — not as a function call.Sentry.logger.fmt("text")will not produce structured parameters.
When to use fmt vs plain attributes
| Approach | Use when |
|---|---|
Sentry.logger.info(msg, { key: val }) |
Variables are logically distinct attributes with names |
Sentry.logger.info(Sentry.logger.fmt\...`)` |
Variables are part of a human-readable sentence |
Console Capture — consoleLoggingIntegration
Automatically forwards console.* calls to Sentry as structured logs. Requires SDK ≥10.13.0.
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
enableLogs: true,
integrations: [
Sentry.consoleLoggingIntegration({
levels: ["log", "warn", "error"], // which console methods to forward
}),
],
});
// These calls are now automatically sent to Sentry:
console.log("User action recorded", { userId: 123 });
console.warn("Slow render detected", 240, "ms");
console.error("Fetch failed", new Error("timeout"));Multiple arguments are mapped to positional parameters:
console.log("Text", 123, true)
→ message.parameter.0 = 123
→ message.parameter.1 = trueCapturable console levels
| Console method | Sentry log level |
|---|---|
console.log |
info |
console.info |
info |
console.warn |
warn |
console.error |
error |
console.debug |
debug |
console.assert (failing) |
error |
Configure levels to include only the methods you want forwarded.
Log Filtering — beforeSendLog
Use beforeSendLog to drop, modify, or scrub logs before they leave the client. Return null to discard:
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
enableLogs: true,
beforeSendLog: (log) => {
// Drop debug and trace logs in production
if (log.level === "debug" || log.level === "trace") {
return null;
}
// Scrub sensitive attribute keys
if (log.attributes?.password) {
delete log.attributes.password;
}
if (log.attributes?.["credit_card"]) {
log.attributes["credit_card"] = "[REDACTED]";
}
// Drop noisy health-check logs by message content
if (log.message?.includes("/health")) {
return null;
}
return log; // send the (possibly modified) log
},
});The log object shape
| Field | Type | Description |
|---|---|---|
level |
string |
"trace" | "debug" | "info" | "warn" | "error" | "fatal" |
message |
string |
The log message text |
timestamp |
number |
Unix timestamp |
attributes |
object |
Key/value pairs attached to this log |
Structured Attributes
Every Sentry.logger.* call accepts an attributes object as its second argument:
Sentry.logger.info("Checkout completed", {
orderId: "ord_789",
userId: "usr_123",
cartValue: 149.99,
itemCount: 3,
paymentMethod: "stripe",
userTier: "premium",
duration: Date.now() - startTime,
});Attributes become searchable and filterable in Sentry's log explorer. Prefer one comprehensive log with all relevant context over many small scattered logs ("wide events").
Scope-Based Automatic Attributes (SDK ≥10.32.0)
Attributes set on scopes are automatically added to all logs emitted within that scope.
Global scope — entire session
// Set once at app startup — persists for the lifetime of the page
Sentry.getGlobalScope().setAttributes({
service: "react-checkout",
version: "2.1.0",
region: "us-east-1",
});Isolation scope — logical user session context
// Set after user authenticates
Sentry.getIsolationScope().setAttributes({
org_id: user.orgId,
user_tier: user.tier,
account_type: user.accountType,
});Current scope — single operation
Sentry.withScope((scope) => {
scope.setAttribute("order_id", "ord_789");
scope.setAttribute("payment_method", "stripe");
Sentry.logger.info("Processing payment", { amount: 49.99 });
// order_id and payment_method are included on this log only
});Constraint: Scope attributes accept only string, number, and boolean values — no arrays or objects.
Auto-Generated Attributes
These are added by the SDK to every log without any developer configuration:
| Attribute | Source | Notes |
|---|---|---|
sentry.environment |
environment in Sentry.init() |
— |
sentry.release |
release in Sentry.init() |
— |
sentry.sdk.name |
SDK internals | e.g., "sentry.javascript.react" |
sentry.sdk.version |
SDK internals | — |
browser.name |
User-Agent parsing | e.g., "Chrome" |
browser.version |
User-Agent parsing | e.g., "121.0.0" |
user.id, user.name, user.email |
Sentry.setUser() |
Requires sendDefaultPii: true |
sentry.trace.parent_span_id |
Active tracing span | Enables log ↔ trace correlation |
sentry.replay_id |
Active Session Replay session | Enables log ↔ replay correlation |
message.template |
logger.fmt usage |
The template string |
message.parameter.N |
logger.fmt usage |
Each interpolated value |
Log-to-Trace Correlation
When tracing is enabled alongside logging, logs are automatically linked to the active span:
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
enableLogs: true,
tracesSampleRate: 1.0,
integrations: [Sentry.browserTracingIntegration()],
});
// Logs emitted inside a span are linked to it automatically
await Sentry.startSpan({ name: "checkout-flow", op: "ui.action" }, async () => {
Sentry.logger.info("Validating cart", { cartId: "cart_abc" });
await validateCart();
Sentry.logger.info("Initiating payment", { gateway: "stripe" });
await initiatePayment();
});
// Both logs above have sentry.trace.parent_span_id set to the checkout-flow span IDIn the Sentry UI:
- From a log → click the trace link to jump to the parent span and full trace
- From a trace span → click "Logs" to see all logs emitted during that span
- From a replay → logs are shown inline with the user session recording
React-Specific Best Practice: Wide Events
Prefer one comprehensive log with all context over many fragmented logs:
// ✅ Preferred — one wide log with full context
Sentry.logger.info("Checkout completed", {
orderId: order.id,
userId: user.id,
cartValue: cart.total,
itemCount: cart.items.length,
paymentMethod: "stripe",
userTier: user.tier,
activeFeatureFlags: user.flags.join(","),
durationMs: Date.now() - startTime,
});
// ❌ Avoid — fragmented logs with poor context
Sentry.logger.info("Order ID set", { orderId: order.id });
Sentry.logger.info("Cart total calculated", { cartValue: cart.total });
Sentry.logger.info("Checkout done");When to Use Each API
| Scenario | Recommended API |
|---|---|
| Business event with structured data | Sentry.logger.info(msg, { ...attrs }) |
| Message with embedded variables | Sentry.logger.info(Sentry.logger.fmt\...`)` |
| Capture an unexpected exception | Sentry.captureException(err) |
| Send an informational string event | Sentry.captureMessage(msg, "info") |
Auto-capture existing console.* calls |
consoleLoggingIntegration({ levels: [...] }) |
Use Sentry.logger.* for structured, searchable observability data. Use captureException for actual errors that need issue grouping and stack traces.
Log Level Guide
| Level | When to use | Production volume |
|---|---|---|
trace |
Function entry/exit, loop iterations | Filter out in production |
debug |
Variable values, code paths taken | Filter out in production |
info |
User actions, business milestones, API calls | Keep — low/medium volume |
warn |
Degraded paths, retries, near-limits | Keep — low volume |
error |
Failures that need investigation | Keep — should be rare |
fatal |
System-down, unrecoverable state | Keep — should be very rare |
Troubleshooting
| Issue | Solution |
|---|---|
| Logs not appearing in Sentry | Verify enableLogs: true is in Sentry.init(); requires SDK ≥9.41.0 |
logger.fmt not creating message.parameter.* |
Use as tagged template: Sentry.logger.fmt\text ${var}`— notSentry.logger.fmt("text", var)` |
| Logs not linked to traces | Ensure browserTracingIntegration() is added and tracesSampleRate > 0; logs must be emitted inside an active span |
consoleLoggingIntegration not available |
Upgrade to @sentry/react ≥10.13.0 |
| Scope attributes not appearing on logs | Upgrade to @sentry/react ≥10.32.0 for getGlobalScope/getIsolationScope APIs |
| Too many logs — high volume / costs | Use beforeSendLog to drop trace and debug levels in production |
Log attributes contain undefined |
Only string, number, boolean are accepted — filter undefined values before passing |
beforeSendLog not firing |
Confirm enableLogs: true is set; without it, no logs are sent and no hook is called |
| Sensitive data appearing in logs | Add filtering in beforeSendLog; better yet, avoid logging sensitive data at the call site |
| Logs appear but have no user context | Call Sentry.setUser({ id, email }) after authentication and set sendDefaultPii: true |