jmonitor/jmonitor-bundle
JMonitor Bundle offers a packaged integration for bringing JMonitor monitoring into PHP applications. It provides bundle-style configuration to help connect your project to JMonitor services, centralize setup, and simplify adding monitoring to existing Composer-based projects.
First, the reality check. jmonitor/jmonitor-bundle is a Symfony bundle (not Laravel), with 1 GitHub star and 0 dependents. There is effectively no community documentation beyond the README and the source itself. Budget 20–30 minutes to read src/ before you write any integration code — treat the README as a starting point, not a contract.
What it actually collects (confirmed by v2.2.0). The bundle ships per-component collectors — at minimum flex, scheduler, and messenger — plus whatever request/kernel instrumentation the README describes. Each collector is independent, which matters for both scoping and failure modes (see Gotchas).
Minimal install (Symfony app):
composer require --dev jmonitor/jmonitor-bundle
config/bundles.php (Symfony Flex may do this for you):
Jmonitor\Bundle\JmonitorBundle::class => ['dev' => true, 'test' => true],
config/routes.yaml or a Resources/config/routing.xml inside the vendor directory:
jmonitor:
resource: '@JmonitorBundle/Resources/config/routing.xml'
prefix: /_jmonitor
bin/console cache:clear && bin/console debug:container | grep -i jmonitor.Where to look first: open vendor/jmonitor/jmonitor-bundle/ and read, in this order — README.md, the bundle class (*Bundle.php, to see what it registers and which compiler passes run), DependencyInjection/Configuration.php (this file is the authoritative list of config keys, not the README), then the collector directory — look for per-component classes matching Flex*, Scheduler*, Messenger* so you know exactly which subsystems it touches.
First use case: pick the subsystem you actually care about. If you're debugging slow requests, instrument a single slow controller and hit it a few times. If you're debugging background work, inspect the scheduler collector — as of v2.2.0 it reports each task's arguments in a dedicated field, so you can confirm a #[AsPeriodicTask(..., arguments: ['--apply'])] is being invoked with the flags you expect. You want visible per-request duration, memory, or per-task arguments before you roll it anywhere near staging. If you can't get signal from one trivial route or one trivial task, stop there; this package is not worth debugging blind.
Environment-gated registration. Only register the bundle in dev/test first (as above), then promote to staging. Never ['all' => true] on a package with this little scrutiny — it hooks kernel events and runs on every request.
Instrument via kernel events, not manual calls. The idiomatic pattern for PHP monitoring bundles is a kernel.request / kernel.response / kernel.exception subscriber. Let the bundle do the wrapping and keep your controllers clean. If the bundle exposes a service, inject it rather than calling static helpers — that keeps it mockable in tests.
Scope the collectors. Because collection is per-component (flex / scheduler / messenger), you can usually turn off the components you don't care about. If you only want scheduler visibility, disable flex and messenger collection in config — this halves your overhead and, more importantly, removes two components from the "what could silently break" list.
Exclude noise aggressively. Health checks, static assets, favicon requests, and admin polling will drown your actual data. Whatever config key governs exclusions (look in Configuration.php — commonly something like excluded_paths or ignore_routes), populate it before you look at the numbers:
# config/packages/dev/jmonitor.yaml (verify key names against Configuration.php)
jmonitor:
enabled: true
# excluded_paths: ['/health', '/_profiler', '^/assets']
Secure the endpoint. If it exposes a dashboard, put it behind Symfony's firewall rather than relying on path obscurity:
# config/packages/security.yaml
security:
access_control:
- { path: ^/_jmonitor, roles: ROLE_ADMIN }
Poll-and-alert pattern. For day-to-day usefulness, don't expect the bundle to page you. Write a small console command that reads the bundle's collected data on a cron and emits to your alerting channel:
#[AsCommand(name: 'app:monitor:check')]
class MonitorCheckCommand extends Command
{
protected function execute(InputInterface $in, OutputInterface $out): int
{
// read metrics via the bundle's service, compare to thresholds,
// exit non-zero so cron/monitoring picks it up
}
}
A non-zero exit code is the cheapest possible integration with any external monitor — you don't need an API client. Include scheduler/messenger state in the check: a task that stopped appearing in the scheduler collector is itself an alert-worthy signal.
Retention and storage. Whatever the backing store (file, Redis, in-memory array), configure a retention/cap. In-memory collectors are useless across requests; file collectors grow unbounded. Check Configuration.php for a TTL or max-entries key and set it explicitly.
Laravel mapping (for the Laravel developer). Read this bundle as a reference implementation, not a dependency:
register()/boot()config/packages/*.yaml + Configuration.php ≈ config/jmonitor.php + config validationIf you want this functionality in Laravel, the honest answer is: don't port it — install Laravel Telescope (dev) and Pulse (production metrics), with Horizon for queue visibility. They solve the same problem with orders of magnitude more maintenance.
config/jmonitor.php. composer require in a Laravel app will install it and do literally nothing. Any bridge you write is your own fork to maintain."jmonitor/jmonitor-bundle": "2.2.0"), commit the lock file, and read the changelog between releases. v2.2.0 is bug-fix-only with no BC breaks, which is reassuring — but the release cadence on a package with no adoption means you should still read every changelog before bumping.arguments out into its own top-level field and changed how the command name is extracted from arguments. If you parse the collector payload (a cron script, a log shipper, a dashboard), that's a schema change hiding inside a patch release. Treat the payload as versioned: assert on field presence, not position, and re-check parsing after every bump.messenger disappears from your dashboard, nothing pages you; the bundle logged it once and moved on. Grep the log for collector errors, and make your poll-and-alert command assert that every expected component is present in the output.Configuration.php doesn't. Config key names in monitoring bundles drift constantly. bin/console config:dump-reference jmonitor (or debug:config jmonitor) gives you the truth; both commands are your fastest debugging tools when the YAML is silently ignored.Configuration.php has no ->children() tree, everything passes through silently and nothing happens. Verify the bundle is actually reacting to config by changing something observable (disable it, confirm the endpoint 404s).bin/console cache:clear in dev; on deploy, cache:clear + cache:warmup. A stale compiled container is the #1 cause of "my config isn't taking effect." Note that the boot-time component disable persists until worker restart — in a long-running worker (Messenger consumer, RoadRunner) clearing the cache mid-flight won't re-enable it.wrk against a warm route with the bundle on and off, and profile one busy queue worker. If it's more than low-single-digit percent, restrict it to staging.--password=… style flags can now surface in the dashboard), sometimes SQL or config. Never expose /jmonitor on a public interface without auth — this is the most likely way this package causes you a real incident.config/bundles.php for the current env? (2) debug:container — does the service exist? (3) debug:router — is the route registered? (4) are you being excluded by your own excluded_paths regex? (5) did a component disable itself at boot after an exception — restart the worker? (6) check var/log/dev.log for swallowed collector exceptions — v2.2.0 logs collection-time errors but does not surface them in the UI, so a component can look absent-and-fine.Stopwatch-based timing subscriber (or a few lines of Messenger middleware) you write yourself in 40 lines.How can I help you explore Laravel packages today?