
Sentry Php Sdk
- 1.8k installs
- 243 repo stars
- Updated July 27, 2026
- getsentry/sentry-for-ai
sentry-php-sdk is an agent skill for Full Sentry SDK setup for PHP. Use when asked to "add Sentry to PHP", "install sentry/sentry", "setup Sentry in PHP", or
About
The sentry-php-sdk skill Full Sentry SDK setup for PHP. Use when asked to "add Sentry to PHP", "install sentry/sentry", "setup Sentry in PHP", or configure error monitoring, tracing, profiling, logging, metrics, or crons for PHP applications. Supports plain PHP, Laravel, and Symfony. It covers user asks to "add Sentry to PHP" or "setup Sentry" in a PHP app. Key workflows include user wants error monitoring, tracing, profiling, logging, metrics, or crons in PHP. - Recommended Init Plain PHP Full init enabling the most features with sensible defaults: php \Sentry\init 'dsn' = $_SERVER 'SENTRY_DSN' ?? '', 'environment' = $_SERVER 'SENTRY_ENVIRONMENT' ?? 'production', 'release' = $_SERVER 'SENTRY_RELEASE' ?? null, 'send_default_pii' = true, // Tracing lower to 0.1 - 0.2 in high-traffic production 'traces_sample_rate' = 1.0, // Profiling - requires excime Developers invoke sentry-php-sdk when the task matches the triggers and reference files in SKILL.md for grounded, stepwise execution.
- User asks to "add Sentry to PHP" or "setup Sentry" in a PHP app
- User wants error monitoring, tracing, profiling, logging, metrics, or crons in PHP
- User mentions sentry/sentry , sentry/sentry-laravel , sentry/sentry-symfony , or Sentry + any PHP framework
- User wants to monitor Laravel routes, Symfony controllers, queues, scheduled tasks, or plain PHP scripts
- Is sentry/sentry or -laravel / -symfony already in composer.json ? If yes, check if the init call exists - may just ne
Sentry Php Sdk by the numbers
- 1,805 all-time installs (skills.sh)
- +52 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #112 of 1,453 DevOps & CI/CD skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
sentry-php-sdk capabilities & compatibility
- Capabilities
- user asks to "add sentry to php" or "setup sentr · user wants error monitoring, tracing, profiling, · user mentions sentry/sentry , sentry/sentry lara · user wants to monitor laravel routes, symfony co · is sentry/sentry or laravel / symfony already
- Use cases
- documentation
What sentry-php-sdk says it does
Opinionated wizard that scans your PHP project and guides you through complete Sentry setup.
npx skills add https://github.com/getsentry/sentry-for-ai --skill sentry-php-sdkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.8k |
|---|---|
| repo stars | ★ 243 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | getsentry/sentry-for-ai ↗ |
What problem does sentry-php-sdk solve for developers using the documented workflows?
Full Sentry SDK setup for PHP. Use when asked to "add Sentry to PHP", "install sentry/sentry", "setup Sentry in PHP", or configure error monitoring, tracing, profiling, logging, metrics, or crons for
Who is it for?
Developers working with sentry-php-sdk patterns described in the skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill documented scope.
When should I use this skill?
Use when Full Sentry SDK setup for PHP. Use when asked to "add Sentry to PHP", "install sentry/sentry", "setup Sentry in PHP", or configure error monitoring, tracing, profiling, logging, me
What you get
Actionable sentry-php-sdk guidance grounded in SKILL.md workflows and reference files.
- Crons instrumentation code
- Monitor slug configuration
- Laravel scheduler macro setup
By the numbers
- Documents 3 Sentry Crons integration approaches
- Minimum sentry/sentry SDK version 3.16.0
- Minimum sentry/sentry-laravel SDK version 3.3.1
Files
All Skills > SDK Setup > PHP SDK
Sentry PHP SDK
Opinionated wizard that scans your PHP project and guides you through complete Sentry setup.
Invoke This Skill When
- User asks to "add Sentry to PHP" or "setup Sentry" in a PHP app
- User wants error monitoring, tracing, profiling, logging, metrics, or crons in PHP
- User mentions
sentry/sentry,sentry/sentry-laravel,sentry/sentry-symfony, or Sentry + any PHP framework - User wants to monitor Laravel routes, Symfony controllers, queues, scheduled tasks, or plain PHP scripts
Note: SDK versions and APIs below reflect Sentry docs at time of writing (sentry/sentry 4.x, sentry/sentry-laravel 4.x, sentry/sentry-symfony 5.x).
Always verify against docs.sentry.io/platforms/php/ before implementing.
---
Phase 1: Detect
Run these commands to understand the project before making recommendations:
# Check existing Sentry
grep -i sentry composer.json composer.lock 2>/dev/null
# Detect framework
cat composer.json | grep -E '"laravel/framework"|"symfony/framework-bundle"|"illuminate/'
# Confirm framework via filesystem markers
ls artisan 2>/dev/null && echo "Laravel detected"
ls bin/console 2>/dev/null && echo "Symfony detected"
# Detect queue systems
grep -E '"laravel/horizon"|"symfony/messenger"' composer.json 2>/dev/null
# Detect AI libraries
grep -E '"openai-php|"openai/|anthropic|llm' composer.json 2>/dev/null
# Check for companion frontend
ls frontend/ resources/js/ assets/ 2>/dev/null
cat package.json 2>/dev/null | grep -E '"react"|"svelte"|"vue"|"next"'What to note:
- Is
sentry/sentry(or-laravel/-symfony) already incomposer.json? If yes, check if the init call exists — may just need feature config. - Framework detected? Laravel (has
artisan+laravel/frameworkin composer.json), Symfony (hasbin/console+symfony/framework-bundle), or plain PHP. - Queue system? (Laravel Queue / Horizon, Symfony Messenger need queue worker configuration.)
- AI libraries? (No PHP AI auto-instrumentation yet — document manually if needed.)
- Companion frontend? (Triggers Phase 4 cross-link.)
---
Phase 2: Recommend
Based on what you found, present a concrete proposal. Don't ask open-ended questions — lead with a recommendation:
Always recommended (core coverage):
- ✅ Error Monitoring — captures unhandled exceptions and PHP errors
- ✅ Logging — Monolog integration (Laravel/Symfony auto-configure; plain PHP uses
MonologHandler)
Recommend when detected:
- ✅ Tracing — web framework detected (Laravel/Symfony auto-instrument HTTP, DB, Twig/Blade, cache)
- ⚡ Profiling — production apps where performance matters (requires
excimerPHP extension, Linux/macOS only) - ⚡ Crons — scheduler patterns detected (Laravel Scheduler, Symfony Scheduler, custom cron jobs)
- ⚡ Metrics — business KPIs or SLO tracking (uses
TraceMetricsAPI)
Recommendation matrix:
| Feature | Recommend when... | Reference |
|---|---|---|
| Error Monitoring | Always — non-negotiable baseline | ${SKILL_ROOT}/references/error-monitoring.md |
| Tracing | Laravel/Symfony detected, or manual spans needed | ${SKILL_ROOT}/references/tracing.md |
| Profiling | Production + excimer extension available | ${SKILL_ROOT}/references/profiling.md |
| Logging | Always; Monolog for Laravel/Symfony | ${SKILL_ROOT}/references/logging.md |
| Metrics | Business events or SLO tracking needed | ${SKILL_ROOT}/references/metrics.md |
| Crons | Scheduler or cron patterns detected | ${SKILL_ROOT}/references/crons.md |
Propose: "I recommend Error Monitoring + Tracing [+ Logging]. Want Profiling, Crons, or Metrics too?"
---
Phase 3: Guide
Install
# Plain PHP
composer require sentry/sentry "^4.0"
# Laravel
composer require sentry/sentry-laravel "^4.0"
# Symfony
composer require sentry/sentry-symfony "^5.0"System requirements:
- PHP 7.2 or later
- Extensions:
ext-json,ext-mbstring,ext-curl(all required) excimerPECL extension (Linux/macOS only — required for profiling)
Framework-Specific Initialization
Plain PHP
Place \Sentry\init() at the top of your entry point (index.php, bootstrap.php, or equivalent), before any application code:
<?php
require_once 'vendor/autoload.php';
\Sentry\init([
'dsn' => $_SERVER['SENTRY_DSN'] ?? '',
'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? 'production',
'release' => $_SERVER['SENTRY_RELEASE'] ?? null,
'send_default_pii' => true,
'traces_sample_rate' => 1.0,
'profiles_sample_rate' => 1.0,
'enable_logs' => true,
]);
// rest of application...Laravel
Step 1 — Register exception handler in bootstrap/app.php:
use Sentry\Laravel\Integration;
return Application::configure(basePath: dirname(__DIR__))
->withExceptions(function (Exceptions $exceptions) {
Integration::handles($exceptions);
})->create();Step 2 — Publish config and set DSN:
php artisan sentry:publish --dsn=YOUR_DSNThis creates config/sentry.php and adds SENTRY_LARAVEL_DSN to .env.
Step 3 — Configure `.env`:
SENTRY_LARAVEL_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_PROFILES_SAMPLE_RATE=1.0For full Laravel configuration options, read ${SKILL_ROOT}/references/laravel.md.Symfony
Step 1 — Register the bundle in config/bundles.php (auto-done by Symfony Flex):
Sentry\SentryBundle\SentryBundle::class => ['all' => true],Step 2 — Create `config/packages/sentry.yaml`:
sentry:
dsn: '%env(SENTRY_DSN)%'
options:
environment: '%env(APP_ENV)%'
release: '%env(SENTRY_RELEASE)%'
send_default_pii: true
traces_sample_rate: 1.0
profiles_sample_rate: 1.0
enable_logs: trueStep 3 — Set the DSN in `.env`:
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0For full Symfony configuration options, read ${SKILL_ROOT}/references/symfony.md.Quick Start — Recommended Init (Plain PHP)
Full init enabling the most features with sensible defaults:
\Sentry\init([
'dsn' => $_SERVER['SENTRY_DSN'] ?? '',
'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? 'production',
'release' => $_SERVER['SENTRY_RELEASE'] ?? null,
'send_default_pii' => true,
// Tracing (lower to 0.1–0.2 in high-traffic production)
'traces_sample_rate' => 1.0,
// Profiling — requires excimer extension (Linux/macOS only)
'profiles_sample_rate' => 1.0,
// Structured logs (sentry/sentry >=4.12.0)
'enable_logs' => true,
]);For Each Agreed Feature
Walk through features one at a time. Load the reference, follow its steps, verify before moving on:
| Feature | Reference file | Load when... |
|---|---|---|
| Error Monitoring | ${SKILL_ROOT}/references/error-monitoring.md | Always (baseline) |
| Tracing | ${SKILL_ROOT}/references/tracing.md | HTTP handlers / distributed tracing |
| Profiling | ${SKILL_ROOT}/references/profiling.md | Performance-sensitive production |
| Logging | ${SKILL_ROOT}/references/logging.md | Always; Monolog for Laravel/Symfony |
| Metrics | ${SKILL_ROOT}/references/metrics.md | Business KPIs / SLO tracking |
| Crons | ${SKILL_ROOT}/references/crons.md | Scheduler / cron patterns detected |
For each feature: Read ${SKILL_ROOT}/references/<feature>.md, follow steps exactly, verify it works.
---
Configuration Reference
Key \Sentry\init() Options (Plain PHP)
| Option | Type | Default | Purpose |
|---|---|---|---|
dsn | `string\ | bool\ | null` |
environment | `string\ | null` | $_SERVER['SENTRY_ENVIRONMENT'] |
release | `string\ | null` | $_SERVER['SENTRY_RELEASE'] |
send_default_pii | bool | false | Include request headers, cookies, IP |
sample_rate | float | 1.0 | Error event sample rate (0.0–1.0) |
traces_sample_rate | `float\ | null` | null |
traces_sampler | `callable\ | null` | null |
profiles_sample_rate | `float\ | null` | null |
enable_logs | bool | false | Send structured logs to Sentry (>=4.12.0) |
max_breadcrumbs | int | 100 | Max breadcrumbs per event |
attach_stacktrace | bool | false | Stack traces on captureMessage() |
in_app_include | string[] | [] | Path prefixes belonging to your app |
in_app_exclude | string[] | [] | Path prefixes for third-party code (hidden in traces) |
ignore_exceptions | string[] | [] | Exception FQCNs to never report |
ignore_transactions | string[] | [] | Transaction names to never report |
error_types | `int\ | null` | error_reporting() |
capture_silenced_errors | bool | false | Capture errors suppressed by @ operator |
max_request_body_size | string | "medium" | "none" / "small" / "medium" / "always" |
before_send | callable | identity | fn(Event $event, ?EventHint $hint): ?Event — return null to drop |
before_breadcrumb | callable | identity | fn(Breadcrumb $b): ?Breadcrumb — return null to discard |
trace_propagation_targets | `string[]\ | null` | null |
strict_trace_continuation | bool | false | Only continue an incoming distributed trace if the sentry-org_id baggage matches the SDK's org ID; prevents trace contamination from third-party Sentry-instrumented services (>=4.21.0) |
debug | bool | false | Verbose SDK output (use a PSR-3 logger option instead for structured output) |
Environment Variables
| Variable | Maps to | Notes |
|---|---|---|
SENTRY_DSN | dsn | Also $_SERVER['SENTRY_DSN'] |
SENTRY_ENVIRONMENT | environment | |
SENTRY_RELEASE | release | Also reads $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] |
SENTRY_SPOTLIGHT | spotlight |
Laravel note: UsesSENTRY_LARAVEL_DSN(falls back toSENTRY_DSN). Other options followSENTRY_TRACES_SAMPLE_RATE,SENTRY_PROFILES_SAMPLE_RATE, etc.
---
Verification
Test that Sentry is receiving events:
// Trigger a real error event — check the Sentry dashboard within seconds
throw new \Exception('Sentry PHP SDK test');Or for a non-crashing check:
\Sentry\captureMessage('Sentry PHP SDK test');Laravel:
php artisan sentry:testIf nothing appears: 1. Enable debug output:
\Sentry\init([
'dsn' => '...',
'logger' => new \Sentry\Logger\DebugStdOutLogger(),
]);2. Verify the DSN is correct (format: https://<key>@o<org>.ingest.sentry.io/<project>) 3. Check SENTRY_DSN (or SENTRY_LARAVEL_DSN) env var is set in the running process 4. For queue workers: ensure Sentry is initialized inside the worker process, not just the web process
---
Phase 4: Cross-Link
After completing PHP setup, check for a companion frontend missing Sentry:
ls frontend/ resources/js/ assets/ public/ 2>/dev/null
cat package.json frontend/package.json 2>/dev/null \
| grep -E '"react"|"svelte"|"vue"|"next"|"nuxt"'If a frontend exists without Sentry, suggest the matching skill:
| Frontend detected | Suggest skill |
|---|---|
| React / Next.js | sentry-react-sdk |
| Svelte / SvelteKit | sentry-svelte-sdk |
| Vue / Nuxt | Use @sentry/vue — see docs.sentry.io/platforms/javascript/guides/vue/ |
| Other JS/TS | sentry-react-sdk (covers generic browser JS patterns) |
---
Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing | Enable logger option (DebugStdOutLogger), verify DSN, check env vars in the running process |
| Malformed DSN error | Format: https://<key>@o<org>.ingest.sentry.io/<project> |
| Laravel exceptions not captured | Ensure Integration::handles($exceptions) is in bootstrap/app.php |
| Symfony exceptions not captured | Verify SentryBundle is registered in config/bundles.php |
| No traces appearing | Set traces_sample_rate (not null); confirm auto-instrumentation is enabled |
| Profiling not working | excimer extension required (Linux/macOS only; not available on Windows); requires traces_sample_rate > 0 |
enable_logs not working | Requires sentry/sentry >= 4.12.0, sentry/sentry-laravel >= 4.15.0, or sentry/sentry-symfony >= 5.4.0 |
| Queue worker errors missing | Init Sentry in the worker process itself, not just the web process; for Laravel use SENTRY_LARAVEL_DSN in worker .env |
| Too many transactions | Lower traces_sample_rate or use traces_sampler to drop health check routes |
| PII not captured | Set send_default_pii: true; for Laravel set send_default_pii: true in config/sentry.php |
@-suppressed errors missing | Set capture_silenced_errors: true |
| Cross-service traces broken | Check trace_propagation_targets; ensure downstream services have Sentry installed |
| Trace contamination from third-party services | Set strict_trace_continuation: true to only continue traces where the incoming sentry-org_id baggage matches your SDK's org ID (>=4.21.0) |
Crons — Sentry PHP SDK
Minimum SDK versions:sentry/sentry≥ 3.16.0 ·sentry/sentry-laravel≥ 3.3.1
Overview
Sentry Crons monitors scheduled jobs by receiving check-ins at job start, success, and failure. Three approaches:
| Approach | Use when |
|---|---|
withMonitor() wrapper | Simple wrapping of any callable |
captureCheckIn() manually | Need control over timing, status, or heartbeats |
sentryMonitor() macro (Laravel) | Laravel scheduled tasks — minimal boilerplate |
Code Examples
withMonitor() wrapper (simplest)
\Sentry\withMonitor(
slug: 'my-cron-job',
callback: fn() => doSomething(),
);
// Sends IN_PROGRESS before, OK on success, ERROR on exceptionManual check-ins with captureCheckIn()
use Sentry\CheckInStatus;
// 1. Signal job started
$checkInId = \Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::inProgress(),
);
try {
// 2. Do work
runScheduledTask();
// 3a. Signal success
\Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::ok(),
checkInId: $checkInId,
);
} catch (\Throwable $e) {
// 3b. Signal failure
\Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::error(),
checkInId: $checkInId,
);
throw $e;
}Heartbeat (single check-in)
Only notifies if the job didn't start when expected (missed). Does not detect max runtime exceeded.
// Success
\Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::ok(),
duration: 10, // optional: seconds
);
// Failure
\Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::error(),
);Upsert monitor config programmatically
Define monitor settings in code so Sentry creates/updates the monitor automatically on first check-in:
use Sentry\CheckInStatus;
use Sentry\MonitorConfig;
use Sentry\MonitorSchedule;
use Sentry\MonitorScheduleUnit;
// Crontab schedule
$monitorConfig = new MonitorConfig(
MonitorSchedule::crontab('0 2 * * *'), // every day at 2 AM
checkinMargin: 5, // minutes late before MISSED alert
maxRuntime: 15, // minutes before TIMEOUT alert
timezone: 'Europe/Vienna',
failureIssueThreshold: 2, // consecutive failures before issue
recoveryThreshold: 5, // consecutive successes to resolve
);
$checkInId = \Sentry\captureCheckIn(
slug: 'daily-backup',
status: CheckInStatus::inProgress(),
monitorConfig: $monitorConfig,
);
runBackup();
\Sentry\captureCheckIn(
slug: 'daily-backup',
status: CheckInStatus::ok(),
checkInId: $checkInId,
);// Interval schedule
$monitorConfig = new MonitorConfig(
MonitorSchedule::interval(10, MonitorScheduleUnit::minute()),
checkinMargin: 5,
maxRuntime: 8,
);Laravel — sentryMonitor() macro
Add sentryMonitor() to any scheduled task in routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command(SendEmailsCommand::class)
->everyHour()
->sentryMonitor(); // that's itWith full configuration:
Schedule::command(SendEmailsCommand::class)
->everyHour()
->sentryMonitor(
monitorSlug: null, // auto-generated if null
checkInMargin: 5, // minutes before MISSED alert
maxRuntime: 15, // minutes before TIMEOUT alert
failureIssueThreshold: 1, // consecutive failures before issue
recoveryThreshold: 1, // consecutive successes to resolve
updateMonitorConfig: true, // set false to manage config in UI only
);Limitation: Tasks usingbetween,unlessBetween,when, orskipare not supported. Use Laravel'scron()method for schedule frequency in those cases.
Symfony — same as base PHP SDK
The Symfony bundle has no dedicated cron integration. Use captureCheckIn() or withMonitor() directly:
use Sentry\CheckInStatus;
// In a Console command or service
$checkInId = \Sentry\captureCheckIn(
slug: 'symfony-cron',
status: CheckInStatus::inProgress(),
);
$this->doWork();
\Sentry\captureCheckIn(
slug: 'symfony-cron',
status: CheckInStatus::ok(),
checkInId: $checkInId,
);CheckInStatus Reference
use Sentry\CheckInStatus;
CheckInStatus::inProgress() // job has started
CheckInStatus::ok() // job completed successfully
CheckInStatus::error() // job failed
// MISSED and TIMEOUT are generated server-side — not sent by SDKMonitorScheduleUnit Reference
use Sentry\MonitorScheduleUnit;
MonitorScheduleUnit::minute()
MonitorScheduleUnit::hour()
MonitorScheduleUnit::day()
MonitorScheduleUnit::week()
MonitorScheduleUnit::month()
MonitorScheduleUnit::year()MonitorConfig Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
$monitorSchedule | MonitorSchedule | ✅ | Crontab or interval schedule |
$checkinMargin | `int\ | null` | No |
$maxRuntime | `int\ | null` | No |
$timezone | `string\ | null` | No |
$failureIssueThreshold | `int\ | null` | No |
$recoveryThreshold | `int\ | null` | No |
Laravel sentryMonitor() Parameters
| Parameter | Default | Description |
|---|---|---|
monitorSlug | null | Custom slug; auto-generated from command name if null |
checkInMargin | 5 | Minutes before check-in is considered missed |
maxRuntime | 15 | Minutes before in-progress is marked timed out |
failureIssueThreshold | 1 | Consecutive failures before creating issue |
recoveryThreshold | 1 | Consecutive successes before resolving issue |
updateMonitorConfig | true | false = configure monitor only in Sentry UI (requires monitorSlug) |
Rate Limits
6 check-ins per minute per monitor-environment. Excess check-ins are silently dropped.
Example:
database-backupinproduction→ up to 6/mindatabase-backupinstaging→ up to 6/min (separate limit)
Verify dropped check-ins on the Sentry Usage Stats page.
Alerts Setup
When a job misses a check-in or reports failure, Sentry creates an error event tagged with monitor.slug:
1. Go to Alerts → Create Alert → select Issues under Errors 2. Filter: The event's tags match monitor.slug equals my-monitor-slug-here
Best Practices
- Use
withMonitor()for simple jobs; use manualcaptureCheckIn()when you need error handling or heartbeats - Provide
MonitorConfigon the first check-in so Sentry creates the monitor automatically — no UI setup needed - For jobs longer than
maxRuntime, send periodicIN_PROGRESScheck-ins as heartbeats to reset the timeout clock - In Laravel, prefer
sentryMonitor()macro over manual check-ins for scheduled tasks - Set
updateMonitorConfig: falsein Laravel when the monitor schedule is managed in the Sentry UI
Troubleshooting
| Issue | Solution |
|---|---|
| Monitor not created in Sentry | Provide MonitorConfig — monitors are not auto-created without it |
| MISSED alerts firing too early | Increase checkinMargin to allow for job startup time |
| TIMEOUT alerts on slow jobs | Increase maxRuntime or send periodic IN_PROGRESS heartbeats |
Laravel sentryMonitor() not working | Check SDK version ≥ 3.3.1; verify ConsoleSchedulingIntegration is active |
between/when tasks not monitored | Use Laravel's cron() method for schedule frequency instead |
| Check-ins silently dropped | You may be hitting the 6/min rate limit — check Usage Stats page |
Error Monitoring — Sentry PHP SDK
Minimum SDK:sentry/sentry^4.0 ·sentry/sentry-laravel^4.0 ·sentry/sentry-symfony^5.0
Configuration
Key \Sentry\init() options for error monitoring:
| Option | Type | Default | Purpose |
|---|---|---|---|
dsn | string | env SENTRY_DSN | Data Source Name; SDK disabled if empty |
environment | string | "production" | Deployment environment tag |
release | string | null | App version string |
sample_rate | float | 1.0 | Fraction of error events to send (0.0–1.0) |
send_default_pii | bool | false | Include IPs, cookies, request body |
attach_stacktrace | bool | false | Add stack traces to captureMessage() |
max_breadcrumbs | int | 100 | Max breadcrumbs per event |
context_lines | int | 5 | Source code lines around each stack frame |
ignore_exceptions | array | [] | Exception classes (matched by instanceof) to never report |
error_types | `int\ | null` | error_reporting() |
capture_silenced_errors | bool | false | Capture errors suppressed with @ operator |
before_send | callable | no-op | Mutate or drop error events before sending |
before_breadcrumb | callable | no-op | Mutate or drop breadcrumbs |
in_app_include | array | [] | Paths to mark as in-app in stack traces |
in_app_exclude | array | [] | Paths to exclude from in-app (e.g., vendor) |
max_request_body_size | string | "medium" | "none" / "small" / "medium" / "always" |
default_integrations | bool | true | Auto-install PHP error/exception/fatal handlers |
Code Examples
Basic setup
// Plain PHP
\Sentry\init([
'dsn' => 'https://<key>@<org>.ingest.sentry.io/<project>',
'environment' => 'production',
'release' => 'my-app@1.2.3',
'sample_rate' => 1.0,
'send_default_pii' => false,
'max_breadcrumbs' => 100,
'in_app_exclude' => ['/var/www/html/vendor'],
]);// Laravel — config/sentry.php (published by php artisan vendor:publish)
return [
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
'release' => env('SENTRY_RELEASE'),
'environment' => env('SENTRY_ENVIRONMENT'), // defaults to APP_ENV
'sample_rate' => (float) env('SENTRY_SAMPLE_RATE', 1.0),
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
'ignore_exceptions' => [],
];# Symfony — config/packages/sentry.yaml
sentry:
dsn: '%env(SENTRY_DSN)%'
register_error_listener: true # auto-captures on kernel.exception
register_error_handler: true # registers PHP error/exception handlers
options:
environment: '%kernel.environment%'
release: '%env(default::SENTRY_RELEASE)%'
sample_rate: 1.0
send_default_pii: false
max_breadcrumbs: 100
in_app_exclude:
- '%kernel.cache_dir%'
- '%kernel.project_dir%/vendor'
ignore_exceptions:
- Symfony\Component\HttpKernel\Exception\NotFoundHttpExceptionCapture APIs
captureException()
// Signature: function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId
// Basic
try {
riskyOperation();
} catch (\Throwable $e) {
\Sentry\captureException($e);
}
// With extra data attached via hint
\Sentry\captureException($e, \Sentry\EventHint::fromArray([
'extra' => ['sql' => $query, 'bindings' => $bindings],
]));
// Mark as unhandled (higher priority / inbox in Sentry UI)
\Sentry\captureException($e, \Sentry\EventHint::fromArray([
'mechanism' => new \Sentry\ExceptionMechanism(
\Sentry\ExceptionMechanism::TYPE_GENERIC,
false // $handled = false
),
]));captureMessage()
// Signature: function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId
\Sentry\captureMessage('Payment gateway timeout');
\Sentry\captureMessage('Low disk space', \Sentry\Severity::warning());
\Sentry\captureMessage('Critical failure', \Sentry\Severity::fatal());
// All Severity factory methods:
// Severity::debug() Severity::info() Severity::warning()
// Severity::error() Severity::fatal()captureEvent()
// Signature: function captureEvent(Event $event, ?EventHint $hint = null): ?EventId
$event = \Sentry\Event::createEvent();
$event->setMessage('Custom billing event');
$event->setLevel(\Sentry\Severity::info());
$event->setTags(['component' => 'payment', 'provider' => 'stripe']);
$event->setExtra(['order_id' => 42, 'retries' => 3]);
$event->setFingerprint(['{{ default }}', 'payment-module']);
$event->setTransaction('checkout.payment');
\Sentry\captureEvent($event);captureLastError()
// Signature: function captureLastError(?EventHint $hint = null): ?EventId
// Reads the most recent error from error_get_last()
register_shutdown_function(function (): void {
\Sentry\captureLastError();
\Sentry\flush(); // required in CLI/shutdown contexts
});
// Note: installed automatically by FatalErrorListenerIntegration
// when default_integrations => true (the default).Automatic capture by framework
// Plain PHP — default integrations auto-install PHP error handlers
\Sentry\init(['dsn' => '...']);
// ErrorListenerIntegration → set_error_handler()
// ExceptionListenerIntegration → set_exception_handler()
// FatalErrorListenerIntegration → register_shutdown_function()
// Laravel 11+ (bootstrap/app.php)
use Sentry\Laravel\Integration;
return Application::configure(basePath: dirname(__DIR__))
->withExceptions(function ($exceptions) {
Integration::handles($exceptions);
})
->create();
// Laravel 10 and below (app/Exceptions/Handler.php)
public function register(): void
{
$this->reportable(function (\Throwable $e) {
\Sentry\Laravel\Integration::captureUnhandledException($e);
});
}
// Symfony — register_error_listener: true in sentry.yaml (default)
// ErrorListener subscribes to kernel.exception and captures automaticallyScope management
Three scope types control where data persists:
| Scope API | Lifetime | Use for |
|---|---|---|
configureScope() | Current scope (persists) | Per-request user identity, session tags |
withScope() | Isolated child scope | Data for a single capture call |
pushScope()/popScope() | Manually isolated scope | Long-running processes (Octane, queues) |
// Persistent — modifies current scope, data appears on all subsequent events
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setTag('tenant', 'acme-corp');
$scope->setUser(['id' => 42, 'email' => 'user@example.com']);
});
// Isolated — child scope discarded after callback; changes don't leak
\Sentry\withScope(function (\Sentry\State\Scope $scope): void {
$scope->setTag('payment_flow', 'checkout');
$scope->setExtra('cart_id', 'cart-123');
\Sentry\captureException(new \RuntimeException('Payment failed'));
});
// ← tag and extra are gone after this line
// Long-running process isolation (prevents cross-request contamination)
$hub = \Sentry\SentrySdk::getCurrentHub();
$hub->pushScope();
try {
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($job): void {
$scope->setTag('job.class', get_class($job));
});
$job->handle();
} finally {
$hub->popScope();
}
// Manual push/pop (lower level)
$hub = \Sentry\SentrySdk::getCurrentHub();
$hub->pushScope();
try {
// ... isolated work ...
} finally {
$hub->popScope();
}Context enrichment
Tags
Tags are indexed — searchable and filterable in the Sentry UI. Max key 32 chars, value 200 chars.
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setTag('environment', 'production');
$scope->setTags(['payment_provider' => 'stripe', 'checkout_version' => 'v3']);
$scope->removeTag('debug_mode');
});
// Per-event only
\Sentry\withScope(function (\Sentry\State\Scope $scope): void {
$scope->setTag('retry_attempt', '2');
\Sentry\captureException(new \RuntimeException('Still failing'));
});User context — UserDataBag
// Via array (Scope::setUser accepts array OR UserDataBag)
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setUser([
'id' => 42,
'email' => 'jane@example.com',
'username' => 'jane_doe',
'ip_address' => '192.168.1.1',
'plan' => 'enterprise', // extra keys become metadata
]);
});
// Via UserDataBag fluent API
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$user = \Sentry\UserDataBag::createFromUserIdentifier(42);
$user->setEmail('jane@example.com');
$user->setMetadata('plan', 'enterprise');
$scope->setUser($user);
});
// Clear user (e.g., on logout)
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->removeUser();
});Breadcrumbs — Breadcrumb constants
// Type constants (Breadcrumb::TYPE_*)
Breadcrumb::TYPE_DEFAULT // 'default' — generic log entry
Breadcrumb::TYPE_HTTP // 'http' — outgoing HTTP request
Breadcrumb::TYPE_USER // 'user' — user interaction
Breadcrumb::TYPE_NAVIGATION // 'navigation' — URL/route change
Breadcrumb::TYPE_ERROR // 'error' — captured error
// Level constants (Breadcrumb::LEVEL_*)
Breadcrumb::LEVEL_DEBUG
Breadcrumb::LEVEL_INFO
Breadcrumb::LEVEL_WARNING
Breadcrumb::LEVEL_ERROR
Breadcrumb::LEVEL_FATAL
// Shorthand via global function (PHP 8 named args)
\Sentry\addBreadcrumb(
category: 'auth',
message: 'User authenticated',
metadata: ['user_id' => 42, 'method' => 'oauth'],
level: \Sentry\Breadcrumb::LEVEL_INFO,
type: \Sentry\Breadcrumb::TYPE_DEFAULT,
);
// Explicit Breadcrumb object
\Sentry\addBreadcrumb(new \Sentry\Breadcrumb(
\Sentry\Breadcrumb::LEVEL_INFO,
\Sentry\Breadcrumb::TYPE_HTTP,
'http.client',
'POST https://api.stripe.com/v1/charges',
['status_code' => 200, 'duration_ms' => 243]
));
// Navigation crumb
\Sentry\addBreadcrumb(new \Sentry\Breadcrumb(
\Sentry\Breadcrumb::LEVEL_INFO,
\Sentry\Breadcrumb::TYPE_NAVIGATION,
'navigation',
null,
['from' => '/home', 'to' => '/checkout']
));
// Breadcrumb is immutable — modify via with*() methods (return clones)
$crumb = new \Sentry\Breadcrumb(
\Sentry\Breadcrumb::LEVEL_INFO,
\Sentry\Breadcrumb::TYPE_DEFAULT,
'cache', 'Cache miss', ['key' => 'user:42:profile']
);
$crumb = $crumb->withLevel(\Sentry\Breadcrumb::LEVEL_WARNING)
->withMetadata('ttl', 300);
\Sentry\addBreadcrumb($crumb);Structured named context
Named context blocks appear as collapsible panels in the Sentry event UI.
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setContext('payment', [
'provider' => 'stripe',
'amount' => 9900,
'currency' => 'USD',
]);
});
// Per-event
\Sentry\withScope(function (\Sentry\State\Scope $scope) use ($exception): void {
$scope->setContext('order', [
'id' => 'ord-999',
'items' => 3,
'total' => 59.99,
'status' => 'pending',
]);
\Sentry\captureException($exception);
});
// Reserved names with special UI rendering: os, runtime, app, browser, device, gpu, culture, tracebefore_send hook — filtering and scrubbing
// Callback signature:
// callable(\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event
// Return null to DROP the event; return (modified) event to send.
\Sentry\init([
'before_send' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
// Drop health-check routes
$request = $event->getRequest();
if ($request?->getUrl() && str_contains($request->getUrl(), '/healthz')) {
return null;
}
// Scrub sensitive extra data
$extra = $event->getExtra();
unset($extra['password'], $extra['credit_card']);
$event->setExtra($extra);
// Add tag based on exception type
if ($hint?->exception instanceof \PDOException) {
$event->setTag('db_error', 'true');
}
// Custom fingerprint based on message
if ($hint?->exception && str_contains($hint->exception->getMessage(), 'timeout')) {
$event->setFingerprint(['timeout', get_class($hint->exception)]);
}
return $event;
},
]);before_breadcrumb hook
// Callback signature:
// callable(\Sentry\Breadcrumb $breadcrumb): ?\Sentry\Breadcrumb
// Return null to DROP; return (modified) Breadcrumb to keep.
\Sentry\init([
'before_breadcrumb' => function (\Sentry\Breadcrumb $breadcrumb): ?\Sentry\Breadcrumb {
// Drop SQL queries containing sensitive data
if ($breadcrumb->getCategory() === 'db.sql.query') {
if (str_contains(strtolower($breadcrumb->getMessage() ?? ''), 'password')) {
return null;
}
}
// Redact API keys from URLs
$metadata = $breadcrumb->getMetadata();
if (isset($metadata['url']) && str_contains($metadata['url'], 'api_key=')) {
return $breadcrumb->withMetadata(
'url',
preg_replace('/api_key=[^&]+/', 'api_key=[Filtered]', $metadata['url'])
);
}
return $breadcrumb;
},
]);Additional hook variants
// Filter performance transactions (not error events)
'before_send_transaction' => function (\Sentry\Event $tx, ?\Sentry\EventHint $hint): ?\Sentry\Event {
if (in_array($tx->getTransaction(), ['GET /up', 'GET /ping', 'GET /healthz'])) {
return null;
}
return $tx;
},
// Filter cron monitor check-ins
'before_send_check_in' => function (\Sentry\Event $checkIn, ?\Sentry\EventHint $hint): ?\Sentry\Event {
return $checkIn; // or null to drop
},
// NOTE: Symfony uses service IDs instead of closures:
// options: { before_send: 'App\Sentry\BeforeSendHandler' }Error context — EventHint and ExceptionMechanism
// EventHint public properties:
// $exception — original \Throwable
// $mechanism — ExceptionMechanism (how it was caught)
// $stacktrace — custom Stacktrace override
// $extra — array<string, mixed> passed to before_send but not in event body
// Handled vs unhandled — affects priority and inbox routing in Sentry
use Sentry\ExceptionMechanism;
$hint = \Sentry\EventHint::fromArray([
'exception' => $exception,
'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false), // unhandled
]);
\Sentry\captureException($exception, $hint);
// Exception chaining — SDK auto-walks $e->getPrevious()
// All chained exceptions appear as exception.values[] in the event
try {
try {
$pdo->query($sql); // throws PDOException
} catch (\PDOException $dbEx) {
throw new \DomainException('Payment failed', 0, $dbEx);
}
} catch (\DomainException $e) {
\Sentry\captureException($e);
// Sentry event: exception.values[0] = DomainException
// exception.values[1] = PDOException ($previous)
}
// Pass data through hint to before_send without including it in the event body
$hint = \Sentry\EventHint::fromArray([
'extra' => ['query' => $sql, 'duration_ms' => $ms],
]);
\Sentry\captureException($exception, $hint);
// Access in before_send
'before_send' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
if ($hint !== null && ($hint->extra['duration_ms'] ?? 0) > 5000) {
$event->setTag('slow_query', 'true');
}
return $event;
},Fingerprinting (custom grouping)
Default fingerprint: ['{{ default }}'] — stack trace hash + exception type + message.
// Via scope — persistent
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setFingerprint(['payment-timeout', 'stripe-api']);
});
// Via withScope — per-event, combined with default
\Sentry\withScope(function (\Sentry\State\Scope $scope) use ($exception, $provider): void {
$scope->setFingerprint(['{{ default }}', 'payment', $provider]);
\Sentry\captureException($exception);
});
// Via before_send — dynamic
\Sentry\init([
'before_send' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
if ($hint?->exception instanceof \PDOException) {
$msg = $hint->exception->getMessage();
if (str_contains($msg, 'timed out')) {
$event->setFingerprint(['db-timeout', 'pdo']);
}
}
return $event;
},
]);
// Via captureEvent — inline
$event = \Sentry\Event::createEvent();
$event->setFingerprint(['my-custom-group', '{{ default }}']);
$event->setMessage('Grouped event');
\Sentry\captureEvent($event);
// Scope fingerprints are APPENDED to any existing event fingerprints:
// array_merge($event->getFingerprint(), $scope->getFingerprint())Framework-Specific Notes
Plain PHP
default_integrations: true(default) auto-installsset_error_handler(),set_exception_handler(), andregister_shutdown_function()— no manual handlers needed- Always call
\Sentry\flush()before process exit in CLI scripts - In long-running workers (e.g., RoadRunner, Swoole), use
pushScope()/popScope()per request to prevent cross-request scope contamination
Laravel
- Laravel 11+: use
Integration::handles($exceptions)inbootstrap/app.php - Laravel 10 and below: use
Integration::captureUnhandledException($e)inHandler::register() Integration::captureUnhandledException()inspects the call stack to guess whether the exception was truly unhandled and sets theExceptionMechanism::$handledflag accordingly- Laravel auto-sets user context from
Auth::user()on theAuthenticatedevent (readsid,email,username) - Octane: scope is automatically isolated per request via
pushScope()/popScope()in the Octane event handlers — no manual action needed SENTRY_SEND_DEFAULT_PII=trueis required to capture user email and IP- Log levels map to breadcrumb levels:
critical/alert/emergency→LEVEL_FATAL,warning→LEVEL_WARNING,error→LEVEL_ERROR,info/notice→LEVEL_INFO,debug→LEVEL_DEBUG
Symfony
- Errors are captured by
ErrorListeneron thekernel.exceptionevent — controlled byregister_error_listener: true(default) - User context requires
send_default_pii: true; populated byLoginListeneronLoginSuccessEvent/AuthenticationSuccessEvent - Inject
HubInterfacevia DI rather than using global\Sentry\*functions in services before_sendin Symfony must be a service ID, not a closure — closures cannot be serialized and break config caching:
// src/Sentry/BeforeSendHandler.php
namespace App\Sentry;
use Sentry\Event;
use Sentry\EventHint;
class BeforeSendHandler
{
public function __invoke(Event $event, ?EventHint $hint): ?Event
{
// ... filter / scrub ...
return $event;
}
}# config/packages/sentry.yaml
sentry:
options:
before_send: 'App\Sentry\BeforeSendHandler'MessengerListenercaptures exceptions from failed Messenger messages — inject it if using the Messenger component
Best Practices
- Set
send_default_pii: false(default) — add explicit scrubbing inbefore_sendfor any sensitive fields - Use
configureScope()for per-request data (user identity) andwithScope()for per-capture isolation - Prefer
setContext()for structured detail data; usesetTag()only for fields you want to filter/search on - Use
ignore_exceptions: [...]for exceptions that should never be reported (e.g.,NotFoundHttpException) - In PHP-FPM (one process per request), global scope is safe. In long-running servers (Octane, RoadRunner), always isolate scope per request/task
ignore_exceptionsruns beforebefore_send— matching classes are silently dropped andbefore_sendis never calledEventHintreceived inbefore_sendis the same object passed tocaptureException()— use it to access the originalThrowableand anyextracontext
Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing in Sentry | Verify DSN, call \Sentry\init() before all other code, try 'debug' => true to log to error_log() |
| User/tag data missing from events | Set scope data before the exception occurs; in Laravel, check SENTRY_SEND_DEFAULT_PII |
| PII appearing in events | Ensure send_default_pii: false (default), add before_send scrubber for headers/cookies |
captureException() sends no event | Check ignore_exceptions — class may match by instanceof; verify before_send isn't returning null |
| Duplicate events in Laravel | Integration::handles() (L11) already calls captureUnhandledException() — don't also add a manual reportable() |
| Cross-request scope contamination in Octane | Enable Octane breadcrumb events (already in default config); use pushScope()/popScope() in queue workers |
before_send option ignored in Symfony | Must be a service ID string, not a closure — closures can't be serialized for config caching |
| Breadcrumbs missing | Check max_breadcrumbs setting and before_breadcrumb hook; in Laravel verify breadcrumbs.* config flags |
| Fatal errors not captured | Ensure default_integrations: true (default) or manually call register_shutdown_function() with captureLastError() + flush() |
Laravel — Sentry SDK Deep Dive
Package:sentry/sentry-laravel· Requiressentry/sentry ^4.21.0
Laravel versions:^6.0through^12.0(Lumen supported)
---
Installation & Setup
Requirements
- PHP
^7.2 | ^8.0 - Laravel
^6.0–^12.0 zend.exception_ignore_args: Offinphp.ini(required for stack trace arguments)
Install
composer require sentry/sentry-laravelAuto-registration via Laravel Package Discovery — no manual config/app.php entry needed. Two service providers register automatically:
Sentry\Laravel\ServiceProviderSentry\Laravel\Tracing\ServiceProvider
Step 1 — Hook Exception Handler (Laravel 11+)
In bootstrap/app.php:
use Sentry\Laravel\Integration;
->withExceptions(function (Exceptions $exceptions) {
Integration::handles($exceptions);
})For Laravel 10 and earlier, add to app/Exceptions/Handler.php:
public function register(): void
{
$this->reportable(function (\Throwable $e) {
\Sentry\Laravel\Integration::captureUnhandledException($e);
});
}Step 2 — Publish Config & Set DSN
php artisan sentry:publish --dsn=___PUBLIC_DSN___This creates config/sentry.php and writes SENTRY_LARAVEL_DSN=your_dsn to .env.
Step 3 — Verify
php artisan sentry:testOr add a test route:
Route::get('/debug-sentry', function () {
throw new Exception('My first Sentry error!');
});---
Environment Variables
| Variable | Purpose | Notes |
|---|---|---|
SENTRY_LARAVEL_DSN | Primary DSN | Takes priority over SENTRY_DSN |
SENTRY_DSN | Generic DSN fallback | Used if SENTRY_LARAVEL_DSN not set |
SENTRY_RELEASE | Release version string | Mapped to release option |
SENTRY_ENVIRONMENT | Environment name | Falls back to APP_ENV if not set |
SENTRY_TRACES_SAMPLE_RATE | Enable/configure tracing | Must be > 0.0 to enable tracing |
SENTRY_PROFILES_SAMPLE_RATE | Enable profiling | Relative to traces sample rate |
SENTRY_ENABLE_LOGS | Enable structured Sentry Logs | true / false |
SENTRY_SEND_DEFAULT_PII | Capture PII (IP, etc.) | true / false |
LOG_CHANNEL | Laravel log channel | e.g., stack |
LOG_STACK | Stack channels | e.g., single,sentry_logs |
LOG_LEVEL | Log level threshold | e.g., info |
SENTRY_LOG_LEVEL | Sentry-specific log level override | Defaults to LOG_LEVEL |
---
config/sentry.php — Complete Options
Root Options
| Key | Default | Description |
|---|---|---|
dsn | env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')) | Sentry DSN |
release | env('SENTRY_RELEASE') | Release version |
environment | env('SENTRY_ENVIRONMENT') | Falls back to APP_ENV |
sample_rate | 1.0 | Error event sample rate (0.0–1.0) |
traces_sample_rate | null | Transaction sample rate; enables tracing when set |
profiles_sample_rate | null | Profile sample rate; relative to traces rate |
enable_logs | false | Enable Sentry structured logs |
send_default_pii | false | Capture PII (IP, cookies, headers) |
ignore_exceptions | [] | Exception FQCNs to suppress |
ignore_transactions | ['/up'] | Transaction names to suppress (health check) |
logs_channel_level | 'debug' | Min log level for the sentry log channel (Laravel-only, not forwarded to core SDK) |
Breadcrumb Options
All controlled per-feature in the breadcrumbs sub-array:
| Key | ENV Variable | Default | What it captures |
|---|---|---|---|
breadcrumbs.logs | SENTRY_BREADCRUMBS_LOGS_ENABLED | true | Log message breadcrumbs |
breadcrumbs.cache | SENTRY_BREADCRUMBS_CACHE_ENABLED | true | Cache operation breadcrumbs |
breadcrumbs.livewire | SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED | true | Livewire breadcrumbs |
breadcrumbs.sql_queries | SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED | true | SQL query breadcrumbs |
breadcrumbs.sql_bindings | SENTRY_BREADCRUMBS_SQL_BINDINGS_ENABLED | false | Include SQL parameter bindings |
breadcrumbs.queue_info | SENTRY_BREADCRUMBS_QUEUE_INFO_ENABLED | true | Queue job breadcrumbs |
breadcrumbs.command_info | SENTRY_BREADCRUMBS_COMMAND_JOBS_ENABLED | true | Artisan command breadcrumbs |
breadcrumbs.http_client_requests | SENTRY_BREADCRUMBS_HTTP_CLIENT_REQUESTS_ENABLED | true | HTTP client breadcrumbs |
breadcrumbs.notifications | SENTRY_BREADCRUMBS_NOTIFICATIONS_ENABLED | true | Notification breadcrumbs |
Tracing Options
| Key | ENV Variable | Default | Description |
|---|---|---|---|
tracing.queue_job_transactions | SENTRY_TRACE_QUEUE_ENABLED | true | Queue jobs as root transactions |
tracing.queue_jobs | SENTRY_TRACE_QUEUE_JOBS_ENABLED | true | Queue jobs as child spans |
tracing.sql_queries | SENTRY_TRACE_SQL_QUERIES_ENABLED | true | SQL queries as spans |
tracing.sql_bindings | SENTRY_TRACE_SQL_BINDINGS_ENABLED | false | Include SQL bindings in spans |
tracing.sql_origin | SENTRY_TRACE_SQL_ORIGIN_ENABLED | true | Track origin of SQL queries |
tracing.sql_origin_threshold_ms | SENTRY_TRACE_SQL_ORIGIN_THRESHOLD_MS | 100 | Only track origin above this ms |
tracing.views | SENTRY_TRACE_VIEWS_ENABLED | true | Blade view rendering spans |
tracing.livewire | SENTRY_TRACE_LIVEWIRE_ENABLED | true | Livewire component spans |
tracing.http_client_requests | SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ENABLED | true | HTTP client spans |
tracing.cache | SENTRY_TRACE_CACHE_ENABLED | true | Cache operation spans |
tracing.redis_commands | SENTRY_TRACE_REDIS_COMMANDS | false | Redis command spans |
tracing.redis_origin | SENTRY_TRACE_REDIS_ORIGIN_ENABLED | true | Track Redis command origins |
tracing.notifications | SENTRY_TRACE_NOTIFICATIONS_ENABLED | true | Notification sending spans |
tracing.missing_routes | SENTRY_TRACE_MISSING_ROUTES_ENABLED | false | Track 404 routes as transactions |
tracing.continue_after_response | SENTRY_TRACE_CONTINUE_AFTER_RESPONSE | true | Continue traces after response |
tracing.default_integrations | SENTRY_TRACE_DEFAULT_INTEGRATIONS_ENABLED | true | Register default tracing integrations |
---
Auto-Instrumented Operations
The Laravel SDK auto-instruments the following (via EventHandler + feature integrations):
| Operation | Span Op | Minimum Version |
|---|---|---|
| HTTP request lifecycle | http.server | All |
| Database queries | db.sql.query | All |
| Database transactions | db.transaction | All |
| View rendering (Blade) | view.render | All |
| Queue job publishing | queue.publish | All |
| Queue job processing | queue.process | All |
| Cache operations | (cache spans) | Laravel ≥ v11.11.0 |
| HTTP Client requests | (http.client spans) | Laravel ≥ v8.45.0 |
| Redis operations | (redis spans) | All |
| Notifications | (notification spans) | All |
| Livewire components | (livewire spans) | Requires livewire/livewire |
| Lighthouse GraphQL | (graphql spans) | Requires nuwave/lighthouse |
| Folio routes | breadcrumb + transaction name | Requires laravel/folio |
| Filesystem disk operations | (file spans) | Opt-in, see below |
Filesystem Disk Instrumentation (Opt-in)
// config/filesystems.php — wrap ALL disks
'disks' => Sentry\Laravel\Features\Storage\Integration::configureDisks([
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
's3' => [
'driver' => 's3',
// ...
],
], /* enableSpans: */ true, /* enableBreadcrumbs: */ true),
// Or wrap a single disk
's3' => Sentry\Laravel\Features\Storage\Integration::configureDisk('s3', [
// ... disk config ...
], /* enableSpans: */ true, /* enableBreadcrumbs: */ true),---
Middleware
Three middleware are auto-registered — no manual setup needed:
| Middleware | Purpose |
|---|---|
SetRequestMiddleware | Converts and caches PSR-7 request early (prevents upload parsing failures) |
SetRequestIpMiddleware | Sets request IP on Sentry scope |
FlushEventsMiddleware | Flushes pending events in terminate() phase after response sent |
The tracing middleware (Tracing\Middleware) is auto-prepended via $httpKernel->prependMiddleware() — it runs before all user middleware and records the full boot time.
FastCGI behavior: On FastCGI, the terminate phase runs after the response is sent to the client, so Sentry upload does not add user-visible latency. On non-FastCGI (built-in server, RoadRunner), the response is delayed by the Sentry upload — use a local Relay proxy in that case.
---
Log Channels
Laravel provides two separate Sentry log channel drivers:
sentry channel — Error Events/Breadcrumbs (classic)
Sends log messages as Sentry error events or breadcrumbs.
// config/logging.php
'channels' => [
'sentry' => [
'driver' => 'sentry',
'level' => env('LOG_LEVEL', 'error'),
'bubble' => true,
],
],sentry_logs channel — Structured Sentry Logs (≥ 4.15.0)
Sends structured logs to the Sentry Logs product (not as error events).
# .env
LOG_CHANNEL=stack
LOG_STACK=single,sentry_logs
SENTRY_ENABLE_LOGS=true
LOG_LEVEL=info
SENTRY_LOG_LEVEL=warning// config/logging.php (required for SDK <= 4.16.0; auto-registered in > 4.16.0)
'sentry_logs' => [
'driver' => 'sentry_logs',
'level' => env('LOG_LEVEL', 'info'),
],// Usage
use Illuminate\Support\Facades\Log;
Log::info('User logged in', ['user_id' => $user->id]);
Log::warning('User {id} failed to login.', ['id' => $user->id]);
Log::error('Something went wrong', ['user_id' => auth()->id(), 'action' => 'update_profile']);
// Send only to Sentry (bypass other channels)
Log::channel('sentry_logs')->error('This goes only to Sentry');Auto-flush: When enable_logs is true, the ServiceProvider registers a terminating callback that flushes pending logs automatically.
Troubleshooting Tinker: tinker doesn't trigger the normal request lifecycle — flush manually:
\Sentry\logger()->flush();---
Queue Integration
The queue integration is the most complete in any framework. All of the following is automatic when traces_sample_rate is set:
Distributed Tracing Across Queue
Trace context is automatically injected into job payloads using these keys:
const QUEUE_PAYLOAD_BAGGAGE_DATA = 'sentry_baggage_data';
const QUEUE_PAYLOAD_TRACE_PARENT_DATA = 'sentry_trace_parent_data';
const QUEUE_PAYLOAD_PUBLISH_TIME = 'sentry_publish_time';The worker side automatically reads these and calls continueTrace() — the queue job transaction is linked to the originating HTTP request transaction, giving you end-to-end distributed traces.
Transaction Attributes
Queue process transactions include OpenTelemetry-aligned attributes:
| Attribute | Description |
|---|---|
messaging.system | Queue driver (e.g., redis, sqs) |
messaging.destination.name | Queue name |
messaging.message.id | Job ID |
messaging.message.receive.latency | Time from dispatch to processing (ms) |
Config
// config/sentry.php
'tracing' => [
'queue_job_transactions' => true, // Full distributed transaction per job
'queue_jobs' => true, // Child spans for jobs within a transaction
],
'breadcrumbs' => [
'queue_info' => true, // Job name/queue/attempts breadcrumbs
],---
Cron Monitoring (Scheduled Tasks)
sentryMonitor() Macro
Add to scheduled tasks in routes/console.php (Laravel 9+) or app/Console/Kernel.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command(SendEmailsCommand::class)
->everyHour()
->sentryMonitor();With full configuration:
Schedule::command(SendEmailsCommand::class)
->everyHour()
->sentryMonitor(
monitorSlug: null, // Auto-generated if null
checkInMargin: 5, // Minutes before check-in is considered missed
maxRuntime: 15, // Minutes before in-progress is marked timed out
failureIssueThreshold: 1, // Consecutive failures before creating issue
recoveryThreshold: 1, // Consecutive successes before resolving issue
updateMonitorConfig: false, // Set false to configure only in UI
);⚠️ Limitation: Tasks using between, unlessBetween, when, and skip methods are not supported. Use cron('...') for the schedule frequency instead.
Automatic Slug Generation
When no slug is provided:
- Commands:
"scheduled_emails-send"(from command name) - Jobs:
"scheduled_send-email-job"(from reversed class name)
Automatic Transaction Tracing
ConsoleSchedulingIntegration also creates tracing transactions for scheduled tasks automatically (no additional config beyond traces_sample_rate):
op: 'console.command.scheduled'source: TransactionSource::task()
---
Artisan Commands
| Command | Description |
|---|---|
php artisan sentry:publish --dsn=DSN | Publish config/sentry.php, write DSN to .env, optionally enable PII/tracing, send test event |
php artisan sentry:test | Send a test exception (and optionally a test transaction) to verify configuration |
php artisan about | Displays Sentry version/config info (via AboutCommandIntegration) |
---
User Context
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setUser([
'id' => auth()->user()->id,
'email' => auth()->user()->email,
]);
});Requires 'send_default_pii' => true in config/sentry.php.
---
Closures and Config Caching
php artisan config:cache will fail if config/sentry.php contains PHP closures (e.g., inline before_send callbacks). Use a static class method callable instead:
// config/sentry.php — safe for config:cache
'before_send' => [App\Sentry\Callbacks::class, 'beforeSend'],
'before_send_log' => [App\Sentry\Callbacks::class, 'beforeSendLog'],
'before_send_metric' => [App\Sentry\Callbacks::class, 'beforeSendMetric'],
'traces_sampler' => [App\Sentry\Callbacks::class, 'tracesSampler'],// app/Sentry/Callbacks.php
namespace App\Sentry;
use Sentry\Event;
use Sentry\EventHint;
class Callbacks
{
public static function beforeSend(Event $event, ?EventHint $hint): ?Event
{
// return null to drop the event
return $event;
}
public static function tracesSampler(\Sentry\Tracing\SamplingContext $context): float
{
return $context->getParentSampled() ? 1.0 : 0.25;
}
}---
Lumen Support
// bootstrap/app.php
$app->register(Sentry\Laravel\ServiceProvider::class);Lumen does not auto-register service providers via Package Discovery. No artisan sentry:publish — create config/sentry.php manually and configure it as a Lumen config file:
$app->configure('sentry');---
Laravel Octane (Long-Running Server)
When using Laravel Octane (Swoole/RoadRunner/FrankenPHP), requests are handled in long-lived workers. Sentry scope must be isolated per request:
// The SDK automatically handles this via context isolation
// when the Octane integration is active⚠️ Important: If using withScope() / configureScope() for per-request context, ensure you're using withScope() (not configureScope()) in Octane environments — configureScope() persists across requests in long-running workers.
---
Feature Flags (Laravel Pennant)
The PennantIntegration auto-records Pennant feature flag evaluations:
use Laravel\Pennant\Feature;
// Checked features are automatically added as feature flags in Sentry
$value = Feature::value('billing-v2');
$active = Feature::active('new-onboarding');No configuration needed — enabled automatically when laravel/pennant is installed.
---
Complete .env Reference
# Required
SENTRY_LARAVEL_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
# Release & environment
SENTRY_RELEASE=1.0.0
SENTRY_ENVIRONMENT=production # defaults to APP_ENV
# Performance
SENTRY_TRACES_SAMPLE_RATE=0.1 # 10% of transactions
SENTRY_PROFILES_SAMPLE_RATE=0.1 # 10% of traced transactions
# Logging
SENTRY_ENABLE_LOGS=true
LOG_CHANNEL=stack
LOG_STACK=single,sentry_logs
LOG_LEVEL=info
SENTRY_LOG_LEVEL=warning
# Privacy
SENTRY_SEND_DEFAULT_PII=false
# Breadcrumb toggles (all default true)
SENTRY_BREADCRUMBS_SQL_BINDINGS_ENABLED=false
SENTRY_BREADCRUMBS_CACHE_ENABLED=true
# Tracing toggles
SENTRY_TRACE_REDIS_COMMANDS=false
SENTRY_TRACE_MISSING_ROUTES_ENABLED=falseLogging — Sentry PHP SDK
Minimum SDK versions:sentry/sentry≥ 4.12.0 ·sentry/sentry-laravel≥ 4.15.0 ·sentry/sentry-symfony≥ 5.4.0
Overview
Sentry PHP structured logs are separate from error reporting. They produce searchable log records in the Sentry Logs UI. The feature must be explicitly enabled with enable_logs: true.
Configuration
PHP (base SDK)
\Sentry\init([
'dsn' => '___PUBLIC_DSN___',
'enable_logs' => true,
]);Laravel (.env)
LOG_CHANNEL=stack
LOG_STACK=single,sentry_logs
SENTRY_ENABLE_LOGS=true
LOG_LEVEL=info
SENTRY_LOG_LEVEL=warning # optional: Sentry-specific thresholdconfig/sentry.php:
'enable_logs' => env('SENTRY_ENABLE_LOGS', false),
'logs_channel_level' => env('SENTRY_LOG_LEVEL', env('LOG_LEVEL', 'debug')),For SDK versions ≤ 4.16.0, also addsentry_logstoconfig/logging.phpchannels manually. Versions > 4.16.0 auto-register it.
Symfony (config/packages/sentry.yaml + monolog.yaml)
# config/packages/sentry.yaml
sentry:
options:
enable_logs: true# config/packages/monolog.yaml
monolog:
handlers:
sentry_logs:
type: service
id: Sentry\SentryBundle\Monolog\LogsHandler# config/services.yaml
services:
Sentry\SentryBundle\Monolog\LogsHandler:
arguments:
- !php/const Monolog\Logger::INFOCode Examples
PHP — direct logger API
\Sentry\logger()->trace('Starting request processing');
\Sentry\logger()->debug('Cache lookup for key %s', values: ['user:42']);
\Sentry\logger()->info('User logged in');
\Sentry\logger()->warn('Rate limit approaching for %s', values: ['/api/v1/users']);
\Sentry\logger()->error('Payment failed for order %s', values: [$orderId]);
\Sentry\logger()->fatal('Database connection pool exhausted');
// Must flush at end of script (CLI) or long-running processes
\Sentry\logger()->flush();PHP — with custom attributes
\Sentry\logger()->warn('This is a warning log with attributes.', attributes: [
'attribute1' => 'string',
'attribute2' => 1,
'attribute3' => 1.0,
'attribute4' => true,
]);PHP — Monolog bridge
use Monolog\Level;
use Monolog\Logger;
\Sentry\init([
'dsn' => '___PUBLIC_DSN___',
'enable_logs' => true,
]);
$log = new Logger('app');
$log->pushHandler(new \Sentry\Monolog\LogsHandler(
hub: \Sentry\SentrySdk::getCurrentHub(),
level: Level::Info,
));
$log->info('Application started');
$log->error('Something went wrong', ['user_id' => 42]);
\Sentry\logger()->flush();Laravel — Laravel Log facade
use Illuminate\Support\Facades\Log;
Log::info('This is an info message');
Log::warning('User {id} failed to login.', ['id' => $user->id]);
Log::error('Payment failed', [
'user_id' => auth()->id(),
'order_id' => $orderId,
]);
// Send only to Sentry (not to other channels)
Log::channel('sentry_logs')->error('Critical failure in payment module');Symfony — via injected LoggerInterface
// Inject via constructor or autowiring
$this->logger->info('User {id} logged in', ['id' => $userId]);
$this->logger->warning('Slow query detected', ['duration_ms' => 850]);
$this->logger->error('Payment processing failed', [
'user_id' => $userId,
'action' => 'checkout',
]);Filtering with before_send_log
PHP / Laravel:
\Sentry\init([
'dsn' => '___PUBLIC_DSN___',
'enable_logs' => true,
'before_send_log' => function (\Sentry\Logs\Log $log): ?\Sentry\Logs\Log {
if ($log->getLevel() === \Sentry\Logs\LogLevel::info()) {
return null; // drop info logs
}
return $log;
},
]);Symfony (uses service ID, not a closure):
sentry:
options:
before_send_log: "sentry.callback.before_send_log"// App\Service\Sentry
public function getBeforeSendLog(): callable
{
return function (\Sentry\Logs\Log $log): ?\Sentry\Logs\Log {
if ($log->getLevel() === \Sentry\Logs\LogLevel::info()) {
return null;
}
return $log;
};
}Two Log Channel Types in Laravel
| Channel | Driver | Purpose |
|---|---|---|
sentry | sentry | Sends log messages as Sentry error events/breadcrumbs |
sentry_logs | sentry_logs | Sends structured logs to the Sentry Logs product |
These are independent — use sentry_logs for the structured logs feature.
Log Levels
| Method | PSR Level |
|---|---|
trace() | debug |
debug() | debug |
info() | info |
warn() | warning |
error() | error |
fatal() | critical |
Automatically Added Attributes
Every log record receives these automatically:
| Attribute | Description |
|---|---|
sentry.environment | Environment from SDK config |
sentry.release | Release from SDK config |
sentry.sdk.name / sentry.sdk.version | SDK metadata |
sentry.server.address | Server hostname |
sentry.message.template | Parameterized template string |
sentry.message.parameter.N | Template parameter values |
user.id, user.name, user.email | Active scope user (if set) |
sentry.origin | Log origin (e.g., auto.log.monolog) |
Flushing
Logs are buffered and must be flushed to be sent:
| Context | Behavior |
|---|---|
| PHP (CLI/scripts) | Call \Sentry\logger()->flush() manually at end of execution |
| Laravel | Auto-flushed via app->terminating() callback |
| Symfony (HTTP) | Auto-flushed on kernel.terminate by LogRequestListener |
| Symfony (console) | Auto-flushed on console.terminate by ConsoleListener |
Long-running CLI tasks: Call \Sentry\logger()->flush() periodically to avoid memory buildup.
Troubleshooting
| Issue | Solution |
|---|---|
| Logs not appearing | Verify enable_logs: true and that \Sentry\logger()->flush() is called |
| Laravel logs missing | Check LOG_STACK includes sentry_logs and LOG_LEVEL permits expected messages |
| Symfony logs missing | Verify LogsHandler is registered in monolog.yaml and enable_logs: true is set |
| Tinker session missing logs | Manually call \Sentry\logger()->flush() — Tinker skips normal lifecycle |
| Info logs filtered out | Check before_send_log callback and SENTRY_LOG_LEVEL threshold |
Metrics — Sentry PHP SDK
Minimum SDK versions:sentry/sentry≥ 4.19.0 ·sentry/sentry-laravel≥ 4.20.0 ·sentry/sentry-symfony≥ 5.8.0
Overview
Custom metrics (counters, distributions, gauges) are enabled by default — no extra flag required. Use \Sentry\traceMetrics() as the entry point.
Note: The old\Sentry\metrics()API is fully deprecated — all methods are no-ops. Use\Sentry\traceMetrics()instead.
Metric Types
| Type | Method | Aggregations | Use for |
|---|---|---|---|
| Counter | count() | sum | Event occurrences, request counts |
| Distribution | distribution() | p90, min, max, avg | Latencies, sizes — supports percentiles |
| Gauge | gauge() | min, max, avg, sum, count | Current values — no percentiles |
Code Examples
Counter — event occurrences
\Sentry\traceMetrics()->count('button-click', 5, [
'browser' => 'Firefox',
'app_version' => '1.0.0',
]);Distribution — percentile analysis
Best for latencies, response sizes, durations where p90/p99 matter:
use \Sentry\Metrics\Unit;
\Sentry\traceMetrics()->distribution('page-load', 15.0, ['page' => '/home'], Unit::millisecond());Gauge — space-efficient aggregates
Use when high cardinality is a concern; no percentile support:
use \Sentry\Metrics\Unit;
\Sentry\traceMetrics()->gauge('active-connections', 42.0, ['region' => 'eu-west'], Unit::none());Flushing manually
Metrics are buffered (up to 1000 entries). Flush explicitly in CLI scripts or when emitting high volumes:
\Sentry\traceMetrics()->flush();Filtering with before_send_metric
PHP / Laravel:
use \Sentry\Metrics\Types\Metric;
\Sentry\init([
'dsn' => '___PUBLIC_DSN___',
'before_send_metric' => static function (Metric $metric): ?Metric {
if ($metric->getName() === 'removed-metric') {
return null; // drop this metric
}
return $metric;
},
]);Symfony (uses service ID, not a closure):
sentry:
options:
enable_metrics: true
before_send_metric: 'App\Sentry\BeforeSendMetricCallback'Units
use \Sentry\Metrics\Unit;
// Duration
Unit::nanosecond() Unit::microsecond() Unit::millisecond()
Unit::second() Unit::minute() Unit::hour()
Unit::day() Unit::week()
// Information
Unit::bit() Unit::byte()
Unit::kilobyte() Unit::megabyte() Unit::gigabyte()
Unit::terabyte()
// Fraction
Unit::ratio() Unit::percent()
// Dimensionless
Unit::none()Flushing
Metrics are buffered in a ring buffer (capacity: 1000 entries):
| Context | Behavior |
|---|---|
| PHP (CLI/scripts) | Call \Sentry\traceMetrics()->flush() manually |
| Laravel | Auto-flushed at end of each request or command |
| Symfony (HTTP) | Auto-flushed on kernel.terminate |
| Symfony (console) | Auto-flushed on console.terminate |
Buffer limit: When more than 1000 metrics are buffered, the oldest entries are dropped. Flush periodically in high-volume scripts.
Auto-Flush Threshold
Use metric_flush_threshold to automatically flush buffered metrics after N entries, without needing to call flush() manually:
PHP / Laravel:
\Sentry\init([
'dsn' => '___PUBLIC_DSN___',
'metric_flush_threshold' => 500, // flush automatically after 500 metrics
]);Symfony:
sentry:
options:
metric_flush_threshold: 500This is useful in CLI scripts or workers that emit metrics continuously. The threshold triggers a flush mid-process so the buffer never fills to its 1000-entry cap.
Symfony Configuration
sentry:
options:
enable_metrics: true # default: true
attach_metric_code_locations: true # attach file/line info
metric_flush_threshold: 500 # auto-flush after N metrics (optional)
before_send_metric: 'App\Sentry\BeforeSendMetricCallback'Automatically Added Attributes
Every metric receives these automatically:
| Attribute | Description |
|---|---|
sentry.environment | Environment from SDK config |
sentry.release | Release from SDK config |
sentry.sdk.name / sentry.sdk.version | SDK metadata |
server.address | Server hostname |
user.id, user.name, user.email | Active scope user (only if send_default_pii: true) |
Best Practices
- Keep attribute cardinality low — avoid user IDs, UUIDs, or timestamps as attribute values
- Use
distributionovergaugewhen you need percentile analysis (p90, p99) - Prefix metric names with your service:
"payments.charge_time"not"charge_time" - In high-throughput scripts, flush periodically to prevent the buffer from dropping old entries
- Laravel closures in
config/sentry.phpmay cause issues withconfig:cache— see Laravel closures config docs
Troubleshooting
| Issue | Solution |
|---|---|
| Metrics not appearing | Verify SDK version meets minimum; check enable_metrics is true |
| Metrics being dropped | Buffer cap is 1000 — flush periodically with \Sentry\traceMetrics()->flush() |
| No percentiles in Sentry UI | Switch from gauge to distribution — gauges do not support percentiles |
| High cardinality warning | Reduce attribute values — avoid per-user or per-request identifiers |
Old \Sentry\metrics() calls doing nothing | Migrate to \Sentry\traceMetrics() — the old API is fully deprecated |
Profiling — Sentry PHP SDK
Requires the Excimer PHP extension (Linux/macOS only — Windows not supported)
Prerequisites
Excimer extension must be installed:
# Linux (recommended)
apt-get install php-excimer
# PECL
pecl install excimer
# Enable (if needed)
phpenmod -s fpm excimerExcimer requires PHP 7.2+ and does not support Windows.
Version Requirements
| Framework | Min SDK Version |
|---|---|
| PHP (base) | sentry/sentry ≥ 3.15.0 |
| Laravel | sentry/sentry-laravel ≥ 3.3.0 |
| Symfony | sentry/sentry-symfony ≥ 4.7.0 |
Configuration
Profiling requires traces_sample_rate > 0. profiles_sample_rate is relative to traces_sample_rate.
PHP (base SDK)
\Sentry\init([
'dsn' => '___PUBLIC_DSN___',
'traces_sample_rate' => 1.0,
'profiles_sample_rate' => 1.0, // relative to traces_sample_rate
]);Laravel (config/sentry.php)
return [
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_TRACES_SAMPLE_RATE'),
'profiles_sample_rate' => env('SENTRY_PROFILES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_PROFILES_SAMPLE_RATE'),
];.env:
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_PROFILES_SAMPLE_RATE=1.0Symfony (config/packages/sentry.yaml)
sentry:
options:
traces_sample_rate: 1.0
profiles_sample_rate: 1.0How profiles_sample_rate Works
profiles_sample_rate is a fraction of already-sampled transactions, not of all requests:
Effective profiling rate = traces_sample_rate × profiles_sample_rate
Examples:
traces_sample_rate: 1.0, profiles_sample_rate: 1.0 → 100% of requests profiled
traces_sample_rate: 0.5, profiles_sample_rate: 0.5 → 25% of requests profiled
traces_sample_rate: 0.1, profiles_sample_rate: 1.0 → 10% of requests profiledReducing Latency Impact
Profiling data is sent to Sentry after generating the response, not before:
- Laravel (FastCGI): Uses terminable middleware — data sent after response is dispatched
- Symfony (FastCGI): Uses
kernel.terminateevent — same behavior - Non-FastCGI servers: Use a local Relay instance:
PHP App → local Relay (127.0.0.1) → Sentry CloudBest Practices
- Start with
profiles_sample_rate: 1.0in development to verify setup - In production, reduce
traces_sample_rate(e.g.,0.1) — profiling follows automatically - Profiling has no meaningful overhead on Linux with Excimer; Relay is only needed to avoid latency on non-FastCGI servers
- Profiles are capped at 30 seconds per transaction
Troubleshooting
| Issue | Solution |
|---|---|
| No profiles appearing | Verify Excimer is installed (`php -m \ |
profiles_sample_rate has no effect | Check SDK version meets minimum requirement |
| Windows deployment | Profiling is not supported on Windows — use Linux or macOS |
| High latency from profiling | Use FastCGI (terminable middleware) or deploy a local Relay instance |
Symfony — Sentry SDK Deep Dive
Package:sentry/sentry-symfony· Requiressentry/sentry ^4.20.0
Symfony versions:^4.4.20through^8.0
---
Installation & Setup
Requirements
- PHP
^7.2 | ^8.0 - Symfony
^4.4.20–^8.0 zend.exception_ignore_args: Offinphp.ini(required for stack trace arguments)
Install
composer require sentry/sentry-symfonyWith Symfony Flex, this automatically:
- Registers the bundle in
config/bundles.php - Creates
config/packages/sentry.yaml - Adds
SENTRY_DSNto.env
Without Flex, register manually:
// config/bundles.php
return [
Sentry\SentryBundle\SentryBundle::class => ['all' => true],
];Environment Variable Setup
###> sentry/sentry-symfony ###
SENTRY_DSN="___PUBLIC_DSN___"
###< sentry/sentry-symfony ###Verify
php bin/console sentry:test---
config/packages/sentry.yaml — Complete Schema
sentry:
dsn: "%env(SENTRY_DSN)%" # The ONLY mandatory option
register_error_listener: true # Register Symfony error event listener
register_error_handler: true # Register PHP error/exception handlers
options:
# Release & environment
environment: "%kernel.environment%" # Default: kernel env (not "production")
release: "%env(default::SENTRY_RELEASE)%"
server_name: "web-01"
# Error monitoring
sample_rate: 1.0
ignore_exceptions:
- "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException"
- "Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException"
error_types: "E_ALL & ~E_NOTICE"
before_send: "sentry.callback.before_send" # DIC service ID
# Tracing
traces_sample_rate: 0.1
traces_sampler: "sentry.callback.traces_sampler"
ignore_transactions:
- "GET /health"
before_send_transaction: "sentry.callback.before_send_transaction"
trace_propagation_targets:
- "example.com"
# Profiling
profiles_sample_rate: 0.1
# Logs
enable_logs: true
before_send_log: "sentry.callback.before_send_log"
# Metrics
enable_metrics: true
before_send_metric: "sentry.callback.before_send_metric"
# Breadcrumbs
max_breadcrumbs: 100
before_breadcrumb: "sentry.callback.before_breadcrumb"
# Context
context_lines: 5
attach_stacktrace: false
send_default_pii: false
max_request_body_size: "medium" # none|never|small|medium|always
capture_silenced_errors: false
max_value_length: 1024
in_app_exclude:
- "%kernel.cache_dir%"
- "%kernel.project_dir%/vendor"
in_app_include:
- "%kernel.project_dir%/src"
# Tags
tags:
server: "web-01"
region: "us-east-1"
# Transport
http_proxy: "proxy.example.com:8080"
http_connect_timeout: 2
http_timeout: 5
# Serialization
class_serializers:
App\User: "App\\Sentry\\Serializer\\UserSerializer"
# Symfony Messenger integration
messenger:
enabled: true
capture_soft_fails: true
isolate_breadcrumbs_by_message: false
# Symfony auto-instrumentation
tracing:
enabled: true
dbal:
enabled: true
connections:
- default
twig:
enabled: true
cache:
enabled: true
http_client:
enabled: true
console:
excluded_commands:
- "messenger:consume"---
Symfony-Specific Defaults
These differ from the plain PHP SDK:
| Option | Plain PHP Default | Symfony Default |
|---|---|---|
environment | SENTRY_ENVIRONMENT or "production" | %kernel.environment% (kernel env) |
release | SENTRY_RELEASE env | Auto-detected via PrettyVersions::getRootPackageVersion() |
in_app_exclude | [] | Auto-includes %kernel.cache_dir%, %kernel.build_dir%, %kernel.project_dir%/vendor |
---
Bundle-Level Options
| Option | Type | Default | Description |
|---|---|---|---|
dsn | `scalar\ | null` | null |
register_error_listener | boolean | true | Register Symfony ErrorListener event subscriber on kernel.exception |
register_error_handler | boolean | true | Register PHP-level error/exception handlers |
logger | `scalar\ | null` | null |
---
Callable Options — DIC Service Pattern
Symfony's YAML configuration cannot hold inline PHP closures. All callable options must reference a DIC service ID whose factory method returns the callable.
# config/packages/sentry.yaml
sentry:
options:
before_send: "sentry.callback.before_send"
traces_sampler: "sentry.callback.traces_sampler"
before_send_log: "sentry.callback.before_send_log"
# config/services.yaml
services:
sentry.callback.before_send:
class: 'App\Service\SentryCallbacks'
factory: ['@App\Service\SentryCallbacks', 'getBeforeSend']
sentry.callback.traces_sampler:
class: 'App\Service\SentryCallbacks'
factory: ['@App\Service\SentryCallbacks', 'getTracesSampler']
sentry.callback.before_send_log:
class: 'App\Service\SentryCallbacks'
factory: ['@App\Service\SentryCallbacks', 'getBeforeSendLog']// src/Service/SentryCallbacks.php
namespace App\Service;
class SentryCallbacks
{
public function getBeforeSend(): callable
{
return function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
if ($hint?->exception instanceof MyIgnoredException) {
return null;
}
return $event;
};
}
public function getTracesSampler(): callable
{
return function (\Sentry\Tracing\SamplingContext $context): float {
return $context->getParentSampled() ? 1.0 : 0.25;
};
}
public function getBeforeSendLog(): callable
{
return function (\Sentry\Logs\Log $log): ?\Sentry\Logs\Log {
if ($log->getLevel() === \Sentry\Logs\LogLevel::info()) {
return null; // drop info logs
}
return $log;
};
}
}This pattern applies to: before_send, before_send_transaction, before_send_check_in, before_send_log, before_send_metric, before_breadcrumb, traces_sampler, transport, http_client, logger, class_serializers values.
---
Auto-Instrumented Operations
| Operation | Span Op | Requires |
|---|---|---|
| HTTP main request | http.server | Always |
| HTTP sub-request | http.server (child span) | Always |
| Console command | console.command | Always |
| Outbound HTTP calls | http.client | symfony/http-client |
| Doctrine DB prepare | db.sql.prepare | doctrine/doctrine-bundle |
| Doctrine DB query | db.sql.query | doctrine/doctrine-bundle |
| Doctrine DB exec | db.sql.exec | doctrine/doctrine-bundle |
| Doctrine TX begin | db.sql.transaction.begin | doctrine/doctrine-bundle |
| Doctrine TX commit | db.sql.transaction.commit | doctrine/doctrine-bundle |
| Doctrine TX rollback | db.sql.transaction.rollback | doctrine/doctrine-bundle |
| PSR-6 cache get/put/delete/flush | cache.* | symfony/cache |
| Twig template render | view.render | symfony/twig-bundle |
Doctrine span data fields: db.system, db.user, db.name, server.address, server.port
⚠️ HTTP client tracing warning: "Using HTTP client tracing will not execute your requests concurrently." — tracing wraps each request synchronously.
Tracing Sub-Config
sentry:
tracing:
enabled: true
dbal:
enabled: true
connections: [default] # Specify which DB connections to trace
twig:
enabled: true
cache:
enabled: true
http_client:
enabled: true
console:
excluded_commands:
- "messenger:consume" # Always excluded by default
- "app:my-command"---
Structured Logs (≥ 5.4.0)
# config/packages/monolog.yaml
monolog:
handlers:
sentry_logs:
type: service
id: Sentry\SentryBundle\Monolog\LogsHandler
# config/services.yaml
services:
Sentry\SentryBundle\Monolog\LogsHandler:
arguments:
- !php/const Monolog\Logger::INFO # Minimum log level
# config/packages/sentry.yaml
sentry:
options:
enable_logs: true// Usage — inject LoggerInterface via DI
class MyService
{
public function __construct(private \Psr\Log\LoggerInterface $logger) {}
public function doSomething(int $userId): void
{
$this->logger->info('User logged in');
$this->logger->warning('User {id} failed to login.', ['id' => $userId]);
$this->logger->error('Something went wrong', [
'user_id' => $userId,
'action' => 'update_profile',
]);
}
}Auto-flush:
- HTTP requests: flushed on
kernel.terminate - Console commands: flushed on
console.terminate
Traditional Monolog Handler (Error Events, Not Structured Logs)
For sending log messages as Sentry error events (not the Logs product):
# config/services.yaml
services:
app.sentry.handler:
class: Sentry\Monolog\Handler
arguments:
- '@Sentry\State\HubInterface'
- !php/const Monolog\Logger::WARNING
Sentry\Monolog\BreadcrumbHandler:
arguments:
- '@Sentry\State\HubInterface'
- !php/const Monolog\Logger::WARNING
# config/packages/monolog.yaml
monolog:
handlers:
sentry:
type: service
id: app.sentry.handler
sentry_buffer:
type: buffer
handler: sentry
level: notice
buffer_size: 50The BufferFlushPass compiler pass auto-discovers BufferHandler instances and flushes them on kernel.terminate, console.command, console.terminate, and console.error.
---
Messenger Integration
The Messenger integration provides error capture for queue workers (not tracing spans):
sentry:
messenger:
enabled: true # Auto-enabled when MessageBusInterface exists
capture_soft_fails: true # Capture failures that will be retried
isolate_breadcrumbs_by_message: false # Separate breadcrumb buffer per message| Option | Default | Description |
|---|---|---|
enabled | true | Enable Messenger integration |
capture_soft_fails | true | Capture exceptions even if the message will be retried |
isolate_breadcrumbs_by_message | false | Push/pop scope per message — prevents breadcrumb leakage between messages |
What it captures:
- Tags events with
messenger.receiver_name,messenger.message_class,messenger.message_bus - Unwraps nested exceptions from
HandlerFailedException,DelayedMessageHandlingException,WrappedExceptionsInterface - Sets
ExceptionMechanism(isHandled: $willRetry)— retried failures are marked as handled - Flushes the client after each failure (background workers have no shutdown hook)
⚠️ No tracing spans: There is no MessengerTracingMiddleware in the current SDK — Messenger integration is error-capture only. Use captureCheckIn() manually for cron-like queue monitoring.
---
Cron Monitoring
The Symfony SDK has no dedicated scheduled-task integration. Use the PHP SDK functions directly:
use Sentry\CheckInStatus;
use Sentry\MonitorConfig;
use Sentry\MonitorSchedule;
// Two-step check-in (recommended)
$checkInId = \Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::inProgress(),
);
// ... do work ...
\Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::ok(),
checkInId: $checkInId,
);
// Wrapper approach
\Sentry\withMonitor(
slug: 'my-cron-job',
callback: fn () => $this->doWork(),
);
// With programmatic monitor config
$monitorConfig = new \Sentry\MonitorConfig(
\Sentry\MonitorSchedule::crontab('*/10 * * * *'),
checkinMargin: 5,
maxRuntime: 15,
timezone: 'Europe/Vienna',
failureIssueThreshold: 2,
recoveryThreshold: 5,
);
$checkInId = \Sentry\captureCheckIn(
slug: 'my-cron-job',
status: CheckInStatus::inProgress(),
monitorConfig: $monitorConfig,
);Filter check-ins:
sentry:
options:
before_send_check_in: "App\\Sentry\\BeforeSendCheckInCallback"---
Console Command Tracing & Monitoring
Error Capture (ConsoleListener)
Auto-enabled. Tags every console command scope with:
console.command— command nameconsole.command.exit_code— exit codeFull command— full command with arguments (extra context)
Flushes logs AND metrics on console.terminate.
Tracing (TracingConsoleListener)
sentry:
tracing:
console:
excluded_commands:
- "messenger:consume" # Always excludedCreates transactions with:
op: 'console.command'origin: 'auto.console'source: TransactionSource::task()- Status:
ok()if exit code 0,internalError()otherwise
---
Event Listeners Auto-Registered
| Listener | Events | Description |
|---|---|---|
ErrorListener | kernel.exception | Captures all uncaught exceptions |
RequestListener | kernel.request, kernel.controller | Sets IP (PII-gated); tags route name |
ConsoleCommandListener | console.command, console.terminate | Tags scope, flushes on terminate |
MessengerListener | Messenger worker events | Captures failures, optional scope isolation |
LoginListener | Symfony Security login events | Captures authenticated user context |
TracingRequestListener | kernel.request, kernel.terminate | Creates/finishes transaction for HTTP requests |
TracingSubRequestListener | kernel.request (subrequest) | Creates child spans for sub-requests |
TracingConsoleListener | console.command, console.terminate | Creates/finishes transaction for console commands |
Distributed Tracing — Automatic Header Handling
The TracingRequestListener automatically reads incoming trace headers:
// Accepts both Sentry and W3C trace context formats:
$request->headers->get('sentry-trace') // Sentry format
$request->headers->get('traceparent') // W3C format
$request->headers->get('baggage')The AbstractTraceableHttpClient decorator automatically injects outbound headers on all Symfony HTTP Client requests:
sentry-trace: <value>
baggage: <value>
traceparent: <value> # W3C trace contextRespects trace_propagation_targets — only injects headers to matching hostnames.
---
Metrics (≥ 5.8.0)
use function Sentry\traceMetrics;
traceMetrics()->count('button-click', 5, ['browser' => 'Firefox']);
traceMetrics()->distribution('page-load', 15.0, ['page' => '/home'], \Sentry\Unit::millisecond());
traceMetrics()->gauge('memory-usage', memory_get_usage(), ['worker' => 'web-01']);Config:
sentry:
options:
enable_metrics: true
before_send_metric: "App\\Sentry\\BeforeSendMetricCallback"Auto-flush: Flushed automatically on kernel.terminate and console.terminate — no manual flush needed in typical web/console contexts.
Tracing — Sentry PHP SDK
Minimum SDK:sentry/sentry^4.0 ·sentry/sentry-laravel^4.0 ·sentry/sentry-symfony^5.0
Configuration
| Option | Type | Default | Purpose |
|---|---|---|---|
traces_sample_rate | float | null | Fraction of transactions to trace (0.0–1.0); null disables tracing |
traces_sampler | callable | null | Per-transaction sampling function; takes precedence over traces_sample_rate |
profiles_sample_rate | float | null | Fraction of sampled transactions to profile (relative to traces_sample_rate) |
ignore_transactions | array | [] | Transaction names to never trace (e.g., ['/up', '/healthz']) |
before_send_transaction | callable | no-op | Mutate or drop transaction events before sending |
strict_trace_continuation | bool | false | Only continue an incoming distributed trace if the sentry-org_id baggage matches the SDK's org ID; prevents trace contamination from third-party services (>=4.21.0). Replaces deprecated strict_trace_propagation |
Code Examples
Enable tracing
// Plain PHP — uniform sample rate
\Sentry\init([
'dsn' => 'https://<key>@<org>.ingest.sentry.io/<project>',
'traces_sample_rate' => 0.2, // trace 20% of requests
]);
// Laravel — config/sentry.php
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null
? null
: (float) env('SENTRY_TRACES_SAMPLE_RATE'),# Symfony — config/packages/sentry.yaml
sentry:
options:
traces_sample_rate: 0.2Dynamic sampling with traces_sampler
// Plain PHP — closure directly in init()
\Sentry\init([
'dsn' => '...',
'traces_sampler' => function (\Sentry\Tracing\SamplingContext $context): float {
$transactionName = $context->getTransactionContext()->getName();
// Drop health checks
if (in_array($transactionName, ['/healthz', '/up', '/ping'])) {
return 0.0;
}
// Honour parent sampling decision in distributed traces
$parentSampled = $context->getParentSampled();
if ($parentSampled !== null) {
return (float) $parentSampled;
}
// 50% of HTTP requests, 10% of everything else
return str_starts_with($transactionName, 'GET ') || str_starts_with($transactionName, 'POST ')
? 0.5
: 0.1;
},
]);# Symfony — traces_sampler must be wired through the service container (closures can't be serialized)
sentry:
options:
traces_sampler: "sentry.callback.traces_sampler"
services:
sentry.callback.traces_sampler:
class: 'App\Service\Sentry'
factory: ['@App\Service\Sentry', 'getTracesSampler']// src/Service/Sentry.php
namespace App\Service;
class Sentry
{
public function getTracesSampler(): callable
{
return function (\Sentry\Tracing\SamplingContext $context): float {
return 0.5;
};
}
}Custom span API — TransactionContext and SpanContext
use Sentry\Tracing\TransactionContext;
use Sentry\Tracing\SpanContext;
// 1. Build and start a root transaction
$transactionContext = TransactionContext::make()
->setName('process-order')
->setOp('task');
$transaction = \Sentry\startTransaction($transactionContext);
// 2. Register on the hub so child spans attach to it
\Sentry\SentrySdk::getCurrentHub()->setSpan($transaction);
// 3. Add a child span
$spanContext = SpanContext::make()
->setOp('db.query')
->setDescription('SELECT * FROM orders WHERE id = ?');
$span = $transaction->startChild($spanContext);
// ... do work ...
$span->finish();
// 4. Finish transaction — submits everything to Sentry
$transaction->finish();\Sentry\trace() helper (recommended)
Removes boilerplate: starts the span, sets it as current, finishes it automatically.
$result = \Sentry\trace(
function (\Sentry\State\Scope $scope): array {
return fetchOrdersFromDatabase();
},
SpanContext::make()
->setOp('db.query')
->setDescription('fetch-orders')
);Safe manual span pattern (handles no-transaction case)
function expensiveOperation(): void
{
$parent = \Sentry\SentrySdk::getCurrentHub()->getSpan();
$span = null;
if ($parent !== null) {
$context = SpanContext::make()
->setOp('some_operation')
->setDescription('This is a description');
$span = $parent->startChild($context);
\Sentry\SentrySdk::getCurrentHub()->setSpan($span);
}
try {
// ... do work ...
} finally {
if ($span !== null) {
$span->finish();
\Sentry\SentrySdk::getCurrentHub()->setSpan($parent);
}
}
}Span data attributes
// At creation time
$spanContext = SpanContext::make()
->setOp('http.client')
->setData([
'http.request.method' => 'GET',
'http.response.status_code' => 200,
]);
// On an existing span
$span->setData(['db.system' => 'postgresql', 'db.table' => 'orders']);
// Read-modify-write
$span->setData([
'counter' => $span->getData('counter', 0) + 1,
]);Span status
$span->setStatus(\Sentry\Tracing\SpanStatus::createFromHttpStatusCode($response->getStatusCode()));
$transaction->setStatus(\Sentry\Tracing\SpanStatus::ok());
$transaction->setStatus(\Sentry\Tracing\SpanStatus::internalError());Accessing the active span/transaction
$transaction = \Sentry\SentrySdk::getCurrentHub()->getTransaction(); // ?Transaction
$span = \Sentry\SentrySdk::getCurrentHub()->getSpan(); // ?Span
if ($transaction !== null) {
$transaction->setData(['order.type' => 'subscription']);
}Mutating all spans via before_send_transaction
\Sentry\init([
'before_send_transaction' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
// Drop health-check transactions
if (in_array($event->getTransaction(), ['GET /up', 'GET /healthz'])) {
return null;
}
// Add data to every span in the transaction
foreach ($event->getSpans() as $span) {
$span->setData(['server' => 'web-01']);
}
return $event;
},
]);Auto-Instrumentation Matrix
Plain PHP — No automatic instrumentation
The plain PHP SDK provides zero automatic instrumentation. Every transaction and span must be created manually using the Custom Span API.
Laravel — Auto-instrumented operations
The Tracing\Middleware is auto-prepended and the Tracing\ServiceProvider wires all listeners automatically.
| Operation | Span Op | Enabled by default |
|---|---|---|
| HTTP request lifecycle | http.server | ✅ Always |
| Route handler dispatch | http.route | ✅ Always |
| SQL queries | db.sql.query | ✅ (tracing.sql_queries) |
| DB transactions | db.transaction | ✅ Always |
| Blade view rendering | view.render | ✅ (tracing.views) |
| Outgoing HTTP client | http.client | ✅ (tracing.http_client_requests, Laravel ≥ 8.45) |
| Cache operations | cache.* | ✅ (tracing.cache, Laravel ≥ 11.11) |
| Queue job processing | queue.process | ⚙️ tracing.queue_jobs: true |
| Queue job as transaction | queue.process | ⚙️ tracing.queue_job_transactions: true |
| Redis commands | (redis spans) | ⚙️ tracing.redis_commands: true |
| Livewire components | (livewire spans) | ⚙️ tracing.livewire: true |
| Notifications | (notification spans) | ✅ (tracing.notifications) |
| Lighthouse GraphQL | (graphql spans) | ✅ When Lighthouse installed |
| Laravel Folio routes | transaction name | ✅ When Folio installed |
| Filesystem disk operations | (file spans) | ⚙️ Opt-in via Storage\Integration::configureDisks() |
// Filesystem disk opt-in (config/filesystems.php)
'disks' => \Sentry\Laravel\Features\Storage\Integration::configureDisks([
'local' => ['driver' => 'local', 'root' => storage_path('app'), 'throw' => false],
's3' => ['driver' => 's3', /* ... */],
], enableSpans: true, enableBreadcrumbs: true),Symfony — Auto-instrumented operations
TracingRequestListener and other compiler-wired listeners activate automatically.
| Operation | Span Op | Origin |
|---|---|---|
| HTTP main request | http.server | auto.http.server |
| HTTP sub-request | http.server | auto.http.server |
| Console command | console.command | auto.console |
| Outbound HTTP calls | http.client | auto.http.client |
| Doctrine DB query | db.sql.query | auto.db |
| Doctrine DB prepare | db.sql.prepare | auto.db |
| Doctrine DB exec | db.sql.exec | auto.db |
| Doctrine TX begin/commit/rollback | db.sql.transaction.* | auto.db |
| PSR-6 cache get/put/delete/flush | cache.* | auto.cache |
| Twig template rendering | view.render | auto.view |
Distributed Tracing
Two headers carry trace context between services:
| Header | Purpose |
|---|---|
sentry-trace | Trace ID, span ID, sampling decision (Sentry native format) |
baggage | Dynamic sampling context (W3C baggage spec) |
CORS note: Both headers must be in your CORS allowlist if browser requests are involved. Proxies and API gateways may strip unknown headers.
Extracting incoming trace context
$sentryTrace = $_SERVER['HTTP_SENTRY_TRACE'] ?? '';
$baggage = $_SERVER['HTTP_BAGGAGE'] ?? '';
// continueTrace() returns a TransactionContext pre-populated with parent trace data
$ctx = \Sentry\continueTrace($sentryTrace, $baggage);
$ctx->setName('process-payment')->setOp('task');
$transaction = \Sentry\startTransaction($ctx);
\Sentry\SentrySdk::getCurrentHub()->setSpan($transaction);Injecting outgoing trace headers
// Manual header injection (e.g., Guzzle, curl, any HTTP client)
$headers = [
'sentry-trace' => \Sentry\getTraceparent(),
'baggage' => \Sentry\getBaggage(),
];
$client = new \GuzzleHttp\Client();
$response = $client->get('https://internal-api.example.com', [
'headers' => $headers,
]);Guzzle middleware (automatic header injection)
use Sentry\Tracing\GuzzleTracingMiddleware;
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push(GuzzleTracingMiddleware::trace());
$client = new \GuzzleHttp\Client(['handler' => $stack]);
$response = $client->get('https://example.com/');
// sentry-trace + baggage headers injected automatically; span created for the requestcontinueTrace() full pattern (queue consumers, workers)
// Continue a distributed trace from a queue job payload
$context = \Sentry\continueTrace(
$job->getMetadata('sentry_trace'),
$job->getMetadata('baggage')
);
$context->setOp('queue.process')->setName('App\Jobs\ProcessPayment');
$transaction = \Sentry\startTransaction($context);
\Sentry\SentrySdk::getCurrentHub()->setSpan($transaction);
try {
$job->handle();
} catch (\Throwable $e) {
$transaction->setStatus(\Sentry\Tracing\SpanStatus::internalError());
throw $e;
} finally {
$transaction->finish();
}HTML meta tag injection (frontend/backend trace stitching)
Inject these tags into your HTML <head> so the Sentry JavaScript SDK can continue the backend trace in the browser.
// Plain PHP
echo sprintf('<meta name="sentry-trace" content="%s"/>', \Sentry\getTraceparent());
echo sprintf('<meta name="baggage" content="%s"/>', \Sentry\getBaggage());{{-- Laravel Blade template --}}
{!! \Sentry\Laravel\Integration::sentryMeta() !!}
{{-- Or individually: --}}
{!! \Sentry\Laravel\Integration::sentryTracingMeta() !!}
{!! \Sentry\Laravel\Integration::sentryBaggageMeta() !!}{# Symfony Twig template — inject via a controller variable #}
{{ sentry_trace_meta | raw }}
{{ sentry_baggage_meta | raw }}Framework-automatic propagation
| Framework | Incoming (extract) | Outgoing (inject) |
|---|---|---|
| Plain PHP | Manual continueTrace() | Manual header injection |
| Laravel | Auto in Tracing\Middleware | Auto on Laravel HTTP Client (≥ 8.45) |
| Symfony | Auto in TracingRequestListener (supports both sentry-trace and traceparent) | Auto via HTTP Client decorator (injects sentry-trace, baggage, traceparent) |
Profiling
Requires the Excimer PHP extension (Wikimedia sampling profiler).
- Platform: Linux or macOS only — Windows is not supported
- PHP: 7.2+
# Install Excimer
apt-get install php-excimer # Debian/Ubuntu
pecl install excimer # PECL
phpenmod -s fpm excimer # Enable// Plain PHP — traces_sample_rate must be set; profiles_sample_rate is relative to it
\Sentry\init([
'dsn' => '...',
'traces_sample_rate' => 1.0,
'profiles_sample_rate' => 1.0, // profile 100% of sampled transactions
]);// Laravel — config/sentry.php
'traces_sample_rate' => 1.0,
'profiles_sample_rate' => 1.0,# Symfony — config/packages/sentry.yaml
sentry:
options:
traces_sample_rate: 1.0
profiles_sample_rate: 1.0profiles_sample_rate is a ratio of already-sampled transactions:
Effective profiling rate = traces_sample_rate × profiles_sample_rate
Examples:
0.5 × 0.5 = 25% of all requests profiled
1.0 × 1.0 = 100% of all requests profiled
0.1 × 1.0 = 10% of all requests profiledFramework-Specific Notes
Plain PHP
- Zero automatic instrumentation — create every transaction and span manually
- Call
\Sentry\flush()before process exit in CLI scripts to ensure buffered data is sent - For queue workers and long-running processes, start a transaction per job with
continueTrace()to preserve distributed trace context
Laravel
Tracing\Middlewareis auto-prepended byTracing\ServiceProvider— do not add it manually- Transaction start time is backdated to Laravel boot via
setBootedTimestamp()— captures full request duration including framework startup missing_routes: false(default) discards 404/unmatched route transactions; set totrueto trace themconfig:cachebreaks iftraces_sampleris a closure — use a class/callable string or only set it at runtime- Queue tracing:
tracing.queue_jobs: truecreates spans;tracing.queue_job_transactions: truecreates standalone transactions with distributed trace propagation using payload keyssentry_trace_parent_data/sentry_baggage_data - On Octane: FastCGI terminable middleware dispatches spans after response — no user-visible latency. Without FastCGI, use a local Relay proxy
Symfony
TracingRequestListeneraccepts bothsentry-trace(Sentry) andtraceparent(W3C) headers for incoming distributed traces- HTTP Client decorator injects three outgoing headers:
sentry-trace,baggage,traceparent - Console commands are auto-traced by
TracingConsoleListener— creates a root transaction if none is active, otherwise creates a child span - Messenger integration is error-capture only (via
MessengerListener) — no tracing spans for queue messages - FastCGI: spans are sent after response via
kernel.terminate. Without FastCGI, use a local Relay proxy
Span Ops Reference
op | What it tracks |
|---|---|
http.server | Incoming HTTP request |
http.client | Outgoing HTTP request |
http.route | Controller/action dispatch (Laravel) |
db.sql.query | SQL query execution |
db.sql.prepare | SQL prepare (Symfony) |
db.sql.exec | SQL exec (Symfony) |
db.sql.execute | Statement execute (Symfony) |
db.sql.transaction.begin / .commit / .rollback | DB transaction lifecycle (Symfony) |
db.transaction | DB transaction (Laravel) |
view.render | Template rendering (Blade / Twig) |
cache.get | Cache read |
cache.put | Cache write |
cache.remove | Cache delete |
cache.flush | Cache clear |
queue.publish | Job enqueue |
queue.process | Job processing |
console.command | CLI command (Symfony) |
Troubleshooting
| Issue | Solution |
|---|---|
| No transactions appearing | Verify traces_sample_rate > 0 or traces_sampler returns non-zero; confirm SDK is initialized before request handling |
| Spans not linked to transaction | Ensure spans are created inside an active transaction; call setSpan($transaction) on the hub after startTransaction() |
| Distributed traces broken | Verify sentry-trace and baggage headers pass through proxies, load balancers, and CORS middleware |
traces_sampler ignored in Laravel | Closures break config:cache; use an invokable class or only configure at runtime (not in config file) |
traces_sampler not working in Symfony | Must be a service factory — closures can't be serialized. Register as a service and reference by ID |
| Missing route transactions in Laravel | Set tracing.missing_routes: true in sentry config (default is false — 404s are discarded) |
| No profiling data | Confirm Excimer extension is installed (`php -m |
| High response latency from tracing | Use FastCGI (terminable middleware) or deploy a local Relay proxy to make uploads async |
| Queue jobs not traced | Set tracing.queue_jobs: true; for standalone transactions also set tracing.queue_job_transactions: true |
| HTML meta tags show empty values | Call getTraceparent() / getBaggage() inside an active transaction; outside a transaction these return empty strings |
Related skills
How it compares
Pick sentry-php-sdk for Sentry-native cron uptime; pick generic logging skills when you only need stdout trails without alert routing.
FAQ
Who is sentry-php-sdk for?
Developers and software engineers working with sentry-php-sdk patterns described in the skill documentation.
When should I use sentry-php-sdk?
When Full Sentry SDK setup for PHP. Use when asked to "add Sentry to PHP", "install sentry/sentry", "setup Sentry in PHP", or configure error monitoring, tracing, profiling, logging, me.
Is sentry-php-sdk safe to install?
Review the Security Audits panel on this page before installing in production.