Custom Telemetry Design: Monitoring Business Metrics in Laravel
Learn to build a custom telemetry pipeline in Laravel. Discover how to track business-critical events and export metrics to Prometheus for Grafana visualization.
Previously in this course, we discussed Defense Against SSRF, where we focused on securing our perimeter. Now, we shift our focus inward: how do we know our business logic is actually performing as expected in production?
Standard logs tell you what happened, but telemetry tells you how your system is behaving. To scale a high-traffic SaaS platform, you need more than just error logs; you need structured, queryable metrics that allow you to track business-critical KPIs—like subscription conversion rates or API latency—in real-time.
The Philosophy of Custom Telemetry
In a distributed architecture, observability is a three-pillar system: Logs (the events), Traces (the request flow), and Metrics (the numerical aggregates). While Laravel’s default logging is excellent for debugging, it is poorly suited for long-term trend analysis.
To build an effective telemetry system, we must treat metrics as a first-class citizen. We avoid bloating the application database with telemetry data, instead pushing aggregates to a time-series database like Prometheus, which excels at high-cardinality data storage.
Designing the Telemetry Pipeline
We will design a system where our domain events trigger telemetry updates. By leveraging a dedicated TelemetryService, we decouple the business logic from the transport mechanism (e.g., StatsD, Prometheus, or even a simple Redis-backed counter).
Step 1: The Telemetry Service Interface
First, we define a contract for our metrics. This allows us to swap drivers (e.g., local development vs. production Prometheus) without touching our domain code.
PHPnamespace App\Contracts; interface TelemetryManager { public function increment(string $key, array $tags = [], int $value = 1): void; public function gauge(string $key, float $value, array $tags = []): void; }
Step 2: Implementing the Prometheus Exporter
For production, we don't write directly to Prometheus; we expose an endpoint that Prometheus scrapes. We'll use a collector pattern to store these values in memory or Redis until the scrape occurs.
PHPnamespace App\Infrastructure\Telemetry; use App\Contracts\TelemetryManager; use Prometheus\CollectorRegistry; class PrometheusTelemetry implements TelemetryManager { public function __construct(private CollectorRegistry $registry) {} public function increment(string $key, array $tags = [], int $value = 1): void { $counter = $this->registry->getOrRegisterCounter('app', $key, 'help text', array_keys($tags)); $counter->incBy($value, array_values($tags)); } public function gauge(string $key, float $value, array $tags = []): void { $gauge = $this->registry->getOrRegisterGauge('app', $key, 'help text', array_keys($tags)); $gauge->set($value, array_values($tags)); } }
Integrating into the SaaS Platform
In our running project, we need to track how often users trigger the UpgradeSubscription action. We inject our TelemetryManager into the action class to ensure we capture this metric immediately upon success.
PHPnamespace App\Domain\Billing\Actions; use App\Contracts\TelemetryManager; class UpgradeSubscription { public function __construct(private TelemetryManager $telemetry) {} public function execute(User $user, string $plan): void { #6A9955">// ... business logic ... $this->telemetry->increment('subscription_upgrades_total', [ 'plan' => $plan, 'region' => $user->region ]); } }
Hands-on Exercise
- Create a
TelemetryServiceProviderthat binds theTelemetryManagerinterface to ourPrometheusTelemetryimplementation. - Define a route
/metricsthat uses a controller to return theCollectorRegistrydata in the Prometheus text format. - Configure your local Prometheus instance to scrape your Laravel site's
/metricsendpoint. - Challenge: Create a custom recorder to track the latency of your external payment gateway API, similar to the strategies discussed in Laravel Pulse Custom Recorders for API Monitoring.
Common Pitfalls
- High Cardinality: Never put unique IDs (like
user_id) in your metric tags. Prometheus will crash as it creates a new time series for every single user. Stick to low-cardinality dimensions likeplan_type,region, orstatus_code. - Blocking I/O: Do not make network requests to a telemetry server during the request lifecycle. Always use an asynchronous buffer or an in-memory collector that Prometheus scrapes later.
- Over-instrumentation: Don't measure everything. Focus on "Golden Signals": latency, traffic, errors, and saturation. If you can't build an alert for a metric, you probably don't need to track it.
Recap
We've moved from simple logging to a structured telemetry system. By decoupling the TelemetryManager interface, we can instrument our domain logic without worrying about the underlying monitoring stack. This approach provides the visibility needed to scale our SaaS platform confidently.
Up next, we will dive into Distributed Tracing to visualize exactly how a single request traverses our services.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.