├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── LpeController.php ├── LpeManager.php ├── LpeServiceProvider.php ├── Middleware │ ├── AbstractResponseTimeMiddleware.php │ ├── LaravelResponseTimeMiddleware.php │ └── LumenResponseTimeMiddleware.php ├── config │ └── config.php └── laravel_routes.php └── tests └── Middleware └── LaravelMiddlewareTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea 3 | 4 | #composer 5 | composer.lock 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '7.0' 4 | install: 5 | composer install --no-interaction 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Till Backhaus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-prometheus-exporter 2 | 3 | Archived, use https://github.com/traum-ferienwohnungen/laravel-prometheus-exporter instead 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tback/laravel-prometheus-exporter", 3 | "description": "A prometheus exporter for the Laravel web framework", 4 | "keywords": ["laravel", "prometheus"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Till Backhaus", 10 | "email": "till@backha.us" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "Tback\\PrometheusExporter\\": "src/" 16 | } 17 | }, 18 | "repositories": [ 19 | { 20 | "type" : "vcs", 21 | "url": "https://github.com/traum-ferienwohnungen/prometheus_client_php" 22 | } 23 | ], 24 | "require": { 25 | "jimdo/prometheus_client_php": "dev-store-initialized", 26 | "illuminate/support": "^5.6" 27 | }, 28 | "require-dev": { 29 | "illuminate/http": "^5.6", 30 | "phpunit/phpunit": "^6.0", 31 | "orchestra/testbench": "~3.0" 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "Tback\\PrometheusExporter\\LpeServiceProvider" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/LpeController.php: -------------------------------------------------------------------------------- 1 | lpeManager = $lpeManager; 26 | } 27 | 28 | /** 29 | * metric 30 | * 31 | * Expose metrics for prometheus 32 | * 33 | * @return Response 34 | */ 35 | public function metrics() 36 | { 37 | $renderer = new RenderTextFormat(); 38 | 39 | return response($renderer->render($this->lpeManager->getMetricFamilySamples())) 40 | ->header('Content-Type', $renderer::MIME_TYPE); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/LpeManager.php: -------------------------------------------------------------------------------- 1 | 13 | * @package Tback\PrometheusExporter 14 | */ 15 | class LpeManager 16 | { 17 | /** 18 | * @var CollectorRegistry 19 | */ 20 | protected $registry; 21 | 22 | /** 23 | * @var Counter 24 | */ 25 | protected $requestCounter; 26 | 27 | /** 28 | * @var Counter 29 | */ 30 | protected $requestDurationCounter; 31 | 32 | /** 33 | * LpeManager constructor. 34 | * 35 | * @param CollectorRegistry $registry 36 | */ 37 | public function __construct(CollectorRegistry $registry, Router $router) 38 | { 39 | $this->registry = $registry; 40 | $routeNames = []; 41 | foreach($router->getRoutes() as $route){ 42 | $routeNames[] = $route->getName(); 43 | } 44 | $this->initRouteMetrics($routeNames); 45 | } 46 | 47 | public function initRouteMetrics(array $routes) 48 | { 49 | static $run = false; 50 | if (!$run){ 51 | $run = true; 52 | 53 | $namespace = config('prometheus_exporter.namespace_http_server'); 54 | $labelNames = $this->getRequestCounterLabelNames(); 55 | 56 | $name = 'requests_total'; 57 | $help = 'number of http requests'; 58 | $this->requestCounter = $this->registry->registerCounter($namespace, $name, $help, $labelNames); 59 | 60 | $name = 'requests_latency_milliseconds'; 61 | $help = 'duration of http_requests'; 62 | $this->requestDurationCounter = $this->registry->registerCounter($namespace, $name, $help, $labelNames); 63 | 64 | foreach ($routes as $route) { 65 | foreach (config('prometheus_exporter.init_metrics_for_http_methods') as $method) { 66 | foreach (config('prometheus_exporter.init_metrics_for_http_status_codes') as $statusCode) { 67 | $labelValues = [(string)$route, (string)$method, (string) $statusCode]; 68 | $this->requestCounter->incBy(0, $labelValues); 69 | $this->requestDurationCounter->incBy(0.0, $labelValues); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | protected function getRequestCounterLabelNames() 77 | { 78 | return [ 79 | 'route', 'method', 'status_code', 80 | ]; 81 | } 82 | 83 | public function countRequest($route, $method, $statusCode, $duration_milliseconds) 84 | { 85 | $labelValues = [(string)$route, (string)$method, (string) $statusCode]; 86 | $this->requestCounter->inc($labelValues); 87 | $this->requestDurationCounter->incBy($duration_milliseconds, $labelValues); 88 | } 89 | 90 | /** 91 | * Get metric family samples 92 | * 93 | * @return \Prometheus\MetricFamilySamples[] 94 | */ 95 | public function getMetricFamilySamples() 96 | { 97 | return $this->registry->getMetricFamilySamples(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/LpeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app instanceof LaravelApplication) { 26 | $this->publishes([$source => config_path('prometheus_exporter.php')]); 27 | $this->mergeConfigFrom($source, 'prometheus_exporter'); 28 | 29 | $kernel->pushMiddleware(LaravelResponseTimeMiddleware::class); 30 | if(! $this->app->routesAreCached()){ 31 | $this->registerMetricsRoute(); 32 | } 33 | } elseif (class_exists('Laravel\Lumen\Application', false)) { 34 | $this->app->configure('prometheus_exporter'); 35 | $this->mergeConfigFrom($source, 'prometheus_exporter'); 36 | } 37 | } 38 | 39 | /** 40 | * Register the service provider. 41 | */ 42 | public function register() 43 | { 44 | $this->mergeConfigFrom( 45 | __DIR__.'/config/config.php', 46 | 'prometheus_exporter' 47 | ); 48 | 49 | 50 | switch (config('prometheus_exporter.adapter')) { 51 | case 'apc': 52 | $this->app->bind('Prometheus\Storage\Adapter', 'Prometheus\Storage\APC'); 53 | break; 54 | case 'redis': 55 | $this->app->bind('Prometheus\Storage\Adapter', function($app){ 56 | return new \Prometheus\Storage\Redis(config('prometheus_exporter.redis')); 57 | }); 58 | break; 59 | default: 60 | throw new \ErrorException('"prometheus_exporter.adapter" must be either apc or redis'); 61 | } 62 | } 63 | 64 | public function registerMetricsRoute() 65 | { 66 | $this->loadRoutesFrom(__DIR__ . '/laravel_routes.php'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Middleware/AbstractResponseTimeMiddleware.php: -------------------------------------------------------------------------------- 1 | lpeManager = $lpeManager; 32 | } 33 | 34 | /** 35 | * Handle an incoming request. 36 | * 37 | * @param \Illuminate\Http\Request $request 38 | * @param \Closure $next 39 | * @return mixed 40 | */ 41 | public function handle(Request $request, Closure $next) 42 | { 43 | $start = microtime(true); 44 | $this->request = $request; 45 | 46 | /** @var \Illuminate\Http\Response $response */ 47 | $response = $next($request); 48 | $duration = microtime(true) - $start; 49 | $duration_milliseconds = $duration * 1000.0; 50 | 51 | $route_name = $this->getRouteName(); 52 | $method = $request->getMethod(); 53 | $status = $response->getStatusCode(); 54 | 55 | $this->lpeManager->countRequest($route_name, $method, $status, $duration_milliseconds); 56 | 57 | return $response; 58 | } 59 | 60 | /** 61 | * Get route name 62 | * 63 | * @return string 64 | */ 65 | abstract protected function getRouteName(); 66 | } 67 | -------------------------------------------------------------------------------- /src/Middleware/LaravelResponseTimeMiddleware.php: -------------------------------------------------------------------------------- 1 | request->route()[1]; 18 | return array_key_exists('as', $route_info) ? $route_info['as']: 'unnamed'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | env('PROMETHEUS_ADAPTER', 'apc'), 8 | 9 | 'namespace' => 'app', 10 | 11 | 'namespace_http_server' => 'http_server', 12 | 13 | /* 14 | * HTTP Methods. List aquired from 15 | * https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods 16 | * Enable the ones that are in use by your application. 17 | */ 18 | 'init_metrics_for_http_methods' => [ 19 | 'GET', 20 | 'HEAD', 21 | 'POST', 22 | //'PUT', 23 | //'DELETE', 24 | //'CONNECT', 25 | //'OPTIONS', 26 | //'TRACE', 27 | //'PATCH' 28 | ], 29 | 30 | /* 31 | * HTTP Status codes. List aquired from 32 | * https://en.wikipedia.org/wiki/List_of_HTTP_status_codes 33 | * Enable the ones that are in use by your application. 34 | */ 35 | 'init_metrics_for_http_status_codes' => [ 36 | //100, //Continue 37 | //101, //Switching Protocols 38 | //102, //Processing 39 | //103, //Early Hints 40 | 200, //OK 41 | 201, //Created 42 | //202, //Accepted 43 | //203, //Non-Authoritative Information 44 | //204, //No Content 45 | //205, //Reset Content 46 | //206, //Partial Content 47 | //207, //Multi-Status 48 | //208, //Already Reported 49 | //226, //IM Used 50 | //300, //MultipleChoices 51 | //301, //MovedPermanently 52 | //302, //Found 53 | //303, //Not Modified 54 | //305, //Use Proxy 55 | //307, //Temporary Redirect 56 | //308, //Permanent Redirect 57 | //400, //Bad Request 58 | 401, //Unauthorized 59 | //402, //Payment Required 60 | //403, //Forbidden 61 | 404, //Not Found 62 | //405, //Method not allowed 63 | //406, //Not Acceptable 64 | //407, //Proxy Authentication required 65 | //409, //Conflict 66 | //410, //Gone 67 | //411, //Length required 68 | //412, //Precondition failed 69 | //413, //Payload too large 70 | //414, //URI too long 71 | //415, //Unsupported media type 72 | //416, //Range not satisfiable 73 | //417, //Expectation failed 74 | //418, //I'm a teapot 75 | //421, //Misdirected request 76 | //422, //Unprocessable Entity 77 | //423, //Locked 78 | //424, //Failed dependency 79 | //426, //Upgrade required 80 | //428, //Precondition required 81 | //429, //Too many requests 82 | //431, //Request header fields too large 83 | //451, //Unavailable for legal reasons 84 | 500, //Internal Server Error 85 | //501, //Not Implemented 86 | //502, //Bad Gateway 87 | //503, //Service Unavailable 88 | //504, //Gateway Timeout 89 | //505, //HTTP Version Not Supported 90 | //506, //Variant also negotiates 91 | //507, //Insufficient Storage 92 | //508, //Loop detected 93 | //510, //Not Extended 94 | //511, //Network Authentication Required 95 | ], 96 | 97 | 'redis' => [ 98 | 'host' => '127.0.0.1', 99 | 'port' => 6379, 100 | 'timeout' => 0.1, // in seconds 101 | 'read_timeout' => 10, // in seconds 102 | 'persistent_connections' => false, 103 | ], 104 | ]; 105 | -------------------------------------------------------------------------------- /src/laravel_routes.php: -------------------------------------------------------------------------------- 1 | name('metrics'); 4 | -------------------------------------------------------------------------------- /tests/Middleware/LaravelMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | handle(new \Illuminate\Http\Request(), function(){ 13 | return new \Illuminate\Http\Response(); 14 | }); 15 | } 16 | } 17 | --------------------------------------------------------------------------------