Sentry: Error Tracking, Triage, and Resolution
Master Sentry for production error tracking. Covers SDK integration in Python, Node.js, Java, release tracking, source maps, performance monitoring, and alerting.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Introduction
Sentry is an error tracking and performance monitoring platform that captures exceptions, crashes, and performance issues in real-time. When an error occurs in production, Sentry captures the stack trace, request context, user information, and breadcrumbs leading up to the error. The following guide covers Sentry SDK integration in Python, Node.js, and Java, release tracking with source maps, performance monitoring, alerting rules, and production workflows for triaging and resolving errors.
How Sentry Works
1. Error occurs in your application
2. Sentry SDK captures the exception with:
- Stack trace (with source maps for minified code)
- Request context (URL, headers, body)
- User context (ID, email, IP)
- Breadcrumbs (events leading to the error)
- Environment and release tags
3. SDK sends the event to Sentry server
4. Sentry deduplicates and groups similar errors into issues
5. Sentry notifies your team via Slack, email, or PagerDuty
6. Developer triages the issue, identifies root cause, and deploys a fix
7. Sentry marks the issue as resolved when the fix is deployed
SDK Integration
Python: Flask/Django
# sentry_config.py — Sentry SDK for Python
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.celery import CeleryIntegration
def init_sentry():
sentry_sdk.init(
dsn="https://your-dsn@sentry.io/123",
environment="production",
release="order-service@1.2.3",
# Sample 100% of errors, 10% of transactions for performance
traces_sample_rate=0.1,
sample_rate=1.0,
integrations=[
FlaskIntegration(),
SqlalchemyIntegration(),
RedisIntegration(),
CeleryIntegration(),
],
# Send PII (email, IP) — enable with caution
send_default_pii=True,
# Before send hook to filter sensitive data
before_send=filter_sensitive_data,
)
def filter_sensitive_data(event, hint):
"""Remove sensitive data before sending to Sentry."""
if "request" in event:
headers = event["request"].get("headers", {})
# Remove authorization headers
for key in list(headers.keys()):
if key.lower() in ("authorization", "cookie", "x-api-key"):
headers[key] = "[REDACTED]"
# Remove sensitive body fields
body = event["request"].get("data", {})
if isinstance(body, dict):
for key in ("password", "credit_card", "ssn"):
if key in body:
body[key] = "[REDACTED]"
return event
# Manual error capture
from sentry_sdk import capture_exception, capture_message, set_user, add_breadcrumb
class OrderService:
def create_order(self, user_id: int, items: list[dict]) -> dict:
# Set user context
set_user({"id": str(user_id), "email": "user@example.com"})
# Add breadcrumbs for debugging
add_breadcrumb(
category="order",
message=f"Creating order for user {user_id} with {len(items)} items",
level="info",
)
try:
order = self._process_order(user_id, items)
add_breadcrumb(category="order", message="Order created successfully", level="info")
return order
except PaymentError as e:
# Capture with extra context
capture_exception(e, {
"extra": {
"user_id": user_id,
"items_count": len(items),
"total_amount": sum(i["price"] * i["quantity"] for i in items),
},
"tags": {"error_type": "payment", "severity": "high"},
})
raise
except Exception as e:
capture_exception(e)
raise
Node.js: Express
// sentry.ts — Sentry SDK for Node.js
import * as Sentry from "@sentry/node";
import { nodeProfilingIntegration } from "@sentry/profiling-node";
export function initSentry() {
Sentry.init({
dsn: "https://your-dsn@sentry.io/123",
environment: process.env.NODE_ENV || "development",
release: `api-gateway@${process.env.APP_VERSION || "1.0.0"}`,
tracesSampleRate: 0.1,
profilesSampleRate: 0.1,
integrations: [
nodeProfilingIntegration(),
Sentry.httpIntegration(),
Sentry.expressIntegration(),
],
beforeSend(event) {
// Redact sensitive headers
if (event.request?.headers) {
delete event.request.headers.authorization;
delete event.request.headers.cookie;
}
// Redact sensitive body fields
if (event.request?.data && typeof event.request.data === "object") {
delete event.request.data.password;
delete event.request.data.creditCard;
}
return event;
},
});
}
// Express middleware — must be before routes
import express from "express";
const app = express();
Sentry.setupExpressErrorHandler(app);
// Manual error capture
import { captureException, setUser, addBreadcrumb } from "@sentry/node";
class OrderService {
async createOrder(userId: string, items: OrderItem[]): Promise<Order> {
Sentry.setUser({ id: userId, email: "user@example.com" });
Sentry.addBreadcrumb({
category: "order",
message: `Creating order for user ${userId} with ${items.length} items`,
level: "info",
});
try {
const order = await this.processOrder(userId, items);
Sentry.addBreadcrumb({
category: "order",
message: "Order created successfully",
level: "info",
});
return order;
} catch (error) {
Sentry.captureException(error, {
extra: {
userId,
itemCount: items.length,
totalAmount: items.reduce((sum, i) => sum + i.price * i.quantity, 0),
},
tags: { errorType: "order_creation", severity: "high" },
});
throw error;
}
}
}
// Custom error handler
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
Sentry.captureException(err);
res.status(500).json({ error: "Internal server error" });
});
Java: Spring Boot
// SentryConfig.java — Sentry SDK for Spring Boot
import io.sentry.Sentry;
import io.sentry.spring.tracing.SentryTracingConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SentryConfig {
@Bean
public Sentry.OptionsConfiguration sentryOptions() {
return options -> {
options.setDsn("https://your-dsn@sentry.io/123");
options.setEnvironment("production");
options.setRelease("order-service@1.2.3");
options.setTracesSampleRate(0.1);
options.setBeforeSend((event, hint) -> {
// Redact sensitive data
if (event.getRequest() != null) {
event.getRequest().getHeaders().remove("Authorization");
event.getRequest().getHeaders().remove("Cookie");
}
return event;
});
};
}
}
// Service code with manual capture
@Service
public class OrderService {
public Order createOrder(Long userId, List<OrderItem> items) {
Sentry.setUser(new Sentry.User(
String.valueOf(userId),
"user@example.com",
null,
null
));
Sentry.addBreadcrumb("Creating order for user " + userId, "order");
try {
Order order = processOrder(userId, items);
Sentry.addBreadcrumb("Order created successfully", "order");
return order;
} catch (PaymentException e) {
Sentry.captureException(e, Scope -> {
Scope.setExtra("user_id", userId);
Scope.setExtra("items_count", items.size());
Scope.setTag("error_type", "payment");
});
throw e;
}
}
}
Release Tracking and Source Maps
Release tracking
# Create a Sentry release
sentry-cli releases new "order-service@1.2.3"
# Associate commits with the release
sentry-cli releases set-commits "order-service@1.2.3" \
--auto
# Finalize the release
sentry-cli releases finalize "order-service@1.2.3"
# Mark deployment
sentry-cli releases deploys "order-service@1.2.3" new \
--env production \
--url "https://order-service.example.com"
Source maps for JavaScript
// webpack.config.js — Upload source maps to Sentry
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
module.exports = {
devtool: "source-map",
plugins: [
new SentryWebpackPlugin({
org: "your-org",
project: "api-gateway",
authToken: process.env.SENTRY_AUTH_TOKEN,
release: "api-gateway@1.2.3",
include: "./dist",
ignore: ["node_modules", "webpack.config.js"],
// Strip server-side code paths
urlPrefix: "~/static/js",
}),
],
};
CI/CD integration
# .github/workflows/deploy.yml — Sentry release in CI
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Create Sentry release
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
npm run build
npx sentry-cli releases new "api-gateway@${{ github.sha }}"
npx sentry-cli releases set-commits "api-gateway@${{ github.sha }}" --auto
npx sentry-cli releases files "api-gateway@${{ github.sha }}" upload-sourcemaps ./dist
npx sentry-cli releases finalize "api-gateway@${{ github.sha }}"
- name: Deploy
run: npm run deploy
- name: Mark Sentry deploy
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
npx sentry-cli releases deploys "api-gateway@${{ github.sha }}" new \
--env production
Performance Monitoring
# Python: Custom transactions and spans
import sentry_sdk
@sentry_sdk.trace
def process_order(user_id: int, items: list[dict]) -> dict:
with sentry_sdk.start_span(op="db", description="query_user") as span:
user = db.query(User).get(user_id)
span.set_data("user.id", user_id)
with sentry_sdk.start_span(op="http.client", description="charge_payment") as span:
payment = payment_client.charge(user_id, total)
span.set_data("payment.amount", payment["amount"])
with sentry_sdk.start_span(op="db", description="save_order") as span:
order = order_repo.create(user_id, items, payment["id"])
span.set_data("order.id", order["id"])
return order
// Node.js: Custom spans
import * as Sentry from "@sentry/node";
async function processOrder(userId: string, items: OrderItem[]): Promise<Order> {
return Sentry.startSpan({ op: "function", name: "process_order" }, async () => {
const user = await Sentry.startSpan(
{ op: "db", name: "query_user" },
async () => await userRepo.findById(userId)
);
const payment = await Sentry.startSpan(
{ op: "http.client", name: "charge_payment" },
async () => await paymentService.charge(userId, total)
);
const order = await Sentry.startSpan(
{ op: "db", name: "save_order" },
async () => await orderRepo.create({ userId, items, paymentId: payment.id })
);
return order;
});
}
Alerting Rules
# Sentry alert rules (configured in Sentry UI or via API)
# Example: Alert on high error rate
rules:
- name: "High error rate"
conditions:
- event_level: error
- threshold: 10 # errors in 1 hour
actions:
- notify_slack: "#alerts"
- notify_email: ["oncall@company.com"]
- name: "New error in production"
conditions:
- event_level: error
- environment: production
- is_new: true
actions:
- notify_slack: "#engineering"
- create_issue: true
- name: "Performance regression"
conditions:
- metric: p95_duration
- threshold: 2000 # ms
- comparison: greater_than
actions:
- notify_slack: "#performance"
Triage Workflow
1. Receive alert (Slack/email/PagerDuty)
2. Open the issue in Sentry
3. Review:
- Stack trace → identify the failing code
- Breadcrumbs → understand what led to the error
- User context → who was affected
- Request context → what was the input
- Release tag → which version introduced the bug
- Tags → filter by environment, service, error type
4. Assign the issue to a developer
5. Link to a Jira/GitHub issue
6. Write a fix and deploy
7. Sentry detects the fix in the new release
8. Issue is auto-resolved if no new events in 72 hours
Best Practices
-
For a deeper guide, see Complete Guide to Observability with the Grafana Stack.
-
Set
releasetag on every deployment — Sentry auto-resolves issues when a fix is deployed -
Upload source maps for minified JavaScript — get readable stack traces
-
Use
before_sendto redact PII — passwords, tokens, credit card numbers -
Set user context early in the request lifecycle — identify who was affected
-
Add breadcrumbs for key operations — database queries, API calls, state changes
-
Use tags for filtering —
service,endpoint,feature_flag,user_tier -
Sample transactions wisely — 10% for high-traffic, 100% for low-traffic
-
Group similar errors — Sentry’s default grouping is good, but custom fingerprinting helps
-
Set up alerting on new errors — catch regressions before users report them
-
Configure environment filtering — don’t send development errors to production project
Common Mistakes
- No release tracking: can’t tell which version introduced the bug. Always set
release. - No source maps: minified stack traces are useless. Upload source maps in CI.
- Sending too much data: high sample rates create noise. Use 10% for transactions.
- Not redacting PII: passwords and tokens leak to Sentry. Use
before_sendhook. - Ignoring breadcrumbs: they show the path to the error. Add them for key operations.
- One project for everything: separate projects per service for cleaner triage.
FAQ
What is Sentry?
An error tracking and performance monitoring platform that captures exceptions, crashes, and performance issues in real-time. It provides stack traces, breadcrumbs, user context, and release tracking to help developers identify and fix production errors.
How does Sentry differ from logging?
Logging captures all events to a log aggregation system. Sentry specifically captures errors and performance issues with rich context (stack traces, breadcrumbs, user info). Use both — logs for audit trails, Sentry for error triage.
What are breadcrumbs?
A trail of events leading up to an error. They include HTTP requests, database queries, UI clicks, and log messages. Breadcrumbs help you understand the sequence of actions that caused the error.
How does Sentry resolve issues?
When you deploy a fix with a new release tag, Sentry checks if the error still occurs. If no new events are seen for 72 hours (configurable), the issue is auto-resolved. You can also manually resolve issues.
What is Sentry’s pricing model?
Sentry offers a free tier (5,000 errors/month), a Team tier ($26/month, 50,000 errors), and Business/Enterprise tiers. Pricing scales with the number of events. You can self-host Sentry using the open-source version.
Related Resources
Structured Logging: JSON Logs, Correlation IDs, Aggregation
Master structured logging with JSON format, correlation IDs, log levels, and aggregation. Covers Python structlog, Node.js pino, Java SLF4J, ELK and Loki stacks.
GuideDistributed Tracing: OpenTelemetry, Jaeger, Zipkin
Master distributed tracing with OpenTelemetry, Jaeger, and Zipkin. Trace propagation across services, span context, sampling strategies, and production debugging.
RecipeReal User Monitoring
Monitor actual user experiences with Core Web Vitals, session replay, and performance analytics to identify real-world bottlenecks.
GuideComplete Guide to Monitoring and Alerting
Build a production monitoring stack. Covers Prometheus, Grafana, AlertManager, metrics instrumentation, alert rules, runbooks, SLI/SLO/SLA, distributed tracing with Jaeger, log aggregation, and on-call best practices with practical configuration examples.
GuideComplete Guide to Observability with the Grafana Stack
Set up metrics, logs, and traces with Grafana, Prometheus, Loki, and Tempo. Covers instrumentation, dashboards, alerting, and distributed tracing for production systems.
GuidePrometheus and Grafana: Metrics, Dashboards, Alerting
Master Prometheus metrics collection and Grafana dashboards. Covers metric types, PromQL, service instrumentation, alerting rules, and production deployment patterns.