Utopia Telemetry

A lightweight, adapter-based PHP telemetry library for OpenTelemetry metrics, built by the Appwrite team.

Library
Composer
v0.4.7
2stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
49/100Fair
Development Activity72
Maintenance72
Community8
Maturity44
Momentum0

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
74/100Good
Architecture80
Code Quality82
Innovation78
Learning Curve55

Utopia Telemetry is a small, dependency-free-at-its-core PHP library for recording application metrics — counters, up/down counters, histograms, gauges, and observable gauges — behind a single Adapter interface. Ship metrics through the built-in OpenTelemetry adapter (which exports over OTLP, with an optional Swoole coroutine transport for high-throughput async apps), swap in the in-memory Test adapter to assert on recorded values in unit tests, or use the None adapter to disable telemetry entirely without touching call sites.

The library is maintained by the Appwrite team as part of the wider Utopia PHP ecosystem, but it has no hard dependency on any other Utopia package and can be dropped into any PHP 8+ codebase. Instruments are created eagerly in application code but registered with the underlying meter lazily, on first write — a design choice made specifically so an instrument that never records never emits a zero-datapoint metric, which newer versions of Prometheus’s OTLP receiver reject outright (taking the rest of the batch down with it).

What You Get

  • Five instrument types - Counter, UpDownCounter, Histogram, Gauge, and ObservableGauge, each as a small typed abstract class with add()/record()/observe().
  • OpenTelemetry adapter - exports metrics over OTLP/HTTP using the official open-telemetry/sdk and open-telemetry/exporter-otlp packages, with cumulative temporality and a configurable service-namespace/name/instance resource.
  • Test adapter - an in-memory adapter that exposes recorded counters/gauges/histograms as public arrays, purpose-built for asserting on emitted metrics in PHPUnit tests.
  • None adapter - a complete no-op implementation for disabling telemetry in an environment (e.g. local dev) without conditionally guarding every call site.
  • Swoole coroutine transport - an optional Transport\Swoole implementation of OpenTelemetry’s TransportInterface with pooled, keep-alive HTTP connections for exporting metrics from long-running Swoole servers.
  • Lazy instrument registration - instruments only register with the real meter on first write, avoiding zero-datapoint metrics that recent Prometheus OTLP receivers reject.

Common Use Cases

  • Instrumenting a PHP API service - track request counts, active-connection gauges, and request-duration histograms and ship them to an OTLP collector feeding Prometheus or Grafana.
  • Testing metric emission - swap in the Test adapter inside PHPUnit to assert a code path recorded the counters/histograms it should, without a real OTLP endpoint.
  • Toggling telemetry per environment - inject the None adapter in local/dev environments and the OpenTelemetry adapter in production, with zero conditional logic in business code.
  • High-throughput Swoole servers - use the pooled Swoole transport so metric exports don’t block request-handling coroutines under sustained load.

Under The Hood

Architecture The library centers on a single Adapter interface (src/Telemetry/Adapter.php) with five factory methods (createCounter, createHistogram, createGauge, createUpDownCounter, createObservableGauge) plus collect(). Each of the three shipped adapters — Adapter/OpenTelemetry.php, Adapter/Test.php, Adapter/None.php — implements it identically in shape but differently in effect: the OpenTelemetry adapter wires an ExportingReader and MeterProvider from the official SDK and returns anonymous classes that lazily call through to the real MeterInterface on first write (cached in a $meterStorage map keyed by instrument type and name so repeated create*() calls for the same name return the same instance); the Test adapter mirrors that laziness but registers into public arrays instead of exporting, so assertions can inspect exactly what was recorded and when; the None adapter returns inert anonymous classes whose methods do nothing. This shared shape means application code written against Adapter never needs to know or branch on which concrete adapter is active.

Tech Stack PHP 8.0+, distributed via Composer with PSR-4 autoloading (Utopia\Telemetry\ -> src/Telemetry). Runtime dependencies are open-telemetry/sdk and open-telemetry/exporter-otlp (both pinned to the 1.x line) for the real export path, nyholm/psr7 for PSR-7 message objects, and symfony/http-client for the default HTTP transport. An optional Transport\Swoole class (src/Telemetry/Adapter/OpenTelemetry/Transport/Swoole.php) implements OpenTelemetry’s generic TransportInterface directly against Swoole\Coroutine\Http\Client, using a Swoole\Coroutine\Channel as a connection pool and a Swoole\Atomic for shutdown coordination — it’s require-dev only (swoole/ide-helper) and suggested, not required, so consumers who don’t run Swoole pay no cost.

Code Quality Tests live under tests/Telemetry and run via PHPUnit (phpunit.xml), including LazyInstrumentTest.php, which specifically guards the deprecated static ::lazy() shims against regressing from the lazy-registration behavior, plus Adapter/OpenTelemetryTest.php and dedicated Swoole transport tests (TransportTest.php, TransportIntegrationTest.php) backed by a MockOtlpServer.php test double. Static analysis runs at PHPStan’s strictest level: max (phpstan.neon, scoped to src), and a rector.php config runs automated refactors while explicitly skipping a rule that would strip the @var T generic annotation createMeter() needs for PHPStan to type-check correctly — a sign the strict typing is treated as a hard constraint, not decoration. Every source file declares strict_types=1. This repository itself is a read-only mirror (its only GitHub Actions workflow redirects issues/PRs to the upstream utopia-php/monorepo), so day-to-day CI runs there rather than here.

What Makes It Unique The library’s one real design bet — deferring instrument registration with the underlying OpenTelemetry meter until the first write, rather than at create*() time — is called out directly in the adapter’s own doc comment as a deliberate fix for a specific failure mode: an instrument that’s created but never written to still gets exported with zero data points, and Prometheus 3.13+ rejects the entire OTLP batch over a single such metric, silently dropping every healthy metric riding along with it. Structuring the library so applications can freely create instruments up front (e.g. at boot, before knowing whether they’ll ever fire) without risking a production metrics outage is a narrow but concrete piece of hard-won operational knowledge baked into the API design, not just a generic wrapper around the OpenTelemetry SDK.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search