master 9574e946bb41 cached
30 files
28.1 KB
7.6k tokens
48 symbols
1 requests
Download .txt
Repository: LKaemmerling/laravel-horizon-prometheus-exporter
Branch: master
Commit: 9574e946bb41
Files: 30
Total size: 28.1 KB

Directory structure:
gitextract_ur9a7upc/

├── .editorconfig
├── .gitattributes
├── .github/
│   └── workflows/
│       └── tests.yml
├── .gitignore
├── .styleci.yml
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│   ├── .gitkeep
│   └── config.php
├── phpunit.xml.dist
├── routes/
│   └── api.php
├── src/
│   ├── Contracts/
│   │   └── Exporter.php
│   ├── Exporter/
│   │   ├── CurrentMasterSupervisors.php
│   │   ├── CurrentProcessesPerQueue.php
│   │   ├── CurrentWorkload.php
│   │   ├── FailedJobsPerHour.php
│   │   ├── HorizonStatus.php
│   │   ├── JobsPerMinute.php
│   │   └── RecentJobs.php
│   ├── HorizonPrometheusExporterServiceProvider.php
│   ├── Http/
│   │   ├── Controller/
│   │   │   └── HorizonPrometheusExporterController.php
│   │   └── Middleware/
│   │       └── IPWhitelistingMiddleware.php
│   └── Repository/
│       └── ExporterRepository.php
└── tests/
    ├── Http/
    │   ├── Controller/
    │   │   └── HorizonPrometheusExporterControllerTest.php
    │   └── Middleware/
    │       └── IPWhitelistingMiddlewareTest.php
    ├── Repository/
    │   └── ExporterRepositoryTest.php
    ├── TestCase.php
    └── Util/
        └── NoopExporter.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
; This file is for unifying the coding style for different editors and IDEs.
; More information at https://editorconfig.org

root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.yml]
indent_size = 2


================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.gitattributes     export-ignore
/.gitignore         export-ignore
/.travis.yml        export-ignore
/phpunit.xml.dist   export-ignore
/.scrutinizer.yml   export-ignore
/tests              export-ignore
/.editorconfig      export-ignore


================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests

on:
  - push
  - pull_request

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: true
      matrix:
        os: [ubuntu-latest]
        php: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
        dependency-version: [prefer-stable]
        redis-version: [6]

    name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Cache dependencies
        uses: actions/cache@v2
        with:
          path: ~/.composer/cache/files
          key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}

      - name: Install dependencies
        run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest

      - name: Execute tests
        run: vendor/bin/phpunit


================================================
FILE: .gitignore
================================================
build
composer.lock
docs
vendor
coverage
.phpunit.result.cache


================================================
FILE: .styleci.yml
================================================
preset: laravel

disabled:
  - single_class_element_per_statement
  - self_accessor


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Contributions are **welcome** and will be fully **credited**.

Please read and understand the contribution guide before creating an issue or pull request.

## Etiquette

This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.

Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.

It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.

## Viability

When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.

## Procedure

Before filing an issue:

- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.

Before submitting a pull request:

- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

## Requirements

If the project maintainer has any additional requirements, you will find them listed here.

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.

**Happy coding**!


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) LK-Development <info@lk-development.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Laravel Horizon Prometheus Exporter

[![Latest Version on Packagist](https://img.shields.io/packagist/v/lkaemmerling/laravel-horizon-prometheus-exporter.svg?style=flat-square)](https://packagist.org/packages/lkaemmerling/laravel-horizon-prometheus-exporter)
[![Actions Status](https://github.com/lkaemmerling/laravel-horizon-prometheus-exporter/workflows/Tests/badge.svg)](https://github.com/lkaemmerling/laravel-horizon-prometheus-exporter/actions)
[![Total Downloads](https://img.shields.io/packagist/dt/lkaemmerling/laravel-horizon-prometheus-exporter.svg?style=flat-square)](https://packagist.org/packages/lkaemmerling/laravel-horizon-prometheus-exporter)


This package allows an easy way to expose the Laravel Horizon Metrics to Prometheus.

## Prom... What?

Prometheus is a scraping service which allows you to easily store and scrape information from your application, server or even from your router!
Prometheus itself does not know about your application, so you need a exporter on your app. This small package is exactly this, an exporter which allows Prometheus to understand some information
from your application. With Prometheus and a visualisation tool called `Grafana` you can build something like this beautiful Dashboard:

![Laravel Horizon Prometheus Exporter Dashboard](https://pbs.twimg.com/media/EHdSoNGX4AEpbia?format=jpg&name=4096x4096)

## Installation

You can install the package via composer:

```bash
composer require lkaemmerling/laravel-horizon-prometheus-exporter
```

## Configuration
```bash
php artisan vendor:publish --provider=LKDevelopment\\HorizonPrometheusExporter\\HorizonPrometheusExporterServiceProvider
```
You can configure this package by changing the values in `config/horizon-exporter.php`.

## Custom Metrics

You can also use this package easily to expose custom metrics. You just need to implement the `LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter` interface and then add your implementation to your `config/horizon-exporter.php` like we do it for the Horizon exporters: https://github.com/LKaemmerling/laravel-horizon-prometheus-exporter/blob/master/config/config.php#L17

## Dashboard

You can find a sample dashboard using this metrics on the [Grafana Marketplace](https://grafana.com/grafana/dashboards/11034).

### Testing

``` bash
composer test
```

### Changelog

Please see [Releases](https://github.com/LKaemmerling/laravel-horizon-prometheus-exporter/releases) for more information on what has changed recently.

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

### Security

If you discover any security related issues, please email kontakt@lukas-kaemmerling.de instead of using the issue tracker.

## Credits

- [Lukas Kämmerling](https://github.com/LKaemmerling)
- [All Contributors](../../contributors)

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.


================================================
FILE: composer.json
================================================
{
    "name": "lkaemmerling/laravel-horizon-prometheus-exporter",
    "description": "A small package to gain and export long time information from Laravel & Horizon for Prometheus.",
    "keywords": [
        "laravel-horizon-prometheus-exporter",
        "horizon",
        "laravel",
        "prometheus",
        "exporter"
    ],
    "homepage": "https://github.com/lkaemmerling/laravel-horizon-prometheus-exporter",
    "license": "MIT",
    "authors": [
        {
            "name": "Lukas Kämmerling",
            "email": "kontakt@lukas-kaemmerling.de",
            "homepage": "https://lukas-kaemmerling.de",
            "role": "Developer"
        }
    ],
    "require": {
        "php": "^7.1|^8.0",
        "promphp/prometheus_client_php": "^1.0.3|^2.0.0",
        "illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
        "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
        "illuminate/config": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
        "laravel/horizon": "^4.0|^5.0"
    },
    "require-dev": {
        "orchestra/testbench": "^v4.9|^5.3|^6.3|^7.0|^8.0|^9.0|^10.0|^11.0",
        "phpunit/phpunit": "^8.2|^9.0|^10.5|^11.5.3|^12.5.12",
        "symfony/var-dumper": "^4.3|^5.1|^7.2"
    },
    "autoload": {
        "psr-4": {
            "LKDevelopment\\HorizonPrometheusExporter\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "LKDevelopment\\HorizonPrometheusExporter\\Tests\\": "tests"
        }
    },
    "scripts": {
        "test": "vendor/bin/phpunit",
        "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
    },
    "config": {
        "sort-packages": true
    },
    "extra": {
        "laravel": {
            "providers": [
                "LKDevelopment\\HorizonPrometheusExporter\\HorizonPrometheusExporterServiceProvider"
            ]
        }
    }
}


================================================
FILE: config/.gitkeep
================================================


================================================
FILE: config/config.php
================================================
<?php
return [
    "namespace" => 'app',
    "enabled" => env('HORIZON_PROMETHEUS_EXPORTER_ENABLED', true),

    /**
     * You can change the default endpoint to something other than metrics.
     * Keep in mind that the change needs to be reflected in your Prometheus configuration as well.
     */
    "url" => 'metrics',

    /**
     * You can enable or disable or even create own exporters by simply implementing the LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter Contract.
     * If you want to disable oder enable a Exporter just comment the specific line out.
     * If you want to add your own Exporter just add the Class Name to this array
     */
    "exporters" => [
        \LKDevelopment\HorizonPrometheusExporter\Exporter\CurrentMasterSupervisors::class,
        \LKDevelopment\HorizonPrometheusExporter\Exporter\JobsPerMinute::class,
        \LKDevelopment\HorizonPrometheusExporter\Exporter\CurrentWorkload::class,
        \LKDevelopment\HorizonPrometheusExporter\Exporter\CurrentProcessesPerQueue::class,
        \LKDevelopment\HorizonPrometheusExporter\Exporter\FailedJobsPerHour::class,
        \LKDevelopment\HorizonPrometheusExporter\Exporter\HorizonStatus::class,
        \LKDevelopment\HorizonPrometheusExporter\Exporter\RecentJobs::class
    ],

    /**
     * IP Whitelisting, you may don't want to expose your metrics on the internet so you can add the IP addresses of your Prometheus Server here.
     */
    "ip_whitelist" => [
        // Keep empty to allow all IP addresses
    ],

    /**
     * You can change the Middleware which is used for the IP whitelisting.  You can add your own, like a token based authentication.
     */
    "middleware" => \LKDevelopment\HorizonPrometheusExporter\Http\Middleware\IPWhitelistingMiddleware::class,

    /**
     * Allow storage to be wiped after a render of data in metrics controller
     */
    "wipe_storage_after_render" => false,
];


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         verbose="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Spatie Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">src/</directory>
        </whitelist>
    </filter>
    <logging>
        <log type="tap" target="build/report.tap"/>
        <log type="junit" target="build/report.junit.xml"/>
        <log type="coverage-html" target="build/coverage"/>
        <log type="coverage-text" target="build/coverage.txt"/>
        <log type="coverage-clover" target="build/logs/clover.xml"/>
    </logging>
</phpunit>


================================================
FILE: routes/api.php
================================================
<?php
Route::get(config('horizon-exporter.url'), \LKDevelopment\HorizonPrometheusExporter\Http\Controller\HorizonPrometheusExporterController::class.'@metrics');



================================================
FILE: src/Contracts/Exporter.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Contracts;

use Prometheus\CollectorRegistry;

/**
 * Interface Exporter
 * Describes an metric exporter that generate the metrics
 * @package LKDevelopment\HorizonPrometheusExporter\Contracts
 */
interface Exporter
{
    /**
     * The metrics method is used to register/describe your metrics to the exporter.
     * @param CollectorRegistry $collectorRegistry
     * @void
     */
    public function metrics(CollectorRegistry $collectorRegistry): void;

    /**
     * The collect method is called from the Exporter when he collects the data.
     * You don't need to call it manually. You should perform the whole data
     * collection within this method.
     * @void
     */
    public function collect(): void;
}


================================================
FILE: src/Exporter/CurrentMasterSupervisors.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;


use Laravel\Horizon\Contracts\MasterSupervisorRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class CurrentMasterSupervisors implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
		$this->gauge = $collectorRegistry->getOrRegisterGauge(
			config('horizon-exporter.namespace'),
			'horizon_current_mastersupervisors',
			'Number of mastersupervisors'
		);
    }

    public function collect(): void
    {
		$number = count(app(MasterSupervisorRepository::class)->all());

		$this->gauge->set($number);
    }
}


================================================
FILE: src/Exporter/CurrentProcessesPerQueue.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;

use Laravel\Horizon\Contracts\WorkloadRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class CurrentProcessesPerQueue implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->gauge = $collectorRegistry->getOrRegisterGauge(
            config('horizon-exporter.namespace'),
            'horizon_current_processes',
            'Current processes of all queues',
            ['queue']
        );
    }

    public function collect(): void
    {
        $workloadRepository = app(WorkloadRepository::class);
        $workloads = collect($workloadRepository->get())->sortBy('name')->values();

        $workloads->each(function ($workload) {
            $this->gauge->set($workload['processes'], [$workload['name']]);
        });
    }
}


================================================
FILE: src/Exporter/CurrentWorkload.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;


use Laravel\Horizon\Contracts\WorkloadRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class CurrentWorkload implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->gauge = $collectorRegistry->getOrRegisterGauge(
            config('horizon-exporter.namespace'),
            'horizon_current_workload',
            'Current workload of all queues',
            ['queue']
        );
    }

    public function collect(): void
    {
        $workloadRepository = app(WorkloadRepository::class);
        $workloads = collect($workloadRepository->get())->sortBy('name')->values();

        $workloads->each(function ($workload) {
            if (isset($workload['split_queues']) && $workload['split_queues']) {
                $workload['split_queues']->each(function ($queue) {
                    $this->gauge->set($queue['length'], [$queue['name']]);
                });

                return;
            }

            $this->gauge->set($workload['length'], [$workload['name']]);
        });
    }
}


================================================
FILE: src/Exporter/FailedJobsPerHour.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;


use Laravel\Horizon\Contracts\JobRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class FailedJobsPerHour implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->gauge = $collectorRegistry->getOrRegisterGauge(
            config('horizon-exporter.namespace'),
            'horizon_failed_jobs',
            'The number of recently failed jobs'
        );
    }

    public function collect(): void
    {
        $this->gauge->set(app(JobRepository::class)->countRecentlyFailed());
    }
}


================================================
FILE: src/Exporter/HorizonStatus.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;


use Laravel\Horizon\Contracts\MasterSupervisorRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class HorizonStatus implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->gauge = $collectorRegistry->getOrRegisterGauge(
            config('horizon-exporter.namespace'),
            'horizon_status',
            'The status of Horizon, -1 = inactive, 0 = paused, 1 = running'
        );
    }

    public function collect(): void
    {
        $status = -1;
        if ($masters = app(MasterSupervisorRepository::class)->all()) {
            $status = collect($masters)->contains(function ($master) {
                return $master->status === 'paused';
            }) ? 0 : 1;
        }
        $this->gauge->set($status);
    }
}


================================================
FILE: src/Exporter/JobsPerMinute.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;


use Laravel\Horizon\Contracts\MetricsRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class JobsPerMinute implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->gauge = $collectorRegistry->getOrRegisterGauge(
            config('horizon-exporter.namespace'),
            'horizon_jobs_per_minute',
            'The number of jobs per minute'
        );
    }

    public function collect(): void
    {
        $this->gauge->set(app(MetricsRepository::class)->jobsProcessedPerMinute());
    }
}


================================================
FILE: src/Exporter/RecentJobs.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Exporter;


use Laravel\Horizon\Contracts\JobRepository;
use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Gauge;

class RecentJobs implements Exporter
{
    protected Gauge $gauge;

    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->gauge = $collectorRegistry->getOrRegisterGauge(
            config('horizon-exporter.namespace'),
            'horizon_recent_jobs',
            'The number of recent jobs'
        );
    }

    public function collect(): void
    {
        $this->gauge->set(app(JobRepository::class)->countRecent());
    }
}


================================================
FILE: src/HorizonPrometheusExporterServiceProvider.php
================================================
<?php

namespace LKDevelopment\HorizonPrometheusExporter;

use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;

class HorizonPrometheusExporterServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../config/config.php' => config_path('horizon-exporter.php'),
            ], 'config');
        }
        $this->app->booted(function () {
            $this->routes();
        });
    }

    /**
     * Register the application services.
     */
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'horizon-exporter');
    }

    protected function routes(): void
    {
        if ($this->app->routesAreCached()) {
            return;
        }

        if (config('horizon-exporter.enabled')) {
            Route::middleware(config('horizon-exporter.middleware'))->group(
                __DIR__ . '/../routes/api.php'
            );
        }
    }
}


================================================
FILE: src/Http/Controller/HorizonPrometheusExporterController.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Http\Controller;


use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use LKDevelopment\HorizonPrometheusExporter\Repository\ExporterRepository;
use Prometheus\RenderTextFormat;

class HorizonPrometheusExporterController extends Controller
{
    public function metrics()
    {
        ExporterRepository::load();
        $renderer = new RenderTextFormat();
        $result = $renderer->render(ExporterRepository::getRegistry()->getMetricFamilySamples());

        if(config('horizon-exporter.wipe_storage_after_render', false)) {
            ExporterRepository::getRegistry()->wipeStorage();
        }

        return new Response($result, Response::HTTP_OK, ["Content-Type" => RenderTextFormat::MIME_TYPE]);
    }
}


================================================
FILE: src/Http/Middleware/IPWhitelistingMiddleware.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\IpUtils;

class IPWhitelistingMiddleware
{
    public function handle(Request $request, \Closure $next)
    {
        if (!empty(config('horizon-exporter.ip_whitelist'))) {
            $clientIp = $request->ip();
            if (IpUtils::checkIp($clientIp, config('horizon-exporter.ip_whitelist'))) {
                return $next($request);
            } else {
                abort(403);
            }
        } else {
            return $next($request);
        }
    }
}


================================================
FILE: src/Repository/ExporterRepository.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Repository;


use LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter;
use Prometheus\CollectorRegistry;
use Prometheus\Storage\InMemory;

/**
 * Class ExporterRepository
 * @package LKDevelopment\HorizonPrometheusExporter\Repository
 */
class ExporterRepository
{
    /**
     * @var CollectorRegistry
     */
    protected static $registry;

    /**
     * @param array $exporters
     */
    public static function load(array $exporters = []): void
    {
        $_exporters = empty($exporters) ? config('horizon-exporter.exporters') : $exporters;

        if (self::getRegistry() === null) {
            self::setRegistry(new CollectorRegistry(new InMemory()));
        }
        foreach ($_exporters as $exporter) {
            $_exporter = new $exporter();
            /**
             * @var Exporter $_exporter
             */
            $_exporter->metrics(self::$registry);
            $_exporter->collect();
        }
    }

    /**
     * @param CollectorRegistry $collectorRegistry
     */
    public static function setRegistry(CollectorRegistry $collectorRegistry): void
    {
        self::$registry = $collectorRegistry;
    }

    /**
     * @return CollectorRegistry
     */
    public static function getRegistry()
    {
        return self::$registry;
    }
}


================================================
FILE: tests/Http/Controller/HorizonPrometheusExporterControllerTest.php
================================================
<?php

namespace LKDevelopment\HorizonPrometheusExporter\Tests\Http\Controller;

use LKDevelopment\HorizonPrometheusExporter\Http\Controller\HorizonPrometheusExporterController;
use LKDevelopment\HorizonPrometheusExporter\Repository\ExporterRepository;
use LKDevelopment\HorizonPrometheusExporter\Tests\Util\NoopExporter;
use LKDevelopment\HorizonPrometheusExporter\Tests\TestCase;

class HorizonPrometheusExporterControllerTest extends TestCase
{

    public function testMetrics()
    {
        $ctrl = new HorizonPrometheusExporterController();

        $resp = $ctrl->metrics();
        $expected = <<<EOF
# HELP app_noop_metric noop metric
# TYPE app_noop_metric counter
app_noop_metric{op="noop"} 1
EOF;
        self::assertStringContainsString($expected, $resp);

    }
}


================================================
FILE: tests/Http/Middleware/IPWhitelistingMiddlewareTest.php
================================================
<?php

namespace LKDevelopment\HorizonPrometheusExporter\Tests\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use LKDevelopment\HorizonPrometheusExporter\Http\Middleware\IPWhitelistingMiddleware;
use LKDevelopment\HorizonPrometheusExporter\Tests\TestCase;
use Symfony\Component\HttpKernel\Exception\HttpException;

class IPWhitelistingMiddlewareTest extends TestCase
{
    /**
     * @dataProvider testCases
     */
    public function testHandle($requestingIP, $expectedStatusCode)
    {
        $middleware = new IPWhitelistingMiddleware();
        $statusCode = null;
        try {
            $statusCode = $middleware->handle(new Request([], [], [], [], [], ['REMOTE_ADDR' => $requestingIP]), \Closure::fromCallable(function ($next) {
                return new Response();
            }))->getStatusCode();

        } catch (HttpException $httpException) {
            $statusCode = $httpException->getStatusCode();
        }
        self::assertEquals($expectedStatusCode, $statusCode);
    }

    public function testCases()
    {
        return [
            [
                "127.0.0.1", // Requesting IP
                Response::HTTP_OK // Expected Status Code
            ],
            [
                "127.0.0.2",
                Response::HTTP_FORBIDDEN
            ],
            [
                "10.0.0.1",
                Response::HTTP_OK
            ],
            [
                "10.0.1.1",
                Response::HTTP_FORBIDDEN
            ]
        ];
    }
}


================================================
FILE: tests/Repository/ExporterRepositoryTest.php
================================================
<?php

namespace LKDevelopment\HorizonPrometheusExporter\Tests\Repository;

use LKDevelopment\HorizonPrometheusExporter\Repository\ExporterRepository;
use LKDevelopment\HorizonPrometheusExporter\Tests\Util\NoopExporter;
use LKDevelopment\HorizonPrometheusExporter\Tests\TestCase;

class ExporterRepositoryTest extends TestCase
{

    public function testLoad()
    {
        ExporterRepository::load([NoopExporter::class]);
        self::assertNotNull(ExporterRepository::getRegistry());

        $counter = ExporterRepository::getRegistry()->getCounter("app", "noop_metric");
        self::assertNotNull($counter);
    }
}


================================================
FILE: tests/TestCase.php
================================================
<?php
namespace LKDevelopment\HorizonPrometheusExporter\Tests;

use LKDevelopment\HorizonPrometheusExporter\Tests\Util\NoopExporter;

class TestCase extends \Orchestra\Testbench\TestCase
{
    protected function getEnvironmentSetUp($app)
    {
        $app['config']->set('horizon-exporter.exporters', [NoopExporter::class]);
        $app['config']->set('horizon-exporter.ip_whitelist', ["127.0.0.1", "10.0.0.0/24"]);
    }
}


================================================
FILE: tests/Util/NoopExporter.php
================================================
<?php


namespace LKDevelopment\HorizonPrometheusExporter\Tests\Util;


use Prometheus\CollectorRegistry;
use Prometheus\Counter;

/**
 * Class NoopExporter
 * @package LKDevelopment\HorizonPrometheusExporter\Tests\Util
 */
class NoopExporter implements \LKDevelopment\HorizonPrometheusExporter\Contracts\Exporter
{
    /**
     * @var Counter
     */
    protected Counter $counter;
    /**
     * @inheritDoc
     */
    public function metrics(CollectorRegistry $collectorRegistry): void
    {
        $this->counter = $collectorRegistry->getOrRegisterCounter(
            "app",
            'noop_metric',
            'noop metric',
            ["op"]
        );
    }

    /**
     * @inheritDoc
     */
    public function collect(): void
    {
        $this->counter->inc(["op" => "noop"]);
    }
}
Download .txt
gitextract_ur9a7upc/

├── .editorconfig
├── .gitattributes
├── .github/
│   └── workflows/
│       └── tests.yml
├── .gitignore
├── .styleci.yml
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│   ├── .gitkeep
│   └── config.php
├── phpunit.xml.dist
├── routes/
│   └── api.php
├── src/
│   ├── Contracts/
│   │   └── Exporter.php
│   ├── Exporter/
│   │   ├── CurrentMasterSupervisors.php
│   │   ├── CurrentProcessesPerQueue.php
│   │   ├── CurrentWorkload.php
│   │   ├── FailedJobsPerHour.php
│   │   ├── HorizonStatus.php
│   │   ├── JobsPerMinute.php
│   │   └── RecentJobs.php
│   ├── HorizonPrometheusExporterServiceProvider.php
│   ├── Http/
│   │   ├── Controller/
│   │   │   └── HorizonPrometheusExporterController.php
│   │   └── Middleware/
│   │       └── IPWhitelistingMiddleware.php
│   └── Repository/
│       └── ExporterRepository.php
└── tests/
    ├── Http/
    │   ├── Controller/
    │   │   └── HorizonPrometheusExporterControllerTest.php
    │   └── Middleware/
    │       └── IPWhitelistingMiddlewareTest.php
    ├── Repository/
    │   └── ExporterRepositoryTest.php
    ├── TestCase.php
    └── Util/
        └── NoopExporter.php
Download .txt
SYMBOL INDEX (48 symbols across 17 files)

FILE: src/Contracts/Exporter.php
  type Exporter (line 13) | interface Exporter
    method metrics (line 20) | public function metrics(CollectorRegistry $collectorRegistry): void;
    method collect (line 28) | public function collect(): void;

FILE: src/Exporter/CurrentMasterSupervisors.php
  class CurrentMasterSupervisors (line 12) | class CurrentMasterSupervisors implements Exporter
    method metrics (line 16) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 25) | public function collect(): void

FILE: src/Exporter/CurrentProcessesPerQueue.php
  class CurrentProcessesPerQueue (line 11) | class CurrentProcessesPerQueue implements Exporter
    method metrics (line 15) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 25) | public function collect(): void

FILE: src/Exporter/CurrentWorkload.php
  class CurrentWorkload (line 12) | class CurrentWorkload implements Exporter
    method metrics (line 16) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 26) | public function collect(): void

FILE: src/Exporter/FailedJobsPerHour.php
  class FailedJobsPerHour (line 12) | class FailedJobsPerHour implements Exporter
    method metrics (line 16) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 25) | public function collect(): void

FILE: src/Exporter/HorizonStatus.php
  class HorizonStatus (line 12) | class HorizonStatus implements Exporter
    method metrics (line 16) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 25) | public function collect(): void

FILE: src/Exporter/JobsPerMinute.php
  class JobsPerMinute (line 12) | class JobsPerMinute implements Exporter
    method metrics (line 16) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 25) | public function collect(): void

FILE: src/Exporter/RecentJobs.php
  class RecentJobs (line 12) | class RecentJobs implements Exporter
    method metrics (line 16) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 25) | public function collect(): void

FILE: src/HorizonPrometheusExporterServiceProvider.php
  class HorizonPrometheusExporterServiceProvider (line 8) | class HorizonPrometheusExporterServiceProvider extends ServiceProvider
    method boot (line 13) | public function boot(): void
    method register (line 28) | public function register(): void
    method routes (line 33) | protected function routes(): void

FILE: src/Http/Controller/HorizonPrometheusExporterController.php
  class HorizonPrometheusExporterController (line 12) | class HorizonPrometheusExporterController extends Controller
    method metrics (line 14) | public function metrics()

FILE: src/Http/Middleware/IPWhitelistingMiddleware.php
  class IPWhitelistingMiddleware (line 10) | class IPWhitelistingMiddleware
    method handle (line 12) | public function handle(Request $request, \Closure $next)

FILE: src/Repository/ExporterRepository.php
  class ExporterRepository (line 15) | class ExporterRepository
    method load (line 25) | public static function load(array $exporters = []): void
    method setRegistry (line 45) | public static function setRegistry(CollectorRegistry $collectorRegistr...
    method getRegistry (line 53) | public static function getRegistry()

FILE: tests/Http/Controller/HorizonPrometheusExporterControllerTest.php
  class HorizonPrometheusExporterControllerTest (line 10) | class HorizonPrometheusExporterControllerTest extends TestCase
    method testMetrics (line 13) | public function testMetrics()

FILE: tests/Http/Middleware/IPWhitelistingMiddlewareTest.php
  class IPWhitelistingMiddlewareTest (line 11) | class IPWhitelistingMiddlewareTest extends TestCase
    method testHandle (line 16) | public function testHandle($requestingIP, $expectedStatusCode)
    method testCases (line 31) | public function testCases()

FILE: tests/Repository/ExporterRepositoryTest.php
  class ExporterRepositoryTest (line 9) | class ExporterRepositoryTest extends TestCase
    method testLoad (line 12) | public function testLoad()

FILE: tests/TestCase.php
  class TestCase (line 6) | class TestCase extends \Orchestra\Testbench\TestCase
    method getEnvironmentSetUp (line 8) | protected function getEnvironmentSetUp($app)

FILE: tests/Util/NoopExporter.php
  class NoopExporter (line 14) | class NoopExporter implements \LKDevelopment\HorizonPrometheusExporter\C...
    method metrics (line 23) | public function metrics(CollectorRegistry $collectorRegistry): void
    method collect (line 36) | public function collect(): void
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (32K chars).
[
  {
    "path": ".editorconfig",
    "chars": 338,
    "preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at https://editorconfig."
  },
  {
    "path": ".gitattributes",
    "chars": 395,
    "preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 1003,
    "preview": "name: Tests\n\non:\n  - push\n  - pull_request\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      fail-fast: "
  },
  {
    "path": ".gitignore",
    "chars": 63,
    "preview": "build\ncomposer.lock\ndocs\nvendor\ncoverage\n.phpunit.result.cache\n"
  },
  {
    "path": ".styleci.yml",
    "chars": 84,
    "preview": "preset: laravel\n\ndisabled:\n  - single_class_element_per_statement\n  - self_accessor\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2972,
    "preview": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contributi"
  },
  {
    "path": "LICENSE.md",
    "chars": 1101,
    "preview": "The MIT License (MIT)\n\nCopyright (c) LK-Development <info@lk-development.de>\n\nPermission is hereby granted, free of char"
  },
  {
    "path": "README.md",
    "chars": 2908,
    "preview": "# Laravel Horizon Prometheus Exporter\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/lkaemmerling/l"
  },
  {
    "path": "composer.json",
    "chars": 1894,
    "preview": "{\n    \"name\": \"lkaemmerling/laravel-horizon-prometheus-exporter\",\n    \"description\": \"A small package to gain and export"
  },
  {
    "path": "config/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "config/config.php",
    "chars": 1926,
    "preview": "<?php\nreturn [\n    \"namespace\" => 'app',\n    \"enabled\" => env('HORIZON_PROMETHEUS_EXPORTER_ENABLED', true),\n\n    /**\n   "
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1002,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         "
  },
  {
    "path": "routes/api.php",
    "chars": 163,
    "preview": "<?php\nRoute::get(config('horizon-exporter.url'), \\LKDevelopment\\HorizonPrometheusExporter\\Http\\Controller\\HorizonPrometh"
  },
  {
    "path": "src/Contracts/Exporter.php",
    "chars": 779,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Contracts;\n\nuse Prometheus\\CollectorRegistry;\n\n/**\n * Interfac"
  },
  {
    "path": "src/Exporter/CurrentMasterSupervisors.php",
    "chars": 739,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\n\nuse Laravel\\Horizon\\Contracts\\MasterSupervisorRepo"
  },
  {
    "path": "src/Exporter/CurrentProcessesPerQueue.php",
    "chars": 974,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\nuse Laravel\\Horizon\\Contracts\\WorkloadRepository;\nu"
  },
  {
    "path": "src/Exporter/CurrentWorkload.php",
    "chars": 1245,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\n\nuse Laravel\\Horizon\\Contracts\\WorkloadRepository;\n"
  },
  {
    "path": "src/Exporter/FailedJobsPerHour.php",
    "chars": 731,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\n\nuse Laravel\\Horizon\\Contracts\\JobRepository;\nuse L"
  },
  {
    "path": "src/Exporter/HorizonStatus.php",
    "chars": 973,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\n\nuse Laravel\\Horizon\\Contracts\\MasterSupervisorRepo"
  },
  {
    "path": "src/Exporter/JobsPerMinute.php",
    "chars": 737,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\n\nuse Laravel\\Horizon\\Contracts\\MetricsRepository;\nu"
  },
  {
    "path": "src/Exporter/RecentJobs.php",
    "chars": 707,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Exporter;\n\n\nuse Laravel\\Horizon\\Contracts\\JobRepository;\nuse L"
  },
  {
    "path": "src/HorizonPrometheusExporterServiceProvider.php",
    "chars": 1123,
    "preview": "<?php\n\nnamespace LKDevelopment\\HorizonPrometheusExporter;\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Illuminate\\Support\\"
  },
  {
    "path": "src/Http/Controller/HorizonPrometheusExporterController.php",
    "chars": 790,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Http\\Controller;\n\n\nuse Illuminate\\Http\\Response;\nuse Illuminat"
  },
  {
    "path": "src/Http/Middleware/IPWhitelistingMiddleware.php",
    "chars": 647,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Http\\Middleware;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\"
  },
  {
    "path": "src/Repository/ExporterRepository.php",
    "chars": 1347,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Repository;\n\n\nuse LKDevelopment\\HorizonPrometheusExporter\\Cont"
  },
  {
    "path": "tests/Http/Controller/HorizonPrometheusExporterControllerTest.php",
    "chars": 779,
    "preview": "<?php\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Tests\\Http\\Controller;\n\nuse LKDevelopment\\HorizonPrometheusExpo"
  },
  {
    "path": "tests/Http/Middleware/IPWhitelistingMiddlewareTest.php",
    "chars": 1525,
    "preview": "<?php\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Tests\\Http\\Middleware;\n\nuse Illuminate\\Http\\Request;\nuse Illumi"
  },
  {
    "path": "tests/Repository/ExporterRepositoryTest.php",
    "chars": 624,
    "preview": "<?php\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Tests\\Repository;\n\nuse LKDevelopment\\HorizonPrometheusExporter\\"
  },
  {
    "path": "tests/TestCase.php",
    "chars": 426,
    "preview": "<?php\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Tests;\n\nuse LKDevelopment\\HorizonPrometheusExporter\\Tests\\Util\\N"
  },
  {
    "path": "tests/Util/NoopExporter.php",
    "chars": 806,
    "preview": "<?php\n\n\nnamespace LKDevelopment\\HorizonPrometheusExporter\\Tests\\Util;\n\n\nuse Prometheus\\CollectorRegistry;\nuse Prometheus"
  }
]

About this extraction

This page contains the full source code of the LKaemmerling/laravel-horizon-prometheus-exporter GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (28.1 KB), approximately 7.6k tokens, and a symbol index with 48 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!