Back to Blog
Lesson 33 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20263 min read

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.

LaravelTelemetryMonitoringPrometheusGrafanaArchitecturephpbackend

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.

PHP
namespace 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.

PHP
namespace 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.

PHP
namespace 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

  1. Create a TelemetryServiceProvider that binds the TelemetryManager interface to our PrometheusTelemetry implementation.
  2. Define a route /metrics that uses a controller to return the CollectorRegistry data in the Prometheus text format.
  3. Configure your local Prometheus instance to scrape your Laravel site's /metrics endpoint.
  4. 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 like plan_type, region, or status_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.

Similar Posts