Skip to content

How to expose Prometheus metrics from your Laravel application

Posted on:February 17, 2025 at 03:22 PM

TLDR

In this poste, we discussed how to expose Prometheus metrics from a Laravel application. We explored the benefits of using Prometheus over building a custom solution, including separation of concerns and leveraging an established monitoring stack. We also covered the installation and configuration of the Spatie Laravel Prometheus package, and provided examples of implementing simple and complex gauges with multiple labels.

Introduction

While working on a SaaS platform, I needed to track various business metrics and have dashboards that are specific to my needs as the business owner. Faced with this requirement, I had two options: either write the full implementation for everything (both backend and frontend) or use an already established stack for monitoring.

In this post, I will discuss how I chose to expose Prometheus metrics from my Laravel application, the benefits of using Prometheus over building a custom solution, and the steps involved in setting it up.

Why I Chose Prometheus Over Implementing It from Scratch

First of all, let’s talk about the separation of concerns. Writing such dashboards and metrics on the frontend would require a large implementation. It’s not just about exposing the metrics, but also about keeping historical data (such as active users, active sessions, and business-specific metrics). This requires having database tables that store this historical data.

In this case, we would have two solutions:

This is where Prometheus comes in. Prometheus is a powerful time-series database designed specifically for monitoring and metrics collection. It provides a standard way to collect and store metrics, making it easier to build dashboards and track historical data.

Additionally, I had already started working on the monitoring and observability part for the infrastructure, so it made sense to include these metrics in Prometheus. This way, I could leverage the existing monitoring stack and avoid duplicating efforts by building a custom solution from scratch.

Spatie Laravel Prometheus

Spatie Laravel Prometheus is a Laravel package developed by the well-known Spatie.be. This package provides a simple interface for exporting Prometheus metrics from your Laravel backend.

Instalation

  1. Require the package via Composer
composer require spatie/laravel-prometheus
  1. Publish the configuration file:
php artisan prometheus:install

Configuration

// config/prometheus.php
'enabled' => env('ENABLE_PROMETHEUS', false),

This setting allows you to enable or disable the package.

// config/prometheus.php
'allowed_ips' => env('PROMETHEUS_ALLOWED_IPS', ['127.0.0.1']),

These are the IP addresses that are allowed to access the metrics endpoint.

// config/prometheus.php
'urls' => [
    'default' => 'metrics',
],

This exposes the metrics over the /metrics endpoint, which is the standard for Prometheus metrics.

Writing Gauges

Writing the First Gauge

Writing the gauges (or the metrics) is done inside the PrometheusServiceProvider’s register function. Here is a simple gauge implementation:

use Prometheus\Prometheus;
use Illuminate\Support\Facades\Cache;
use App\Models\User;

class PrometheusServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Add a gauge for total users
        Prometheus::addGauge('pandacors_total_users')->
          (function () {
            return Cache::remember(
              'metric_total_users',
              now()->addMinutes(5),
              fn () => User::count();
            );
        });
    }
}

In this example, we are adding a gauge named pandacors_total_users that counts the total number of users in the application. The gauge is a type of metric that represents a single numerical value that can arbitrarily go up and down.

Explanation

Gauges with Multiple Labels

To implement a gauge with multiple labels, we need to provide a list of labels for the gauge. For each value, we need to provide the value and the corresponding label values. This allows us to categorize the metrics based on different labels.

Simple Example


Prometheus::addGauge('user_count')
  ->label('status')
  ->value(function() {
    return [
      [User::where('status', 'active')->count(), ['active']],
      [User::where('status', 'inactive')->count(), ['inactive']],
    ];
  });

In this example:

Complex Example

Here is a more complex example where we count subscriptions based on their status (trial, active, disabled):

use Prometheus\Prometheus;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Models\Enterprise;

class PrometheusServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Add a gauge for subscription count with status label
        Prometheus::addGauge('pandacors_subscription')
            ->label('status')
            ->value(function() {
                return Cache::remember(
                  'metric_total_subscriptions',
                  now()->addMinutes(5),
                  function () {
                    $subQuery = DB::table('enterprises', 'e')
                        ->leftJoin('subscriptions as s', 's.subscriber_id', '=', 'e.id')
                        ->where('s.subscriber_type', Enterprise::class)
                        ->select([
                            DB::raw("
                                CASE
                                    WHEN s.trial_ends_at IS NOT NULL AND s.trial_ends_at > NOW() THEN 'trial'
                                    WHEN s.ends_at > NOW() THEN 'active'
                                    ELSE 'disabled'
                                END AS status
                            ")
                        ])
                        ->groupBy(['status', 'e.id']);

                    return DB::query()
                        ->fromSub($subQuery, 'sub')
                        ->selectRaw('COUNT(*) AS total, status')
                        ->groupBy('status')
                        ->get()
                        ->map(fn($row) => [$row->total, ['status' => $row->status]])
                        ->toArray();
                });
            });
    }
}

In this example:

By using labels, we can categorize and filter metrics based on different dimensions, making it easier to analyze and visualize the data in Prometheus.

Conclusion

Exposing Prometheus metrics from your Laravel application allows you to leverage a powerful and widely-used monitoring stack. By using the Spatie Laravel Prometheus package, you can easily export metrics and track important business and application metrics. This approach not only simplifies the implementation but also ensures that you can efficiently monitor and analyze your application’s performance and health. By integrating Prometheus with your existing monitoring infrastructure, you can gain valuable insights and make data-driven decisions to improve your application’s reliability and scalability.