Skip to content

Latest commit

 

History

History
1083 lines (887 loc) · 29.6 KB

File metadata and controls

1083 lines (887 loc) · 29.6 KB
title What to Log
sidebar_order 10
description Practical guidance on what to log, how to search logs, and when to set alerts.

You've set up Sentry Logs. Now what?

This guide covers the high-value logging patterns that help you debug faster and catch problems before users report them.

Anatomy of a Log

Every structured log follows the same format:

Sentry.logger.<level>(message, { attributes });
from sentry_sdk import logger
logger.<level>(message, attribute=value)
\Sentry\logger()-><level>(message, attributes: [...]);
using Sentry;
SentrySdk.Logger.<Level>(message);

Logs in Sentry are automatically trace-connected. Each log shows a trace ID that links to the full trace view.

Sentry.logger.<level>(message)
import 'package:sentry/sentry.dart';
Sentry.logger.<level>(message);
import Sentry
SentrySDK.logger.<level>(message, attributes: [...])
import io.sentry.Sentry
Sentry.logger().<level>(message)

Levels: trace, debug, info, warn (or warning in Python), error, fatal

Attributes: Key-value pairs you can search and filter on. Consistency matters, so use whatever naming convention fits your codebase.

Sentry.logger.info("Order completed", {
  orderId: "order_123",
  userId: user.id,
  amount: 149.99,
  paymentMethod: "stripe",
});
from sentry_sdk import logger as sentry_logger

sentry_logger.info("Order completed",
    order_id="order_123",
    user_id=user.id,
    amount=149.99,
    payment_method="stripe"
)
\Sentry\logger()->info('Order completed', attributes: [
    'order_id' => 'order_123',
    'user_id' => $user->id,
    'amount' => 149.99,
    'payment_method' => 'stripe',
]);
using Sentry;

SentrySdk.Logger.Info("Order completed", logger => logger
    .SetAttribute("orderId", "order_123")
    .SetAttribute("userId", user.Id)
    .SetAttribute("amount", 149.99)
    .SetAttribute("paymentMethod", "stripe"));
Sentry.logger.info('Order completed',
  order_id: 'order_123',
  user_id: user.id,
  amount: 149.99,
  payment_method: 'stripe'
)
Sentry.logger.info('Order completed', attributes: {
  'orderId': 'order_123',
  'userId': user.id,
  'amount': 149.99,
  'paymentMethod': 'stripe',
});
import Sentry

SentrySDK.logger.info("Order completed", attributes: [
    "orderId": "order_123",
    "userId": user.id,
    "amount": 149.99,
    "paymentMethod": "stripe"
])
import io.sentry.Sentry

Sentry.logger().info("Order completed orderId=%s userId=%s amount=%.2f",
    "order_123", user.id, 149.99)

Logs in Sentry are automatically trace-connected. Each log shows a trace ID that links to the full trace view.

Where to Add Logs

Start with these five areas and you'll catch most issues before users do.

1. Authentication Events

Login flows are invisible until something breaks. Log successes and failures to spot patterns like brute force attempts, OAuth misconfigurations, or MFA issues.

// After successful authentication
Sentry.logger.info("User logged in", {
  userId: user.id,
  authMethod: "oauth",
  provider: "google",
});

// After authentication fails
Sentry.logger.warn("Login failed", {
  email: maskedEmail,
  reason: "invalid_password",
  attemptCount: 3,
});
from sentry_sdk import logger as sentry_logger

# After successful authentication
sentry_logger.info("User logged in",
    user_id=user.id,
    auth_method="oauth",
    provider="google"
)

# After authentication fails
sentry_logger.warning("Login failed",
    email=masked_email,
    reason="invalid_password",
    attempt_count=3
)
// After successful authentication
\Sentry\logger()->info('User logged in', attributes: [
    'user_id' => $user->id,
    'auth_method' => 'oauth',
    'provider' => 'google',
]);

// After authentication fails
\Sentry\logger()->warn('Login failed', attributes: [
    'email' => $maskedEmail,
    'reason' => 'invalid_password',
    'attempt_count' => 3,
]);
// After successful authentication
SentrySdk.Logger.Info("User logged in", logger => logger
    .SetAttribute("userId", user.Id)
    .SetAttribute("authMethod", "oauth")
    .SetAttribute("provider", "google"));

// After authentication fails
SentrySdk.Logger.Warning("Login failed", logger => logger
    .SetAttribute("email", maskedEmail)
    .SetAttribute("reason", "invalid_password")
    .SetAttribute("attemptCount", 3));
# After successful authentication
Sentry.logger.info('User logged in',
  user_id: user.id,
  auth_method: 'oauth',
  provider: 'google'
)

# After authentication fails
Sentry.logger.warn('Login failed',
  email: masked_email,
  reason: 'invalid_password',
  attempt_count: 3
)
// After successful authentication
Sentry.logger.info('User logged in', attributes: {
  'userId': user.id,
  'authMethod': 'oauth',
  'provider': 'google',
});

// After authentication fails
Sentry.logger.warn('Login failed', attributes: {
  'email': maskedEmail,
  'reason': 'invalid_password',
  'attemptCount': 3,
});
import Sentry

// After successful authentication
SentrySDK.logger.info("User logged in", attributes: [
    "userId": user.id,
    "authMethod": "oauth",
    "provider": "google"
])

// After authentication fails
SentrySDK.logger.warn("Login failed", attributes: [
    "email": maskedEmail,
    "reason": "invalid_password",
    "attemptCount": 3
])
import io.sentry.Sentry

// After successful authentication
Sentry.logger().info("User logged in userId=%s authMethod=%s", user.id, "oauth")

// After authentication fails
Sentry.logger().warn("Login failed email=%s reason=%s", maskedEmail, "invalid_password")

Query in Explore > Logs: userId:123 "logged in" or severity:warn authMethod:*

Alert idea: Alert when severity:warn "Login failed" spikes in a 5-minute window—this can indicate brute force attempts or auth provider issues.

2. Payment and Checkout

Money paths need visibility even when they succeed. When payments fail, you need context fast.

// After payment gateway returns an error
Sentry.logger.error("Payment failed", {
  orderId: "order_123",
  amount: 99.99,
  gateway: "stripe",
  errorCode: "card_declined",
  cartItems: 3,
});
from sentry_sdk import logger as sentry_logger

# After payment gateway returns an error
sentry_logger.error("Payment failed",
    order_id="order_123",
    amount=99.99,
    gateway="stripe",
    error_code="card_declined",
    cart_items=3
)
// After payment gateway returns an error
\Sentry\logger()->error('Payment failed', attributes: [
    'order_id' => 'order_123',
    'amount' => 99.99,
    'gateway' => 'stripe',
    'error_code' => 'card_declined',
    'cart_items' => 3,
]);
// After payment gateway returns an error
SentrySdk.Logger.Error("Payment failed", logger => logger
    .SetAttribute("orderId", "order_123")
    .SetAttribute("amount", 99.99)
    .SetAttribute("gateway", "stripe")
    .SetAttribute("errorCode", "card_declined")
    .SetAttribute("cartItems", 3));
# After payment gateway returns an error
Sentry.logger.error('Payment failed',
  order_id: 'order_123',
  amount: 99.99,
  gateway: 'stripe',
  error_code: 'card_declined',
  cart_items: 3
)
// After payment gateway returns an error
Sentry.logger.error('Payment failed', attributes: {
  'orderId': 'order_123',
  'amount': 99.99,
  'gateway': 'stripe',
  'errorCode': 'card_declined',
  'cartItems': 3,
});
import Sentry

// After payment gateway returns an error
SentrySDK.logger.error("Payment failed", attributes: [
    "orderId": "order_123",
    "amount": 99.99,
    "gateway": "stripe",
    "errorCode": "card_declined",
    "cartItems": 3
])
import io.sentry.Sentry

// After payment gateway returns an error
Sentry.logger().error("Payment failed orderId=%s gateway=%s errorCode=%s",
    "order_123", "stripe", "card_declined")

Query in Explore > Logs: orderId:order_123 or severity:error gateway:stripe

Alert idea: Alert when severity:error gateway:* spikes—this can indicate payment provider outages.

3. External APIs and Async Operations

Traces capture what your code does. Logs capture context about external triggers and async boundaries. These are things like webhooks, scheduled tasks, and third-party API responses that traces can't automatically instrument.

// Third-party API call
const start = Date.now();
const response = await shippingApi.getRates(items);

Sentry.logger.info("Shipping rates fetched", {
  service: "shipping-provider",
  endpoint: "/rates",
  durationMs: Date.now() - start,
  rateCount: response.rates.length,
});

// Webhook received
Sentry.logger.info("Webhook received", {
  source: "stripe",
  eventType: "payment_intent.succeeded",
  paymentId: event.data.object.id,
});
import time
from sentry_sdk import logger as sentry_logger

# Third-party API call
start = time.time()
response = shipping_api.get_rates(items)

sentry_logger.info("Shipping rates fetched",
    service="shipping-provider",
    endpoint="/rates",
    duration_ms=int((time.time() - start) * 1000),
    rate_count=len(response.rates)
)

# Webhook received
sentry_logger.info("Webhook received",
    source="stripe",
    event_type="payment_intent.succeeded",
    payment_id=event["data"]["object"]["id"]
)
// Third-party API call
$start = microtime(true);
$response = $shippingApi->getRates($items);

\Sentry\logger()->info('Shipping rates fetched', attributes: [
    'service' => 'shipping-provider',
    'endpoint' => '/rates',
    'duration_ms' => (int)((microtime(true) - $start) * 1000),
    'rate_count' => count($response->rates),
]);

// Webhook received
\Sentry\logger()->info('Webhook received', attributes: [
    'source' => 'stripe',
    'event_type' => 'payment_intent.succeeded',
    'payment_id' => $event['data']['object']['id'],
]);
// Third-party API call
var stopwatch = Stopwatch.StartNew();
var response = await shippingApi.GetRatesAsync(items);

SentrySdk.Logger.Info("Shipping rates fetched", logger => logger
    .SetAttribute("service", "shipping-provider")
    .SetAttribute("endpoint", "/rates")
    .SetAttribute("durationMs", stopwatch.ElapsedMilliseconds)
    .SetAttribute("rateCount", response.Rates.Count));

// Webhook received
SentrySdk.Logger.Info("Webhook received", logger => logger
    .SetAttribute("source", "stripe")
    .SetAttribute("eventType", "payment_intent.succeeded")
    .SetAttribute("paymentId", eventData.Object.Id));
# Third-party API call
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
response = shipping_api.get_rates(items)

Sentry.logger.info('Shipping rates fetched',
  service: 'shipping-provider',
  endpoint: '/rates',
  duration_ms: ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).to_i,
  rate_count: response.rates.length
)

# Webhook received
Sentry.logger.info('Webhook received',
  source: 'stripe',
  event_type: 'payment_intent.succeeded',
  payment_id: event['data']['object']['id']
)
// Third-party API call
final stopwatch = Stopwatch()..start();
final response = await shippingApi.getRates(items);

Sentry.logger.info('Shipping rates fetched', attributes: {
  'service': 'shipping-provider',
  'endpoint': '/rates',
  'durationMs': stopwatch.elapsedMilliseconds,
  'rateCount': response.rates.length,
});

// Webhook received
Sentry.logger.info('Webhook received', attributes: {
  'source': 'stripe',
  'eventType': 'payment_intent.succeeded',
  'paymentId': event.data.object.id,
});
import Sentry

// Third-party API call
let start = Date()
let response = try await shippingApi.getRates(items)

SentrySDK.logger.info("Shipping rates fetched", attributes: [
    "service": "shipping-provider",
    "endpoint": "/rates",
    "durationMs": Int(Date().timeIntervalSince(start) * 1000),
    "rateCount": response.rates.count
])

// Webhook received
SentrySDK.logger.info("Webhook received", attributes: [
    "source": "stripe",
    "eventType": "payment_intent.succeeded",
    "paymentId": event.data.object.id
])
import io.sentry.Sentry

// Third-party API call
val start = System.currentTimeMillis()
val response = shippingApi.getRates(items)

Sentry.logger().info("Shipping rates fetched service=%s durationMs=%d",
    "shipping-provider", System.currentTimeMillis() - start)

// Webhook received
Sentry.logger().info("Webhook received source=%s eventType=%s",
    "stripe", "payment_intent.succeeded")

Query in Explore > Logs: service:shipping-provider durationMs:>2000 or source:stripe

Alert idea: Alert when service:* durationMs:>3000 to catch third-party slowdowns before they cascade.

4. Background Jobs

Jobs run outside the request context. Without logs, failed jobs are invisible until someone notices missing data.

// Inside background job handler
Sentry.logger.info("Job started", {
  jobType: "email-digest",
  jobId: "job_456",
  queue: "notifications",
});

Sentry.logger.error("Job failed", {
  jobType: "email-digest",
  jobId: "job_456",
  retryCount: 3,
  lastError: "SMTP timeout",
});
from sentry_sdk import logger as sentry_logger

# Inside background job handler
sentry_logger.info("Job started",
    job_type="email-digest",
    job_id="job_456",
    queue="notifications"
)

sentry_logger.error("Job failed",
    job_type="email-digest",
    job_id="job_456",
    retry_count=3,
    last_error="SMTP timeout"
)
// Inside background job handler
\Sentry\logger()->info('Job started', attributes: [
    'job_type' => 'email-digest',
    'job_id' => 'job_456',
    'queue' => 'notifications',
]);

\Sentry\logger()->error('Job failed', attributes: [
    'job_type' => 'email-digest',
    'job_id' => 'job_456',
    'retry_count' => 3,
    'last_error' => 'SMTP timeout',
]);
// Inside background job handler
SentrySdk.Logger.Info("Job started", logger => logger
    .SetAttribute("jobType", "email-digest")
    .SetAttribute("jobId", "job_456")
    .SetAttribute("queue", "notifications"));

SentrySdk.Logger.Error("Job failed", logger => logger
    .SetAttribute("jobType", "email-digest")
    .SetAttribute("jobId", "job_456")
    .SetAttribute("retryCount", 3)
    .SetAttribute("lastError", "SMTP timeout"));
# Inside background job handler
Sentry.logger.info('Job started',
  job_type: 'email-digest',
  job_id: 'job_456',
  queue: 'notifications'
)

Sentry.logger.error('Job failed',
  job_type: 'email-digest',
  job_id: 'job_456',
  retry_count: 3,
  last_error: 'SMTP timeout'
)
// Inside background job handler
Sentry.logger.info('Job started', attributes: {
  'jobType': 'email-digest',
  'jobId': 'job_456',
  'queue': 'notifications',
});

Sentry.logger.error('Job failed', attributes: {
  'jobType': 'email-digest',
  'jobId': 'job_456',
  'retryCount': 3,
  'lastError': 'SMTP timeout',
});
import Sentry

// Inside background job handler
SentrySDK.logger.info("Job started", attributes: [
    "jobType": "email-digest",
    "jobId": "job_456",
    "queue": "notifications"
])

SentrySDK.logger.error("Job failed", attributes: [
    "jobType": "email-digest",
    "jobId": "job_456",
    "retryCount": 3,
    "lastError": "SMTP timeout"
])
import io.sentry.Sentry

// Inside background job handler
Sentry.logger().info("Job started jobType=%s jobId=%s queue=%s",
    "email-digest", "job_456", "notifications")

Sentry.logger().error("Job failed jobType=%s jobId=%s retryCount=%d",
    "email-digest", "job_456", 3)

Query in Explore > Logs: jobType:email-digest severity:error

Alert idea: Alert when severity:error jobType:* spikes—this can indicate queue processing issues or downstream failures.

5. Feature Flags and Config Changes

When something breaks after a deploy, the first question is "what changed?" Logging flag evaluations and config reloads gives you that answer instantly.

// When feature flag is checked or config changes
Sentry.logger.info("Feature flag evaluated", {
  flag: "new-checkout-flow",
  enabled: true,
  userId: user.id,
});

Sentry.logger.warn("Config reloaded", {
  reason: "env-change",
  changedKeys: ["API_TIMEOUT", "MAX_CONNECTIONS"],
});
from sentry_sdk import logger as sentry_logger

# When feature flag is checked or config changes
sentry_logger.info("Feature flag evaluated",
    flag="new-checkout-flow",
    enabled=True,
    user_id=user.id
)

sentry_logger.warning("Config reloaded",
    reason="env-change",
    changed_keys=["API_TIMEOUT", "MAX_CONNECTIONS"]
)
// When feature flag is checked or config changes
\Sentry\logger()->info('Feature flag evaluated', attributes: [
    'flag' => 'new-checkout-flow',
    'enabled' => true,
    'user_id' => $user->id,
]);

\Sentry\logger()->warn('Config reloaded', attributes: [
    'reason' => 'env-change',
    'changed_keys' => ['API_TIMEOUT', 'MAX_CONNECTIONS'],
]);
// When feature flag is checked or config changes
SentrySdk.Logger.Info("Feature flag evaluated", logger => logger
    .SetAttribute("flag", "new-checkout-flow")
    .SetAttribute("enabled", true)
    .SetAttribute("userId", user.Id));

SentrySdk.Logger.Warning("Config reloaded", logger => logger
    .SetAttribute("reason", "env-change")
    .SetAttribute("changedKeys", new[] { "API_TIMEOUT", "MAX_CONNECTIONS" }));
# When feature flag is checked or config changes
Sentry.logger.info('Feature flag evaluated',
  flag: 'new-checkout-flow',
  enabled: true,
  user_id: user.id
)

Sentry.logger.warn('Config reloaded',
  reason: 'env-change',
  changed_keys: ['API_TIMEOUT', 'MAX_CONNECTIONS']
)
// When feature flag is checked or config changes
Sentry.logger.info('Feature flag evaluated', attributes: {
  'flag': 'new-checkout-flow',
  'enabled': true,
  'userId': user.id,
});

Sentry.logger.warn('Config reloaded', attributes: {
  'reason': 'env-change',
  'changedKeys': ['API_TIMEOUT', 'MAX_CONNECTIONS'],
});
import Sentry

// When feature flag is checked or config changes
SentrySDK.logger.info("Feature flag evaluated", attributes: [
    "flag": "new-checkout-flow",
    "enabled": true,
    "userId": user.id
])

SentrySDK.logger.warn("Config reloaded", attributes: [
    "reason": "env-change",
    "changedKeys": ["API_TIMEOUT", "MAX_CONNECTIONS"]
])
import io.sentry.Sentry

// When feature flag is checked or config changes
Sentry.logger().info("Feature flag evaluated flag=%s enabled=%b userId=%s",
    "new-checkout-flow", true, user.id)

Sentry.logger().warn("Config reloaded reason=%s", "env-change")

Query in Explore > Logs: flag:new-checkout-flow or "Config reloaded"

Creating Alerts From Logs

  1. Go to Explore > Logs
  2. Enter your search query (e.g., severity:error gateway:*)
  3. Click Save As - Alert
  4. Choose a threshold type:
    • Static: Alert when count exceeds a value
    • Percent Change: Alert when count changes relative to a previous period
    • Anomaly: Let Sentry detect unusual patterns
  5. Configure notification channels and save

Learn about creating alerts and best practices for reducing noise and routing notifications.

Logging Strategy

Development Logging

In development, set sample rates to 100% to catch everything. This helps you understand what logs are being generated and tune your instrumentation before it hits production.

Development configuration:

Sentry.init({
  dsn: "...",
  environment: "development",
  tracesSampleRate: 1.0, // 100% of traces
  // Capture all logs in development
  integrations: [
    Sentry.captureConsoleIntegration({
      levels: ["log", "info", "warn", "error", "debug"],
    }),
  ],
});
import sentry_sdk

sentry_sdk.init(
    dsn="...",
    environment="development",
    traces_sample_rate=1.0,  # 100% of traces
    enable_logs=True,
)
\Sentry\init([
    'dsn' => '...',
    'environment' => 'development',
    'traces_sample_rate' => 1.0,  // 100% of traces
    'enable_logs' => true,
]);
SentrySdk.Init(options =>
{
    options.Dsn = "...";
    options.Environment = "development";
    options.TracesSampleRate = 1.0;  // 100% of traces
    options.EnableLogs = true;
});
Sentry.init do |config|
  config.dsn = '...'
  config.environment = 'development'
  config.traces_sample_rate = 1.0  # 100% of traces
  config.enable_logs = true
end
await SentryFlutter.init(
  (options) {
    options.dsn = '...';
    options.environment = 'development';
    options.tracesSampleRate = 1.0;  // 100% of traces
    options.enableLogs = true;
  },
  appRunner: () => runApp(MyApp()),
);
import Sentry

SentrySDK.start { options in
    options.dsn = "..."
    options.environment = "development"
    options.tracesSampleRate = 1.0  // 100% of traces
    options.enableLogs = true
}
import io.sentry.android.core.SentryAndroid

SentryAndroid.init(context) { options ->
    options.dsn = "..."
    options.environment = "development"
    options.tracesSampleRate = 1.0  // 100% of traces
    options.logs.enabled = true
}

Use verbose logging levels like debug (development diagnostics) and trace (fine-grained execution details) freely in development. You can filter these out in production using beforeSendLog to only capture info and above.

Production Logging

Local debugging often means many small logs tracing execution flow. In production, this creates noise that's hard to query.

Instead, log fewer messages with higher cardinality. Store events during execution and emit them as a single structured log.

Don't do this:

Sentry.logger.info("Checkout started", { userId: "882" });
Sentry.logger.info("Discount applied", { code: "WINTER20" });
Sentry.logger.error("Payment failed", { reason: "Insufficient Funds" });
sentry_logger.info("Checkout started", user_id="882")
sentry_logger.info("Discount applied", code="WINTER20")
sentry_logger.error("Payment failed", reason="Insufficient Funds")
\Sentry\logger()->info('Checkout started', attributes: ['user_id' => '882']);
\Sentry\logger()->info('Discount applied', attributes: ['code' => 'WINTER20']);
\Sentry\logger()->error('Payment failed', attributes: ['reason' => 'Insufficient Funds']);
SentrySdk.Logger.Info("Checkout started", l => l.SetAttribute("userId", "882"));
SentrySdk.Logger.Info("Discount applied", l => l.SetAttribute("code", "WINTER20"));
SentrySdk.Logger.Error("Payment failed", l => l.SetAttribute("reason", "Insufficient Funds"));
Sentry.logger.info('Checkout started', user_id: '882')
Sentry.logger.info('Discount applied', code: 'WINTER20')
Sentry.logger.error('Payment failed', reason: 'Insufficient Funds')
Sentry.logger.info('Checkout started', attributes: {'userId': '882'});
Sentry.logger.info('Discount applied', attributes: {'code': 'WINTER20'});
Sentry.logger.error('Payment failed', attributes: {'reason': 'Insufficient Funds'});
SentrySDK.logger.info("Checkout started", attributes: ["userId": "882"])
SentrySDK.logger.info("Discount applied", attributes: ["code": "WINTER20"])
SentrySDK.logger.error("Payment failed", attributes: ["reason": "Insufficient Funds"])
Sentry.logger().info("Checkout started userId=%s", "882")
Sentry.logger().info("Discount applied code=%s", "WINTER20")
Sentry.logger().error("Payment failed reason=%s", "Insufficient Funds")

These logs are trace-connected, but searching for the error won't return the userId or discount code from the same transaction.

Do this instead:

Sentry.logger.error("Checkout failed", {
  userId: "882",
  orderId: "order_pc_991",
  cartTotal: 142.5,
  discountCode: "WINTER20",
  paymentMethod: "stripe",
  errorReason: "Insufficient Funds",
  itemCount: 4,
});
sentry_logger.error("Checkout failed",
    user_id="882",
    order_id="order_pc_991",
    cart_total=142.50,
    discount_code="WINTER20",
    payment_method="stripe",
    error_reason="Insufficient Funds",
    item_count=4
)
\Sentry\logger()->error('Checkout failed', attributes: [
    'user_id' => '882',
    'order_id' => 'order_pc_991',
    'cart_total' => 142.50,
    'discount_code' => 'WINTER20',
    'payment_method' => 'stripe',
    'error_reason' => 'Insufficient Funds',
    'item_count' => 4,
]);
SentrySdk.Logger.Error("Checkout failed", logger => logger
    .SetAttribute("userId", "882")
    .SetAttribute("orderId", "order_pc_991")
    .SetAttribute("cartTotal", 142.50)
    .SetAttribute("discountCode", "WINTER20")
    .SetAttribute("paymentMethod", "stripe")
    .SetAttribute("errorReason", "Insufficient Funds")
    .SetAttribute("itemCount", 4));
Sentry.logger.error('Checkout failed',
  user_id: '882',
  order_id: 'order_pc_991',
  cart_total: 142.50,
  discount_code: 'WINTER20',
  payment_method: 'stripe',
  error_reason: 'Insufficient Funds',
  item_count: 4
)
Sentry.logger.error('Checkout failed', attributes: {
  'userId': '882',
  'orderId': 'order_pc_991',
  'cartTotal': 142.50,
  'discountCode': 'WINTER20',
  'paymentMethod': 'stripe',
  'errorReason': 'Insufficient Funds',
  'itemCount': 4,
});
import Sentry

SentrySDK.logger.error("Checkout failed", attributes: [
    "userId": "882",
    "orderId": "order_pc_991",
    "cartTotal": 142.50,
    "discountCode": "WINTER20",
    "paymentMethod": "stripe",
    "errorReason": "Insufficient Funds",
    "itemCount": 4
])
import io.sentry.Sentry
import io.sentry.SentryAttribute
import io.sentry.SentryAttributes
import io.sentry.SentryLogLevel
import io.sentry.logger.SentryLogParameters

Sentry.logger().log(
    SentryLogLevel.ERROR,
    SentryLogParameters.create(
        SentryAttributes.of(
            SentryAttribute.stringAttribute("userId", "882"),
            SentryAttribute.stringAttribute("orderId", "order_pc_991"),
            SentryAttribute.doubleAttribute("cartTotal", 142.50),
            SentryAttribute.stringAttribute("discountCode", "WINTER20"),
            SentryAttribute.stringAttribute("paymentMethod", "stripe"),
            SentryAttribute.stringAttribute("errorReason", "Insufficient Funds"),
            SentryAttribute.integerAttribute("itemCount", 4)
        )
    ),
    "Checkout failed"
)

One log tells the whole story. Search for the error and get full context.

Log Drains for Platform Logs

If you can't install the Sentry SDK or need platform-level logs (CDN, database, load balancer), use Log Drains.

Platform drains: Vercel, Cloudflare Workers, Heroku, Supabase

Forwarders: OpenTelemetry Collector, Vector, Fluent Bit, AWS CloudWatch, Kafka

Quick Reference

Category Level Example Attributes
Auth events info/warn userId, authMethod, reason
Payments info/error orderId, amount, gateway, errorCode
External APIs info service, endpoint, durationMs
Background jobs info/error jobType, jobId, retryCount
Feature flags info flag, enabled, changedKeys

Next Steps

Explore the Logs product walkthrough guides to learn more about the Sentry interface and discover additional tips.