Docs

Telemetry

A feather-weight metrics façade that lets any MonkeysLegion project emit counters, gauges, or histograms without coupling itself to a specific backend.

Out-of-the-box adapters:

Adapter

Backend

Package needed

PrometheusMetrics

promphp/prometheus_client_php

Pulled in by default

StatsDMetrics

DogStatsD / Telegraf StatsD

php-statsd-client/php-statsd-client (suggest)

NullMetrics

No-op (prod fallback / tests)

none

All adapters implement one contract:

interface MetricsInterface
{
    public function counter(string $name, float $delta = 1, array $labels = []): void;
    public function gauge(string $name, float $value, array $labels = []): void;
    public function histogram(string $name, float $value, array $labels = []): void;
}

1 · Installation

composer require monkeyscloud/monkeyslegion-telemetry

If you plan to ship to Prometheus you’re done—its client library is already a hard dependency.

For StatsD add:

composer require php-statsd-client/php-statsd-client

2 · Bootstrapping

use MonkeysLegion\Telemetry\{PrometheusMetrics, NullMetrics};
use Prometheus\Storage\InMemory;          // any adapter from promphp

return [
    MetricsInterface::class => $_ENV['APP_METRICS'] === 'off'
        ? new NullMetrics()
        : new PrometheusMetrics(
              namespace: 'myapp',
              registry:  new \Prometheus\CollectorRegistry(new InMemory())
          ),
];

Swap the storage driver for APCu, Redis, or custom as needed.

3 · Recording Metrics

/** @var MetricsInterface $m */
$m->counter('http_requests_total');
$m->counter('http_requests_total', labels: ['method' => 'GET','route' => '/posts']);
$m->gauge('queue_size', 42);
$m->histogram('sql_query_seconds', 0.032, ['statement' => 'SELECT']);

Under the hood, each adapter maps calls to its backend’s preferred API:

  • Prometheus: counters + histograms registered once, then inc() / observe().

  • StatsD: sends |c, |g, or timing buckets over UDP.

4 · Middleware Example (request latency)

final class LatencyMiddleware implements MiddlewareInterface
{
    public function __construct(private MetricsInterface $m) {}

    public function process(ServerRequestInterface $r, RequestHandlerInterface $h): ResponseInterface
    {
        $start = microtime(true);
        $res   = $h->handle($r);

        $this->m->histogram(
            'http_latency_seconds',
            microtime(true) - $start,
            [
                'method' => $r->getMethod(),
                'route'  => $r->getAttribute('route')['name'] ?? 'unknown',
                'code'   => $res->getStatusCode(),
            ]
        );

        return $res;
    }
}

Register early in the stack to monitor every request.

5 · Exposing Prometheus Metrics

#[Route('GET', '/metrics')]
public function metrics(\Prometheus\RendererInterface $renderer): ResponseInterface
{
    return new Response(
        200,
        ['Content-Type' => \Prometheus\RenderTextFormat::MIME_TYPE],
        $renderer->render()
    );
}

Scrape /metrics from your Prometheus server at 15-30 s intervals.

6 · Testing & Local Development

  • Use NullMetrics in unit tests to avoid network I/O.

  • For quick local graphs, switch Prometheus storage to InMemory and hit /metrics with PromLens or cURL.

7 · Extending

  • Custom backends – implement MetricsInterface, register via DI.

  • Sampling – wrap an adapter with a decorator that drops X % of calls.

  • Global tags – create a subclass that merges environment labels (service, instance) into every call.

License

MIT © 2025 MonkeysCloud