tasko-products/symfony-prometheus-exporter
A Symfony bundle that exposes application and runtime metrics to Prometheus, wiring up a scrape endpoint and collectors so you can monitor requests, performance, and custom counters without hand-rolling exposition code.
The package is a Symfony bundle that wires promphp/prometheus_client_php into a DI container and exposes a /metrics scrape endpoint. Version 3.0.0 is a maintenance major (dependency bumps + code-style fixes) — the integration surface is unchanged from 2.2.0, so the steps below apply to both.
Minimal steps
composer require tasko-products/symfony-prometheus-exporter:^3.0config/bundles.php) and a storage adapter — in_memory for tests, APCu/Redis/PDO for anything that runs across multiple PHP-FPM workers.tasko_prometheus_exporter.yaml → exporter: list)./metrics and confirm the exposition text format.Where to look first
src/Exporter/ and src/Collector/ — the ExporterInterface / CollectorInterface contracts and the Abstract* base classes you'll extend.src/Controller/MetricsController.php — the scrape endpoint, its route and content-type handling.DependencyInjection/Configuration.php — the exact config keys for storage adapter and exporter registration.tests/ — the most reliable documentation of how services are expected to be wired.composer.json — read the PHP, Symfony, and promphp/prometheus_client_php constraints before upgrading.First use case
Expose two things and stop there: a counter for HTTP requests (method, route template, status) and a gauge for queue depth. That gives you a working dashboard, validates your storage adapter choice under real traffic, and forces you to confront cardinality discipline early — which is the only genuinely hard part of this package.
Exporter vs. Collector — pick deliberately
Both are registered the same way, but they differ in when work happens:
Rule of thumb: if the value is cheap to maintain incrementally, use an exporter. If it's cheap to read but expensive to maintain, use a collector.
Namespace/subsystem conventions
Treat the pair as part of your public API. app_, queue_, billing_ — stable, lowercase, no environment suffix baked in (environment belongs in a Prometheus label or a separate scrape target). Renaming a namespace is a breaking change for every dashboard and alert downstream.
Label discipline
Labels multiply series. Keep the set closed and enumerable:
/orders/{id}), never resolved paths.If you need per-tenant breakdowns, emit a separate aggregate metric and use recording rules, not a tenant_id label.
Laravel adaptation
There is no Laravel service provider or package auto-discovery — this is Symfony-first. Wire it manually in a PrometheusServiceProvider:
Route::get('/metrics', ...)) and exclude it from auth, CSRF, and session middleware.Testing
Bind the in-memory adapter in the test container, drive the code path, then render the endpoint and assert on the exposition text (# TYPE, # HELP, sample lines, label ordering). Asserting on rendered output rather than on internal counters catches namespace and label regressions that unit tests miss.
Treat 3.0.0 as potentially breaking despite the "nothing special" note. A major bump justified only as "housekeeping" is exactly where silent breaks hide:
composer.json for raised PHP/Symfony minimums and any jump in promphp/prometheus_client_php. A transitive major there can change adapter behaviour or constructor signatures.AbstractExporter / AbstractCollector — visibility, protected → private, return types, renamed helpers. If you subclass these, diff them and re-run your suite against 3.0.0 on a branch before merging.^2.2 until you've verified; the upgrade has no feature payoff to weigh against the risk.In-memory adapter is a test-only tool. Under PHP-FPM each request gets a fresh process and the registry resets, so counters restart and histograms stay near-empty. Symptom: /metrics looks fine in dev, goes flat or resets in production. Switch to APCu (single host), Redis (multi-host, preferred), or PDO (no extra infra) before shipping.
Don't share the Redis adapter's connection with your cache. Prometheus keys are long-lived and monotonically written; a FLUSHDB on cache invalidation, or a cache eviction policy, will silently destroy metrics history. Use a dedicated database index and noeviction.
Collectors run on every scrape — keep them O(1). A collector that issues a COUNT(*) over a large table, or fans out to several services, turns your monitoring into a load source and will get you rate-limited by Prometheus's scrape timeout (default 10s). Cache expensive reads for a few seconds.
Counters must be monotonic. Any code path that reconstructs an exporter or resets an in-memory registry mid-process breaks rate() and increase(), producing resets and bogus spikes. Set absolute values via gauges; increment counters.
Metric name validation is silent. Invalid characters or a namespace starting with a digit get sanitized, and the resulting metric name won't match your dashboards. Verify with promtool check metrics < <(curl -s localhost:8080/metrics) in CI.
Debugging workflow: curl -sv localhost:8080/metrics | head -50 to confirm content type (text/plain; version=0.0.4) and that your families appear at all; then check the registry — if the family is missing entirely, the exporter isn't registered (config list, service tag, or config cache). Clear the container cache after any config change; Symfony caches the exporter list aggressively.
Extension point to prefer: subclass AbstractExporter and keep constructors to a single injectable dependency, so autowiring stays trivial across both Symfony and hand-rolled Laravel bindings. Compose behaviour (a MetricsRecorder service injected into controllers and jobs) rather than adding a second base class layer — the abstracts are thin and not designed for deep hierarchies.
How can I help you explore Laravel packages today?