From c16e88584f43b62c586ede7dcbc055d747ae2e7a Mon Sep 17 00:00:00 2001 From: Timotej Date: Fri, 30 Jan 2026 19:23:35 +0100 Subject: [PATCH 1/4] metrics --- README.md | 22 +++++++ app/Http/Controllers/MetricsController.php | 30 ++++++++++ app/Http/Middleware/PrometheusMetrics.php | 52 ++++++++++++++++ app/Providers/AppServiceProvider.php | 12 ++-- bootstrap/app.php | 3 +- composer.json | 3 +- composer.lock | 70 +++++++++++++++++++++- routes/api.php | 5 +- 8 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 app/Http/Controllers/MetricsController.php create mode 100644 app/Http/Middleware/PrometheusMetrics.php diff --git a/README.md b/README.md index abc821b..68ea674 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,25 @@ FLUSH PRIVILEGES; kubectl exec -n cloudshopt -it deploy/product-service -c app -- sh # php artisan migrate ``` + +## GraphQL (Lighthouse) + +This service exposes a GraphQL API (powered by Lighthouse) for querying product data. + +### Endpoint +- `POST /api/products/graphql` + +### Schema +```graphql +type Product { + id: ID! + name: String! + description: String + price: Float! +} + +type Query { + products: [Product!]! @all + product(id: ID! @whereKey): Product @find +} +``` diff --git a/app/Http/Controllers/MetricsController.php b/app/Http/Controllers/MetricsController.php new file mode 100644 index 0000000..01e2280 --- /dev/null +++ b/app/Http/Controllers/MetricsController.php @@ -0,0 +1,30 @@ +header('Authorization', ''); +// if (!preg_match('/^Bearer\s+(.+)$/i', $auth, $m) || $m[1] !== $token) { +// return response()->json(['message' => 'Unauthorized'], 401); +// } +// } + + $registry = app(CollectorRegistry::class); + + $renderer = new RenderTextFormat(); + $result = $renderer->render($registry->getMetricFamilySamples()); + + return response($result, 200, [ + 'Content-Type' => RenderTextFormat::MIME_TYPE, + 'Cache-Control' => 'no-store', + ]); + } +} diff --git a/app/Http/Middleware/PrometheusMetrics.php b/app/Http/Middleware/PrometheusMetrics.php new file mode 100644 index 0000000..aaa873a --- /dev/null +++ b/app/Http/Middleware/PrometheusMetrics.php @@ -0,0 +1,52 @@ +route(); + $routeLabel = $route?->getName() + ?? $route?->uri() + ?? 'unknown'; + + $method = $request->method(); + $status = (string) $response->getStatusCode(); + $service = config('app.name', 'service'); + + $registry = app(CollectorRegistry::class); + + // Counter: request count + $counter = $registry->getOrRegisterCounter( + 'cloudshopt', + 'http_requests_total', + 'Total HTTP requests', + ['service', 'method', 'route', 'status'] + ); + $counter->inc([$service, $method, $routeLabel, $status]); + + // Histogram: request latency + $hist = $registry->getOrRegisterHistogram( + 'cloudshopt', + 'http_request_duration_seconds', + 'HTTP request duration in seconds', + ['service', 'method', 'route', 'status'], + // buckets (sekunde) + [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5] + ); + $hist->observe($duration, [$service, $method, $routeLabel, $status]); + + return $response; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..47a25ae 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,20 +3,18 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; +use Prometheus\CollectorRegistry; +use Prometheus\Storage\InMemory; class AppServiceProvider extends ServiceProvider { - /** - * Register any application services. - */ public function register(): void { - // + $this->app->singleton(CollectorRegistry::class, function () { + return new CollectorRegistry(new InMemory()); + }); } - /** - * Bootstrap any application services. - */ public function boot(): void { // diff --git a/bootstrap/app.php b/bootstrap/app.php index c3928c5..7195741 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,6 @@ withMiddleware(function (Middleware $middleware): void { - // + $middleware->append(PrometheusMetrics::class); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/composer.json b/composer.json index 3a76b75..913fea2 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "php": "^8.2", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", - "nuwave/lighthouse": "^6.64" + "nuwave/lighthouse": "^6.64", + "promphp/prometheus_client_php": "^2.14" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 6acca38..101c281 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e3775f1e2cf4bb464938ff50045151a7", + "content-hash": "dad58639eadd37d12f080ca96b02b568", "packages": [ { "name": "brick/math", @@ -2831,6 +2831,74 @@ ], "time": "2025-12-27T19:41:33+00:00" }, + { + "name": "promphp/prometheus_client_php", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/PromPHP/prometheus_client_php.git", + "reference": "a283aea8269287dc35313a0055480d950c59ac1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PromPHP/prometheus_client_php/zipball/a283aea8269287dc35313a0055480d950c59ac1f", + "reference": "a283aea8269287dc35313a0055480d950c59ac1f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4|^8.0" + }, + "replace": { + "endclothing/prometheus_client_php": "*", + "jimdo/prometheus_client_php": "*", + "lkaemmerling/prometheus_client_php": "*" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.3|^7.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5.4", + "phpstan/phpstan-phpunit": "^1.1.0", + "phpstan/phpstan-strict-rules": "^1.1.0", + "phpunit/phpunit": "^9.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/polyfill-apcu": "^1.6" + }, + "suggest": { + "ext-apc": "Required if using APCu.", + "ext-pdo": "Required if using PDO.", + "ext-redis": "Required if using Redis.", + "promphp/prometheus_push_gateway_php": "An easy client for using Prometheus PushGateway.", + "symfony/polyfill-apcu": "Required if you use APCu on PHP8.0+" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Prometheus\\": "src/Prometheus/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Lukas Kämmerling", + "email": "kontakt@lukas-kaemmerling.de" + } + ], + "description": "Prometheus instrumentation library for PHP applications.", + "support": { + "issues": "https://github.com/PromPHP/prometheus_client_php/issues", + "source": "https://github.com/PromPHP/prometheus_client_php/tree/v2.14.1" + }, + "time": "2025-04-14T07:59:43+00:00" + }, { "name": "psr/clock", "version": "1.0.0", diff --git a/routes/api.php b/routes/api.php index f3a5515..9ec8d59 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@ Date: Fri, 30 Jan 2026 19:36:32 +0100 Subject: [PATCH 2/4] metrics --- routes/api.php | 2 -- routes/web.php | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/api.php b/routes/api.php index 9ec8d59..20351a7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -55,5 +55,3 @@ Route::get('/items', [ProductController::class, 'index']); Route::get('/items/{id}', [ProductController::class, 'show']); - -Route::get('/metrics', MetricsController::class); \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 3ab76a1..2ef5b31 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,3 +3,5 @@ use Illuminate\Support\Facades\Route; Route::get('/healthz', fn () => response('ok', 200)); + +Route::get('/metrics', MetricsController::class); \ No newline at end of file From 9723ccddc5f5c11d5aa6f95651a44e4680b65a88 Mon Sep 17 00:00:00 2001 From: Timotej Date: Fri, 30 Jan 2026 19:37:42 +0100 Subject: [PATCH 3/4] metrics --- routes/api.php | 1 - routes/web.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index 20351a7..bbc246a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,5 @@ response('ok', 200)); From e64d6a776102ab46cd9db522825aeddc4fcb6499 Mon Sep 17 00:00:00 2001 From: Timotej Date: Fri, 30 Jan 2026 20:19:45 +0100 Subject: [PATCH 4/4] metrics --- app/Providers/AppServiceProvider.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 47a25ae..515ce62 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,14 +4,25 @@ use Illuminate\Support\ServiceProvider; use Prometheus\CollectorRegistry; -use Prometheus\Storage\InMemory; +use Prometheus\Storage\Redis; class AppServiceProvider extends ServiceProvider { public function register(): void { + Redis::setDefaultOptions([ + 'host' => env('REDIS_HOST'), + 'port' => (int) env('REDIS_PORT', 6379), + 'password' => env('REDIS_PASSWORD') ?: null, + 'timeout' => 0.1, + 'read_timeout' => 10, + 'persistent_connections' => true, + 'prefix' => config('app.name', 'service') . '_' . gethostname(), + ]); + + $this->app->singleton(CollectorRegistry::class, function () { - return new CollectorRegistry(new InMemory()); + return new CollectorRegistry(new Redis()); }); }