inspector-apm/inspector-symfony
Symfony bundle for Inspector APM that adds real-time error tracking and performance monitoring to your application. Capture exceptions, slow requests, SQL queries, and background jobs, then inspect and debug issues from a single dashboard.
Install the bundle and point it at an Inspector project:
composer require inspector-apm/inspector-symfony
Register the bundle in config/bundles.php:
return [
// ...
Inspector\Symfony\Bundle\InspectorBundle::class => ['all' => true],
];
Store the ingestion key from the Inspector dashboard in .env (never commit it):
INSPECTOR_INGESTION_KEY=your_key_here
Minimal config in config/packages/inspector.yaml:
inspector:
ingestion_key: '%env(INSPECTOR_INGESTION_KEY)%'
enabled: true
Then load any route in the app and confirm the request appears as a transaction in the dashboard. That first round-trip is the fastest way to validate the key, the bundle registration, and outbound HTTP connectivity all at once.
Where to look first:
inspector-apm/inspector-php docs for the Inspector core API (segments, exceptions, transport) — the Symfony bundle is mostly wiring.Inspector\Inspector service in the container; inject it wherever you need custom instrumentation.Let auto-instrumentation do the default work. Out of the box the bundle hooks into the kernel request/response cycle, Doctrine DBAL (query timings), console commands, and unhandled exceptions. Most teams get value on day one without writing any code — requests, slow queries, and errors show up as transactions with a timeline.
Use custom segments for business logic. Inject Inspector\Inspector and wrap the parts you actually care about:
use Inspector\Inspector;
final class ReportBuilder
{
public function __construct(private Inspector $inspector) {}
public function build(int $accountId): Report
{
$segment = $this->inspector->startSegment('report.build', "account:$accountId");
try {
$data = $this->aggregate($accountId);
$segment->end();
return new Report($data);
} catch (\Throwable $e) {
$this->inspector->reportException($e);
$segment->end();
throw $e;
}
}
}
Segments are what turn a generic "the request took 900ms" into "800ms was the aggregation query." Naming them consistently (domain.action) makes the dashboard usable.
Report exceptions you catch and swallow. Anything that reaches the kernel exception listener is recorded automatically; anything you catch in a try/catch and downgrade to a log line is invisible unless you call reportException(). This is the single biggest gap people hit.
Use async transport with Messenger. If outbound HTTP on the request path is a latency concern, configure the bundle to send through Symfony Messenger and run a worker. The trade-off is real: you now need a worker running, and events are lost if the queue backs up or the worker dies. For low-traffic apps, synchronous transport is simpler and fine.
Keep noise down with ignore lists. Health checks, /metrics, asset routes, and cron commands pollute the dashboard. Configure the ignore path/command lists in inspector.yaml rather than filtering later.
Environment-gate the whole thing. Set enabled: false in config/packages/test/inspector.yaml so test suites don't try to ship telemetry. In dev, either disable it or point it at a separate project — mixing local noise into production dashboards makes the data useless.
# config/packages/test/inspector.yaml
inspector:
enabled: false
Treat it as one of several observers. The bundle coexists with Symfony's profiler and Monolog. Use Inspector for cross-request trends and production incidents; keep the profiler for local deep dives.
inspector-php level — the bundle is mostly an event subscriber plus DI config.->end() on a segment produces a broken timeline and can hold references longer than intended. Always pair startSegment with end() in a finally block.dev and re-enable when you actually need to inspect something.composer.json against your Symfony version before upgrading the framework.enabled true in this env, (2) is the ingestion key present and non-empty, (3) can the host reach the ingestion endpoint (curl it), (4) does a synchronous transport deliver, (5) is the worker running for async. That sequence resolves most "nothing shows up" reports.Inspector service. Rather than fighting the bundle's config, wrap or decorate Inspector\Inspector in your container if you need to standardize segment naming, scrub fields, or add tags across the app.How can I help you explore Laravel packages today?