Overview
This skill covers using Sentry for error tracking and performance monitoring on a Drupal site:
- Reading issues via Sentry MCP tools
- Adding comments via the REST API (MCP can't add comments)
- Common issue patterns and analysis
Organization Details
- Org:
<your-sentry-org-slug> - Region URL:
https://us.sentry.io(or your region) - Web URL: https://<your-sentry-org-slug>.sentry.io
Reading Issues (MCP Tools)
Get Issue Details
mcp__sentry__get_issue_details
organizationSlug: "<org-slug>"
issueId: "<numeric-id>" # numeric ID from URL
regionUrl: "https://us.sentry.io"
Search Issues
mcp__sentry__search_issues
organizationSlug: "<org-slug>"
query: "is:unresolved"
regionUrl: "https://us.sentry.io"
Update Issue Status
mcp__sentry__update_issue
organizationSlug: "<org-slug>"
issueId: "<PROJECT-123>"
status: "ignored" # or "resolved", "unresolved"
regionUrl: "https://us.sentry.io"
Adding Comments (REST API)
MCP tools cannot add comments — use the REST API directly (wrap it in a small project script if you do this often):
curl -sS -X POST \
"https://us.sentry.io/api/0/issues/<numeric_issue_id>/comments/" \
-H "Authorization: Bearer ${SENTRY_AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"text": "**Analysis**: N+1 query from Flag module.\n\nRoot cause: flaggings_current_user computed fields require per-entity lookups.\n\n**Impact**: Minimal (349ms total).\n\n**Verdict**: Safe to ignore - known pattern with Flag module + JSON:API."}'
Getting the Numeric Issue ID
From URL: https://<your-sentry-org-slug>.sentry.io/issues/<numeric-id>/
Authentication
Read the token from (in order):
SENTRY_AUTH_TOKENenvironment variable~/.sentry/auth_tokenfile- Project
.envfile (never committed)
Token Setup (One-Time)
- Go to https://sentry.io/settings/account/api/auth-tokens/
- Create token with scopes:
event:read,event:write,org:read,project:read - Save:
mkdir -p ~/.sentry && echo 'your-token' > ~/.sentry/auth_token && chmod 600 ~/.sentry/auth_token
Common Issue Patterns
N+1 Queries (Performance)
Type: performance_n_plus_one_db_queries
Level: info (not error)
Common causes in Drupal apps:
- Flag module - COUNT queries for
flaggings_current_user/flag_counts_entity - Group module - Relationship lookups by UUID
- JSON:API includes - Related entity loading
Analysis template:
**Analysis**: N+1 query from [module] - [severity].
Query pattern: [describe the repeated query]
**Root cause**: [why it happens]
**Impact**: [response time, occurrences]
**Options if optimization needed**:
1. [option 1]
2. [option 2]
**Verdict**: [safe to ignore / needs attention / priority]
PHP Errors
Common types:
TypeError- Type mismatchArgumentCountError- Wrong number of argumentsDeprecated- PHP 8.x deprecations
Database Errors
Common types:
- Connection timeouts
- Deadlocks
- Query timeouts
Workflow: Analyzing a Sentry Issue
- Get issue details via MCP tool
- Identify type: error vs performance
- Check occurrences: 1 vs many
- Analyze root cause
- Document finding: add comment via the REST API
- Update status: ignore/resolve if appropriate
Performance Tracing & Profiling (Raven config)
Backend tracing is provided by the raven module (Drupal Sentry SDK). Config in your exported raven.settings.yml; env-specific overrides in settings.php. Hard-won gotchas:
traceis NOT the performance switch — leave itfalse. It is "Reflection tracing in stacktraces":trueremoves raven'sRemoveExceptionFrameVarsIntegrationscrubber (Raven.php:103) and sends function-call arguments (passwords/tokens/PII) to Sentry. Security review flagstrace: trueHIGH. Don't enable it in committed config.- Performance tracing is applied unconditionally (
Raven.php:116,traces_sample_rate), independent oftrace—database_tracing/twig_tracing/request_tracingtoo. BUT it is gated by thesend performance traces to sentrypermission (form: "if user has the … permission, excluding page-cache hits"). If no role grants it, ZERO requests are traced despite the sample rate. To collect backend traces, grant that permission to a role (start withadministratorfor controlled, low-cost staff traces; widen toauthenticatedfor representative real-user data — at the cost of Sentry quota + tracing real sessions). Anonymous page-cache hits are never traced. - Environment is set per-env in
settings.php($config['raven.settings']['environment'] = ...→ prod/stage/dev). Don't put it in committed config (leave it''there). - Release: set in
settings.phpfromSENTRY_RELEASEenv var (e.g. a CI-set git SHA for suspect-commit linking) or an equivalent per-deploy identifier from your hosting platform. Blankrelease= no deploy correlation. - Profiling (function flamegraphs) needs the Excimer PECL extension —
pecl install excimer. Some managed hosting platforms (e.g. Pantheon) can't install custom PECL extensions, so Sentry Profiling is unavailable there (the raven form disables the profiles field when!extension_loaded('excimer')). Tracing spans (DB/Twig/render breakdown per transaction) are the substitute on those platforms; check whether your host offers a native deep-profiling APM as an alternative.
What Sentry holds, and what it cannot show
Use Sentry before asking someone with server access for watchdog output or log pulls — errors, watchdog entries, per-route transaction volume and p50/p95, DB/Twig spans, breadcrumbs, trace ids and release tagging are usually all there. But state these limits in any verdict built on it.
Raven forwards only the log levels your site enables. Watchdog/logger entries become Sentry events only at the levels enabled in raven.settings:log_levels (the module installs with every level off; sites commonly enable error and above). Check your config before reading silence: if only error+ is enabled, "no Sentry events" means nothing was logged at error+, not that nothing was logged. Raven can also send log records to Sentry Logs (structured logs) via enable_logs plus its own logs_log_levels mapping (both off in raven 7.4.0's install config). Either way log_levels is site-wide with no per-channel level, so if one non-error outcome must be searchable in prod, capture it through the Sentry SDK directly with explicit tags rather than inflating its log severity.
Dataset facts (verify on your org — these were measured, not documented)
- Where transactions live: the legacy Discover
transactionsdataset can return zero rows; querydataset=spanswithis_transaction:true. - Counts: in
spans,count()is already extrapolated per span by1/client_sample_rate;count_sample()is the stored row count. has:<field>lies about population. Inerrors,has:<field>matches rows whose value is the empty string, and an unknown field name returns''on every row with HTTP 200. Only non-empty values prove a field is populated.
Triage discipline: before answering "Sentry doesn't have X" or building a table on a field, check that the field has non-empty values in the dataset and window you queried, that the environment filter matches real events (e.g. prod vs production), and that the window itself has data. If any check fails, refuse to answer from that query and say which dataset does carry the field — an empty column is not evidence. Any wrapper script you build around the API should enforce this and print the dataset, window, project, query and sample-rate caveat with every result.
Blind spots
- Sampling. Transactions are sampled, so counts are extrapolations and rare slow requests are mostly missing. When per-request client sample rates are mixed, use Sentry's weighted
count()— a fixed multiplier (×50 for "2%") over- or under-counts. - Survivorship. A request that hangs until the gateway/edge times out never completes, so it never emits a transaction. Worker exhaustion shows as transaction volume collapsing, never as a spike. Treat the volume curve as a floor.
- Client attribution on server transactions (as measured on a Drupal + raven backend):
- User agent: parsed
browser.name,browser(name + version) anddevice.familyare populated on server transactions;user_agent.originalwas empty in both spans and errors. The rawUser-Agentexists only as a request header on error event detail. os.nameis the app server ("Linux") on server transactions; it describes the client only on browser-SDK spans. Likewisegeo.*/user.geodescribe the app server's location, not the client's.- Client IP:
user.ipis empty everywhere whenraven.settings:capture_user_ipisfalse. The client address may then exist only in CDN/proxy client-IP request headers on error event detail — and those can be client-supplied and passed straight through by some platforms, so trust them for ordinary bots, never for a deliberate scraper. - HTTP status was not queryable on server transactions (
http.status_codewas null there); usespan.status(ok,permission_denied,unavailable, …). Status codes that do appear inspanscome from browser-SDK fetch/resource spans. user.idwas "0" on ~100% of server transactions (cause unproven); error events carry real user ids.- Attributing traffic that raised no error still needs the web server access log.
- User agent: parsed
Related Skills
drupal-performance- Performance optimization patterns