rasuvaeff / yii3-metrics-prometheus
Prometheus metrics backend for rasuvaeff/yii3-metrics (Yii3)
Package info
github.com/rasuvaeff/yii3-metrics-prometheus
pkg:composer/rasuvaeff/yii3-metrics-prometheus
Requires
- php: 8.3 - 8.5
- promphp/prometheus_client_php: ^2.15
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/log: ^1.1 || ^2.0 || ^3.0
- rasuvaeff/yii3-metrics: ^2.1
Requires (Dev)
- ergebnis/composer-normalize: ^2.51
- friendsofphp/php-cs-fixer: ^3.95
- infection/infection: ^0.33 || ^0.34 || ^0.35
- maglnet/composer-require-checker: ^4.17
- nyholm/psr7: ^1.8
- predis/predis: ^2.2 || ^3.0
- rasuvaeff/rector-named-literals: ^1.0
- rasuvaeff/understudy: ^0.4
- rasuvaeff/understudy-testo: ^0.1
- rector/rector: ^2.4
- roave/backward-compatibility-check: ^8.0
- testo/bridge-infection: ^0.1.6
- testo/testo: ^0.10.25
- vimeo/psalm: ^6.16
Suggests
- ext-apcu: APCu shared storage — required for multi-worker php-fpm
- ext-pdo: PDO shared storage adapter (MySQL/PostgreSQL/SQLite) — alternative to APCu/Redis
- ext-redis: Redis shared storage adapter (alternative to APCu)
- predis/predis: Pure-PHP Redis client for the predis storage adapter (no ext-redis needed)
Provides
None
Conflicts
None
Replaces
None
README
Prometheus backend for rasuvaeff/yii3-metrics.
It records the core MetricRegistry metrics into promphp/prometheus_client_php
and exposes them at a /metrics endpoint.
Using an AI coding assistant? llms.txt has a compact API reference you can pass as context.
Requirements
- PHP 8.3+
rasuvaeff/yii3-metrics^1.0promphp/prometheus_client_php^2.0- A shared storage backend for php-fpm:
ext-apcu,ext-redis,predis/predis, or a PDO DSN
Installation
composer require rasuvaeff/yii3-metrics-prometheus
Installing this package binds the swappable MeterProviderInterface — the core
MetricRegistry now records into Prometheus. Do not also bind the provider
yourself (a deliberate yiisoft/config Duplicate key).
Usage
Multiprocess storage (required for php-fpm)
promphp's default InMemory storage is per process, so under php-fpm
/metrics would only reflect the worker that served the scrape. Use a shared
adapter via env:
| Runtime | PROMETHEUS_STORAGE |
Needs |
|---|---|---|
| php-fpm (multiple workers) | apcng (recommended), apcu, redis, predis, or pdo |
ext-apcu / ext-redis / predis/predis / a PDO DSN |
| RoadRunner / Swoole (one long-running process) | in_memory |
— |
| CLI / tests | in_memory |
— |
apcng is promphp's newer APCu adapter with much cheaper scrape-time collection
on large registries — prefer it over apcu for new deployments. predis uses
the pure-PHP client (no ext-redis).
use Rasuvaeff\Yii3MetricsPrometheus\StorageFactory; $adapter = (new StorageFactory())->create('apcng'); // pure-PHP Redis client (no ext-redis): $adapter = (new StorageFactory())->create('predis', ['host' => 'redis', 'port' => 6379]); // or, without apcu/redis (MySQL, PostgreSQL, SQLite): $adapter = (new StorageFactory())->create('pdo', [ 'dsn' => 'mysql:host=db;dbname=app', 'username' => 'app', 'password' => getenv('DB_PASSWORD'), ]);
An unknown adapter name throws (no silent fallback), and selecting in_memory
under php-fpm is reported — a scrape that silently shows one worker's
counters is worse than a visible note. The report goes to the application's PSR-3
logger (the package DI passes it in), or to error_log() when there is none.
It is deliberately not a PHP warning.
yiisoft/error-handlerconverts warnings intoErrorException, so atrigger_error()here would make the shipped default configuration (in_memory+ php-fpm) throw out of the DI factory and 500 every request that touches metrics.
The /metrics endpoint
MetricsEndpoint (PSR-15) renders the registry as Prometheus text exposition
(text/plain; version=0.0.4). Route your /metrics path to it — it needs a
PSR-17 ResponseFactoryInterface. The handler itself has no access control:
it serves the full exposition to any request that reaches it, so restrict the
path at your edge (see Security). A sample whose labels no longer
match its metric (possible with a Redis storage) is rendered as a comment
instead of failing the whole scrape.
use Rasuvaeff\Yii3MetricsPrometheus\MetricsEndpoint; $endpoint = new MetricsEndpoint($collectorRegistry, $responseFactory);
Safe labels (cardinality)
SanitizingRouteResolver collapses id-like path segments (/users/123 →
/users/:id, UUIDs → :uuid) for the RED middleware's route label.
The core's shipped default is ConstantRouteResolver — the route label is the
constant (unset) until the application picks a resolver, because a raw path is
attacker-controlled. This resolver is one of the opt-ins, and it narrows the
id case only: arbitrary scanner paths and non-UUID tokens stay unique, so it
neither bounds cardinality nor hides secrets in a path. Prefer the core's
CurrentRouteResolver (matched router pattern) when you have a router, or wrap
this one in BoundedRouteResolver to cap the series count.
Rebind it in your app config (an app-layer override):
// config/common/di.php use Rasuvaeff\Yii3Metrics\RouteResolverInterface; use Rasuvaeff\Yii3MetricsPrometheus\SanitizingRouteResolver; return [ RouteResolverInterface::class => SanitizingRouteResolver::class, ];
Keep label values low-cardinality — one time series is created per unique label combination.
Recording with a label name that was not declared at registration throws
InvalidArgumentException (a typo'd label would otherwise silently record under
an empty value); so does a declared label missing from the recording's set —
silently filing it as "" merged every such observation into one empty-valued
series, the same corruption the undeclared case is rejected for.
Histogram defaults
A histogram registered without explicit buckets gets the core's
Buckets::PROMETHEUS_DEFAULTS (11 bounds, 0.005 s … 10 s) — the same list on
every backend, so bucket assertions in in-memory tests describe the production
schema. promphp's own default layout (14 bounds) is never used. Metric names
and bucket layouts are validated by the same core Validation the in-memory
and no-op meters apply.
Numeric input contract
Recorded amounts must be finite: inc/observe/add and gauge inc/dec
throw on NAN/±INF; gauge set() allows ±INF but throws on NAN
(promphp has no renderable NaN token). Mirrors the core contract, so the same
code cannot behave differently per backend — and nothing non-finite reaches the
shared, durable storage adapters, where a single unguarded NAN would poison
the series total until the storage is flushed.
Metric namespace
Set PROMETHEUS_NAMESPACE (params namespace) to prefix every metric:
checkout_http_server_requests_total. Empty by default.
Classes
| Class | Purpose |
|---|---|
PrometheusMeterProvider |
core MeterProviderInterface over a promphp CollectorRegistry |
PrometheusMeter / PrometheusCounter / PrometheusGauge / PrometheusUpDownCounter / PrometheusHistogram |
adapters |
PrometheusRenderer |
render a registry as text exposition |
MetricsEndpoint |
PSR-15 /metrics handler |
StorageFactory |
build the storage adapter (in_memory/apcu/apcng/redis/predis/pdo) |
SanitizingRouteResolver |
opt-in low-cardinality route label |
Security
- Label values are arbitrary — keep ids/tokens out of them; use
SanitizingRouteResolverfor the route label. php_infoand other promphp default metrics are disabled (registerDefaultMetrics: false).- Protect
/metrics: the exposition reveals routes, traffic, and error rates. Restrict it to your scrape network (firewall / ingress allowlist) or put basic auth in front — do not expose it publicly.
Examples
Runnable, server-independent scripts in examples/. See
examples/README.md.
Dependency analysers
This leaf package is selected by the root application through config-plugin and may legitimately have no class reference in an autoloaded source directory. Keep the direct dependency: the application, not a core package, selects the backend or bridge. Scope the Composer Dependency Analyser exception to this package:
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration; use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType; return (new Configuration())->ignoreErrorsOnPackage( 'rasuvaeff/yii3-metrics-prometheus', [ErrorType::UNUSED_DEPENDENCY], );
composer-require-checker detects used but undeclared symbols, not unused
packages, so this config-only dependency needs no require-checker suppression.
Development
The core is resolved via a path repository, so run Docker with the monorepo
root mounted as /repo:
docker run --rm -v /path/to/monorepo:/repo -w /repo/yii3-metrics-prometheus \ composer:2 composer build
See AGENTS.md.
License
BSD-3-Clause. See LICENSE.md.