Sentry is an open-source application monitoring platform that helps developers identify, triage, and fix bugs in real time. Originally built as an error tracker, it has evolved into a full observability tool covering errors, performance, profiling, session replay, and release tracking — all tied to your actual code with stack traces.
🐛
Error Monitoring
Capture, group, and triage every unhandled exception with full stack traces and context
⚡
Performance
Distributed tracing, transaction spans, N+1 detection, and slow query analysis
🎬
Session Replay
Watch exactly what the user did before an error — pixel-perfect DOM replays
📊
Profiling
Continuous and on-demand profiling to find CPU hotspots in production
🚀
Releases
Track which release introduced a regression, link commits to errors
🔔
Alerting
Issue alerts, metric alerts, and uptime monitors with Slack/PagerDuty/email routing
A Sentry project maps to one application or service. Each project gets a unique DSN (Data Source Name) used to route events. Projects belong to an Organization.
🔴
Issue
Grouped errors
An Issue is a group of similar events. Sentry deduplicates events using fingerprinting — new occurrences increment a counter rather than creating duplicate issues. Issues have states: unresolved, resolved, ignored.
📨
Event
Single occurrence
A single captured error, message, or transaction. Contains stack trace, breadcrumbs, tags, user info, extra context, and environment metadata. Events belong to Issues.
🧵
Trace
Distributed request
A Trace is a tree of Spans representing a single request flowing through multiple services. Uses W3C traceparent / Sentry's sentry-trace headers for propagation.
📏
Transaction
Root span
The root span of a trace — typically one HTTP request or page load. Transactions have a name, duration, and status. Sentry samples transactions to control volume.
🔷
Span
Unit of work
A span represents a single operation within a transaction: a DB query, HTTP call, cache lookup, render. Spans are hierarchical and form the trace waterfall.
🍞
Breadcrumbs
Event trail
Structured log entries automatically collected before an error: navigation events, console logs, HTTP requests, UI interactions. Gives "what happened before the crash."
🧬
DSN
Data Source Name
The SDK connection string for your project. Format: https://<key>@<host>/<project-id>. Never commit DSNs — use environment variables.
Data Model Hierarchy
Organization
└── Project (one per service / app)
├── Issue ← grouped by fingerprint
│ └── Event ← single occurrence
│ ├── stack trace
│ ├── breadcrumbs
│ ├── tags (indexed, filterable)
│ ├── context (extra data, not indexed)
│ └── user (id, email, username)
│
├── Transaction ← root span (sampled)
│ └── Span ← db.query, http.request, etc.
│
├── Replay ← DOM recording session
│
├── Release ← version + commit range
│ └── Deploy ← environment + timestamp
│
└── Alert ← issue rule or metric rule
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.redis import RedisIntegration
sentry_sdk.init(
dsn="YOUR_DSN",
integrations=[
DjangoIntegration(),
CeleryIntegration(),
RedisIntegration(),
],
traces_sample_rate=0.1,
profiles_sample_rate=0.1, # Continuous profiling
send_default_pii=False, # Don't send PII by default
environment=os.getenv("ENVIRONMENT", "development"),
release=os.getenv("GIT_SHA"),
)
FastAPI
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
sentry_sdk.init(
dsn="YOUR_DSN",
integrations=[StarletteIntegration(), FastApiIntegration()],
traces_sample_rate=0.2,
)
# Install
go get github.com/getsentry/sentry-go
go get github.com/getsentry/sentry-go/http # net/http middleware
go get github.com/getsentry/sentry-go/gin # Gin middleware
// Tags — indexed, filterable in UI, used for searching
Sentry.setTag("feature_flag", "checkout-v2");
Sentry.setTag("region", "us-east-1");
// Context — structured extra data (not indexed)
Sentry.setContext("order", {
orderId: "ord_abc123",
total: 99.99,
currency: "USD",
items: 3,
});
// Extra — arbitrary key-value (not indexed)
Sentry.setExtra("rawPayload", JSON.stringify(payload));
// beforeSend — mutate or drop events before sending
Sentry.init({
beforeSend(event, hint) {
// Drop events from botsif (event.request?.headers?.["user-agent"]
?.includes("bot")) {
returnnull;
}
// Scrub sensitive fieldif (event.request?.data) {
delete event.request.data.password;
}
return event;
},
});
Fingerprinting (Custom Grouping)
// Override Sentry's automatic grouping
Sentry.withScope((scope) => {
// Group all DB timeout errors together
scope.setFingerprint(["database-timeout"]);
Sentry.captureException(err);
});
// Config-based fingerprinting rule (in Sentry UI → Project Settings → Issue Grouping)// Or via SDK:
Sentry.init({
beforeSend(event) {
if (event.exception?.values?.[0].type === "TimeoutError") {
event.fingerprint = ["timeout-error", event.tags?.service];
}
return event;
},
});
Performance Monitoring
Sentry's performance monitoring is built on distributed tracing — every request gets a trace ID that flows through all services, producing a full picture of latency across your stack.
// Recommended: use startSpanconst result = await Sentry.startSpan(
{ name: "process-order", op: "task" },
async (span) => {
const order = await Sentry.startSpan(
{ name: "db.fetch", op: "db.query" },
() => fetchOrder(id)
);
span?.setAttribute("order.id", id);
span?.setAttribute("order.total", order.total);
return order;
}
);
// Python equivalentwith sentry_sdk.start_transaction(
name="process-order", op="task"
) as transaction:
with transaction.start_child(op="db.query"):
order = fetch_order(order_id)
Sampling Strategy
// Static rate — simple but inflexible
tracesSampleRate: 0.1, // 10% of all transactions// Dynamic sampler — recommended for production
tracesSampler: (samplingContext) => {
const { transactionContext, parentSampled } = samplingContext;
// Inherit parent's sampling decision for consistencyif (parentSampled !== undefined) return parentSampled;
// Always trace health checks at 0%if (transactionContext.name === "GET /health") return0;
// High-value transactions — trace 50%if (transactionContext.name.startsWith("POST /checkout")) return0.5;
// Default — 5%return0.05;
},
Cost tip: Sentry charges by event volume. Use tracesSampler to trace important transactions at higher rates (checkout, auth) and noisy read endpoints at lower rates (feed, search).
Web Vitals (Frontend)
The browser SDK automatically captures Core Web Vitals when browserTracingIntegration is enabled.
Metric
What It Measures
Good
Needs Work
Poor
LCP — Largest Contentful Paint
Load performance — when is the largest visible element rendered?
< 2.5s
2.5–4s
> 4s
FID — First Input Delay
Interactivity — how quickly does the page respond to first input?
< 100ms
100–300ms
> 300ms
CLS — Cumulative Layout Shift
Visual stability — how much does content shift unexpectedly?
< 0.1
0.1–0.25
> 0.25
FCP — First Contentful Paint
When does any content first appear?
< 1.8s
1.8–3s
> 3s
TTFB — Time To First Byte
Server response latency
< 800ms
800ms–1.8s
> 1.8s
INP — Interaction to Next Paint
Responsiveness throughout the page lifecycle (replaces FID)
< 200ms
200–500ms
> 500ms
Performance Lab Testing — See Results Instantly
Sentry's performance data comes from real user sessions (RUM / field data) — which means you must deploy and wait for traffic before seeing the effect of any change.
Lab testing simulates a user visit in a controlled environment and gives you scores immediately on localhost or in CI — no real traffic needed. Use lab tools to iterate fast, then use Sentry to confirm the improvement in production.
── Development Loop ──────────────────────────────────────────────
Code change
│
▼
Lab Test← runs on localhost or CI, results in secondsLighthouse CLI / PerformanceObserver / Playwright
│ Score +/- instantly visible
▼
Deploy to production
│
▼
Sentry RUM← confirms real-user impact over hours/daysWeb Vitals · Transactions · p75/p95 latency── Lab ≠ Field (but lab predicts field direction) ───────────────Lab: synthetic, single run, throttled CPU+network, no cachingField: real devices, real networks, real cache states — averages
1. Lighthouse CLI — Instant Scores on Localhost
Lighthouse audits a URL and returns LCP, CLS, TBT (proxy for FID/INP), FCP, SI, and a total Performance score 0–100 in ~30 seconds.
# Install globally
npm install -g lighthouse
# Audit localhost (dev server must be running)
lighthouse http://localhost:3000 --view
# JSON output for scripting
lighthouse http://localhost:3000 --output=json --output-path=./lh.json
# Throttle to simulate mobile (Moto G4 + 4G)
lighthouse http://localhost:3000 \
--emulated-form-factor=mobile \
--throttling-method=simulate \
--view
# Only run performance (skip a11y, SEO, PWA)
lighthouse http://localhost:3000 --only-categories=performance --view
# Run 3 times and average (reduces variance)for i in 1 2 3; do
lighthouse http://localhost:3000 --output=json --output-path=./lh$i.json
done
Read scores from JSON output
# Extract score from JSON
node -e "
const r = require('./lh.json');
const c = r.categories.performance;
console.log('Score:', Math.round(c.score * 100));
const a = r.audits;
console.log('LCP:', a['largest-contentful-paint'].displayValue);
console.log('TBT:', a['total-blocking-time'].displayValue);
console.log('CLS:', a['cumulative-layout-shift'].displayValue);
console.log('FCP:', a['first-contentful-paint'].displayValue);
console.log('SI:', a['speed-index'].displayValue);
"
# GitHub Actions integrationname: Lighthouse CI
on: [pull_request]
jobs:lhci:runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci && npm run build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
LHCI posts a PR comment with score changes — green if improved, red if regressed. It catches performance regressions before they reach production.
3. PerformanceObserver — Measure Vitals in Your Own Code
The web-vitals library wraps the browser's PerformanceObserver API. Use it locally to print scores to the console as you navigate — no deployment needed.
bundlephobia — check package cost before installing
# CLI version
npx bundlephobia moment
npx bundlephobia date-fns
npx bundlephobia lodash
# Example output:# moment → 72.1 kB (gzip: 18.4 kB) ⚠️# date-fns → 35.6 kB (gzip: 9.9 kB) ✓ (tree-shakeable)# day.js → 6.5 kB (gzip: 2.6 kB) ✓
Source Map Explorer
# Shows byte breakdown from source maps
npx source-map-explorer 'dist/static/js/*.js'
# Or for a specific chunk
npx source-map-explorer dist/static/js/main.abc123.js
# JSON output for CI comparison
npx source-map-explorer 'dist/**/*.js' --json > bundle-report.json
5. Playwright — Automated Performance Checks in Tests
Run Playwright tests against your dev/staging build to assert on performance metrics — catches regressions in CI before any real user is affected.
// Break long task into microtasksasync functionprocessLargeList(items: Item[]) {
const CHUNK = 50;
for (let i = 0; i < items.length; i += CHUNK) {
processChunk(items.slice(i, i + CHUNK));
// Yield to browser between chunksawait scheduler.yield(); // or: await new Promise(r => setTimeout(r))
}
}
// Virtualize long lists (react-window)import { FixedSizeList } from'react-window';
<FixedSizeList
height={600} width="100%"
itemCount={10000} itemSize={50}
>
{({ index, style }) =>
<Row item={items[index]} style={style} />
}
</FixedSizeList>
Tool Comparison: When to Use What
Tool
Speed
What it measures
Best for
Needs deploy?
Lighthouse CLI
~30s
LCP, FCP, CLS, TBT, SI, Score 0–100
Quick local audit, one-off checks
No — localhost
Lighthouse CI
2–5 min
Same as CLI, averaged over N runs
PR gates, regression detection
No — CI build
web-vitals library
Instant
LCP, INP, CLS, FCP, TTFB (real browser)
Dev console feedback while coding
No — localhost
PerformanceObserver
Instant
Long tasks, LCP element, layout shifts
Debugging specific interactions
No — browser DevTools
Bundle Analyzer
~10s
JS bundle size by module
Finding bloated dependencies
No — build step
Chrome DevTools Perf
Instant
Flame chart, long tasks, layout, paint
Deep-dive profiling of specific pages
No — localhost
Playwright perf tests
1–3 min
Timing APIs, custom vital assertions
Automated regression tests in CI
No — CI/staging
PageSpeed Insights API
~1 min
Lab + field CrUX data (28-day p75)
Seeing real user percentiles
Yes — public URL
Sentry Web Vitals (RUM)
Hours–days
Real p50/p75/p95 from actual users
Confirming improvement shipped to users
Yes — production traffic
Workflow: Use Lighthouse CLI + web-vitals console logging during active development → run Lighthouse CI in PRs to catch regressions → confirm real-user impact in Sentry after deploy. You iterate at dev speed, not traffic speed.
Session Replay
Session Replay records DOM mutations, user interactions, console logs, and network requests — letting you watch exactly what a user experienced before and during an error. Privacy-first: all sensitive data is masked by default.
🎬
How It Works
Uses rrweb to record DOM snapshots and mutations as a compact event stream — not a video. Playback reconstructs the DOM state at any point in time.
~10–50KB per minute (compressed)
Buffered on client, flushed on error
No video codec — pure DOM events
🔒
Privacy Controls
.sentry-mask — mask element contents
.sentry-unmask — allow specific elements
.sentry-block — replace with empty box
.sentry-ignore — skip interactions
Input text masked by default
Images blocked by default
🎚️
Sampling
replaysSessionSampleRate — % of all sessions recorded
replaysOnErrorSampleRate — % of error sessions recorded (set to 1.0)
Replay linked to error automatically when both are captured in same session
Configuration
Sentry.init({
integrations: [
Sentry.replayIntegration({
// Mask all text content by default
maskAllText: true,
// Block all media (images, video)
blockAllMedia: true,
// Custom masking
mask: [".credit-card", "[data-sensitive]"],
unmask: [".public-text"],
block: [".avatar-image"],
ignore: [".non-interactive"],
// Network request recording
networkDetailAllowUrls: ["https://api.myapp.com"],
networkCaptureBodies: true,
networkRequestHeaders: ["x-request-id"],
networkResponseHeaders: ["x-ratelimit-remaining"],
}),
],
replaysSessionSampleRate: 0.05, // 5% of normal sessions
replaysOnErrorSampleRate: 1.0, // 100% of error sessions
});
Releases & Source Maps
Releases let Sentry correlate errors with specific commits, show which release introduced a regression, and unminify JavaScript stack traces using source maps.
1
Set the release version in SDK
Sentry.init({
release: process.env.SENTRY_RELEASE, // e.g. "my-app@1.4.2" or git SHA
environment: "production",
});
Source maps in production: Upload source maps to Sentry but set sourceMapReferences: false and serve your JS without //# sourceMappingURL= comments — so maps are only accessible to Sentry, not the public.
Alerting & Notifications
Issue Alerts
Triggered by issue lifecycle events — new issue, regression (previously resolved issue re-appears), or issue seen by N users.
Example conditions:
- A new issue is created
- An issue is seen by more than 100 users
- An issue regresses (resolved → reoccurs)
- Error count > 50 in 1 hour
- Crash rate > 1% for a release
Actions:
- Email / Slack / PagerDuty / Opsgenie
- Create Jira / GitHub / Linear ticket
- Send webhook to any URL
Example metric alerts:
- p95 response time > 2s (15 min window)
- Error rate > 5% of transactions
- Apdex score drops below 0.8
- Number of unique users affected > 100
Alert types:
- Static threshold
- Anomaly detection (% change from baseline)
- Dynamic threshold (seasonal adjustment)
Uptime Monitoring
# Configure in Sentry UI → Alerts → Uptime Monitors# Or via API
curl https://sentry.io/api/0/projects/{org}/{project}/uptime/ \
-H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
-d '{"name":"API Health","url":"https://api.myapp.com/health","intervalSeconds":60,"method":"GET"}'# Or via Cron Monitors (for scheduled jobs)# Wrap your cron job with check-in pings# Node.js example:const checkInId = Sentry.captureCheckIn({
monitorSlug: "daily-report",
status: "in_progress",
});
awaitgenerateReport();
Sentry.captureCheckIn({
checkInId,
monitorSlug: "daily-report",
status: "ok",
duration: 42, // seconds
});
Routing Rules
Use Alert Routing (Sentry → Settings → Integrations) to route different alerts to different channels: backend errors → PagerDuty on-call, frontend errors → Slack #frontend-bugs, low-priority issues → email digest.
Self-Hosting Sentry
Sentry is open-source (BSL license since 2024 — free for non-SaaS use) and can be self-hosted via Docker Compose.
1
Install self-hosted Sentry
# Minimum: 4 CPU, 16 GB RAM, 20 GB disk
git clone https://github.com/getsentry/self-hosted
cd self-hosted
./install.sh
# Start all services
docker compose up -d
# Sentry UI on port 9000
open http://localhost:9000
Production considerations: Use external managed PostgreSQL (RDS/Cloud SQL) and Redis (ElastiCache) instead of containerized versions. Scale Kafka for high event volume. Enable backup for PostgreSQL and ClickHouse data.
Sentry vs Alternatives
Feature
Sentry
Datadog APM
Rollbar
Bugsnag
New Relic
Primary focus
Error + Perf
Full APM
Error tracking
Error tracking
Full APM
Open Source
Yes (BSL)
No
No
No
No
Self-hostable
Yes
No
No
No
No
Session Replay
Yes
Yes
No
No
Limited
Distributed tracing
Yes
Yes
No
No
Yes
Profiling
Yes
Yes
No
No
Yes
Source maps
Yes
Yes
Yes
Yes
Yes
Cron monitoring
Yes
Yes
No
No
Via synthetics
Free tier
5K errors/mo
No
5K/mo
7.5K/mo
100GB/mo
Pricing model
Per event volume
Per host / per GB
Per event
Per event
Per GB ingest
Best for
Dev-first teams needing code-level insight
Ops teams with broad infrastructure monitoring
Simple error tracking on a budget
Mobile + web error tracking
Enterprise full-stack monitoring
Tips & Best Practices
1
Use environments to separate dev/staging/production noise
Always set environment in your SDK init. Filter the Sentry UI by environment. Never let dev errors pollute your production issue queue — or you'll ignore real alerts.
2
Set release + link commits for every deploy
Use sentry-cli releases set-commits --auto in CI. This enables "suspect commits" — Sentry tells you which commit likely caused a regression based on the file paths in the stack trace.
3
Use tracesSampler instead of tracesSampleRate
Static sampling treats all transactions equally. Dynamic sampling lets you over-sample high-value flows (checkout, auth) and under-sample cheap read endpoints — controlling costs while retaining insight where it matters.
4
Set up inbound data filters to reduce noise
In Project Settings → Inbound Filters: enable "Filter known browser errors" (ResizeObserver loop, non-error promise rejections) and "Filter localhost errors." Add custom filters for 404s you don't care about.
5
Use beforeSend to scrub PII before it leaves the browser
Never rely on server-side scrubbing alone. Use beforeSend to remove credit card numbers, tokens, and passwords from event data at the SDK level — before any network request is made.
6
Wire up Sentry to your issue tracker
Integrate with Jira, GitHub Issues, or Linear. When a new Sentry issue is created, auto-create a ticket. When the ticket is closed, auto-resolve the Sentry issue. Keeps engineering workflow in sync.
7
Use Ownership Rules to auto-assign issues to teams
In Project Settings → Ownership Rules: map file paths or URL patterns to teams or users. e.g., path:src/payments/** → #payments-team. Issues are auto-assigned so the right people are notified.
8
Monitor cron jobs with Sentry Crons
Wrap every scheduled job with Sentry check-in pings. If a job doesn't check in within the expected window, Sentry alerts you — catching silent failures that logs and metrics often miss.
Useful SDK Methods Cheat Sheet
// ─── Error capture ──────────────────────────────────────────
Sentry.captureException(error) // capture Error object
Sentry.captureMessage("msg", "warning") // capture arbitrary message
Sentry.captureEvent({ message, level }) // fully custom event// ─── Scope ──────────────────────────────────────────────────
Sentry.setUser({ id, email }) // attach user to all future events
Sentry.setTag(key, value) // indexed tag
Sentry.setTags({ key: value }) // multiple tags
Sentry.setContext(name, object) // extra context object
Sentry.setExtra(key, value) // arbitrary extra data
Sentry.addBreadcrumb({ category, message })// manual breadcrumb
Sentry.withScope((scope) => { ... }) // isolated scope for one event
Sentry.configureScope((scope) => { ... })// modify global scope// ─── Performance ────────────────────────────────────────────
Sentry.startTransaction({ name, op }) // start root span (legacy)
Sentry.startSpan({ name, op }, callback) // start span (modern API)
Sentry.getActiveSpan() // get current active span// ─── Crons ──────────────────────────────────────────────────
Sentry.captureCheckIn({ monitorSlug, status })// check-in ping
Sentry.withMonitor(slug, callback) // auto check-in wrapper// ─── Utilities ──────────────────────────────────────────────
Sentry.flush(2000) // force flush (serverless)
Sentry.close(2000) // flush + close SDK
Sentry.isInitialized() // check if SDK is ready
Sentry.lastEventId() // get ID of last captured event
Serverless & Edge Considerations
Always call Sentry.flush(2000) at the end of serverless functions (AWS Lambda, Vercel Edge, Cloudflare Workers) — the process exits before the SDK can send buffered events asynchronously. Without it, you'll silently lose errors.