artprima / prometheus-metrics-bundle

Symfony 5/6 Prometheus Metrics Bundle
MIT License
127 stars 29 forks source link

Handle Error 5xx and custom Controller ? #60

Closed cstern-eenov closed 2 years ago

cstern-eenov commented 2 years ago

Hello, Thank you very much for this plugin which is very practical.

It is mentioned in the documentation that we can use a custom controller, but how to specify the call to a particular controller? MyMetricsCollector

Second point, it is mentioned in the documentation that we can get 5.X.X errors, however how to activate them? I have enabled enable_console_metrics: true but nothing show in the url..

Thanks for your help

denisvmedia commented 2 years ago
  1. I think, the doc clearly states this:
app_metrics:
    path: /mypath/mymetrics
    controller: Artprima\PrometheusMetricsBundle\Controller\MetricsController::prometheus

Just change it to your controller path and name. Check the bundled controller for the implementation details.

  1. 5xx errors should be caught by default (unless you disabled them with disable_default_metrics: false). Note, some errors may happen early and never reach the event. Such errors should be caught in your http server (which is external to the application and will know the results even if the app dies early or crashes in an unrecoverable manner).
cstern-eenov commented 2 years ago

Hello @denisvmedia thanks for you reply.

  1. Okay, thanks you I added the prometheus properties and renderer, as write in the bundled controller. Is it correct ? My Controller seems to be called.

  2. For the moment, the /metrics/prometheus only display myapp_http_requests_total What i need to add to get all the 4xx and 5xx call ?

image

prometheus_metrics.yaml

artprima_prometheus_metrics:
    # namespace is used to prefix the prometheus metrics
    namespace: myapp

    # metrics backend type
    type: in_memory # possible values: in_memory, apcu, redis

    # ignoring some routes in metrics
    ignored_routes: [some_route_name, another_route_name]

    # used to disable default application metrics
    disable_default_metrics: true

    # used to enable console metrics
    enable_console_metrics: true

metrics.yaml

app_metrics:
    path: /metrics/prometheus
    controller: App\Metrics\MyMetricsCollector::prometheus

App\Metrics\MyMetricsCollector

<?php

declare(strict_types=1);

namespace App\Metrics;

use Artprima\PrometheusMetricsBundle\Metrics\Renderer;
use Artprima\PrometheusMetricsBundle\Metrics\RequestMetricsCollectorInterface;
use Artprima\PrometheusMetricsBundle\Metrics\TerminateMetricsCollectorInterface;
use Artprima\PrometheusMetricsBundle\Metrics\ConsoleCommandMetricsCollectorInterface;
use Artprima\PrometheusMetricsBundle\Metrics\ConsoleTerminateMetricsCollectorInterface;
use Artprima\PrometheusMetricsBundle\Metrics\ConsoleErrorMetricsCollectorInterface;
use Prometheus\CollectorRegistry;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\TerminateEvent;

/**
 * Class MyMetricsCollector.
 */
class MyMetricsCollector implements
    RequestMetricsCollectorInterface,
    TerminateMetricsCollectorInterface,
    ConsoleCommandMetricsCollectorInterface,
    ConsoleTerminateMetricsCollectorInterface,
    ConsoleErrorMetricsCollectorInterface

{

    private Renderer $renderer;

    public function __construct(Renderer $metricsRenderer)
    {
        $this->renderer = $metricsRenderer;
    }

    public function prometheus()
    {
        return $this->renderer->renderResponse();
    }
    /**
     * @var string
     */
    private $namespace;

    /**
     * @var CollectorRegistry
     */
    private $collectionRegistry;

    public function init(string $namespace, CollectorRegistry $collectionRegistry): void
    {
        $this->namespace = $namespace;
        $this->collectionRegistry = $collectionRegistry;
    }

    private function incRequestsTotal(?string $method = null, ?string $route = null): void
    {
        $counter = $this->collectionRegistry->getOrRegisterCounter(
            $this->namespace,
            'http_requests_total',
            'total request count',
            ['action']
        );

        $counter->inc(['all']);

        if (null !== $method && null !== $route) {
            $counter->inc([sprintf('%s-%s', $method, $route)]);
        }
    }

    private function incResponsesTotal(?string $method = null, ?string $route = null): void
    {
        $counter = $this->collectionRegistry->getOrRegisterCounter(
            $this->namespace,
            'http_responses_total',
            'total response count',
            ['action']
        );
        $counter->inc(['all']);

        if (null !== $method && null !== $route) {
            $counter->inc([sprintf('%s-%s', $method, $route)]);
        }
    }

    private function incErrorTotal(?string $method = null, ?string $route = null): void
    {
        $counter = $this->collectionRegistry->getOrRegisterCounter(
            $this->namespace,
            'http_error_5xx_total',
            'total response count',
            ['action']
        );
        $counter->inc(['all']);

        if (null !== $method && null !== $route) {
            $counter->inc([sprintf('%s-%s', $method, $route)]);
        }
    }

    // called on the `kernel.request` event
    public function collectRequest(RequestEvent $event): void
    {
        $request = $event->getRequest();
        $requestMethod = $request->getMethod();
        $requestRoute = $request->attributes->get('_route');

        // do not track "OPTIONS" requests
        if ('OPTIONS' === $requestMethod) {
            return;
        }

        $this->incRequestsTotal($requestMethod, $requestRoute);
    }

    // called on the `kernel.terminate` event
    public function collectResponse(TerminateEvent $event): void
    {
        $response = $event->getResponse();
        $request = $event->getRequest();

        $requestMethod = $request->getMethod();
        $requestRoute = $request->attributes->get('_route');

        $this->incResponsesTotal($requestMethod, $requestRoute);
    }

    // called on the `kernel.terminate` event
    public function collectConsoleCommand(ConsoleCommandEvent $event): void
    {
        $response = $event->getResponse();
        $request = $event->getRequest();

        $requestMethod = $request->getMethod();
        $requestRoute = $request->attributes->get('_route');

        $this->incErrorTotal($requestMethod, $requestRoute);
    }

    // called on the `kernel.terminate` event
    public function collectConsoleTerminate(ConsoleTerminateEvent $event): void
    {
        $response = $event->getResponse();
        $request = $event->getRequest();

        $requestMethod = $request->getMethod();
        $requestRoute = $request->attributes->get('_route');

        $this->incErrorTotal($requestMethod, $requestRoute);
    }

    // called on the `kernel.terminate` event
    public function collectConsoleError(ConsoleErrorEvent $event): void
    {
        $response = $event->getResponse();
        $request = $event->getRequest();

        $requestMethod = $request->getMethod();
        $requestRoute = $request->attributes->get('_route');

        $this->incErrorTotal($requestMethod, $requestRoute);
    }
}

Thanks for your help

denisvmedia commented 2 years ago

I'm not sure you need to copy all the default stuff in your own code, but to have 4xx and 5xx rendered you first need to trigger them (make the actual errors to happen first).