Kontakt aufnehmen
Backend 3 Min. Lesezeit · 10/2022

Sentry Performance, PHP und GraphQL

Wie Sie Sentry Performance für das Performance-Monitoring einer PHP-Anwendung einsetzen – einschließlich der Performance einer GraphQL-API.

Sentry ist ein hervorragendes Tool für das Monitoring und Logging von Fehlerzuständen einer Anwendung. Es lässt sich sehr einfach sowohl als SaaS-Dienst als auch auf eigener Infrastruktur nutzen – dank des verfügbaren Docker-Images.

Die Unterstützung in einem Symfony-Projekt hinzuzufügen, ist dank Composer sehr einfach

bash
composer require sentry/sentry-symfony

In diesem Beitrag beschreibe ich, wie Sie Sentry als Performance-Monitor für eine PHP-Symfony-Anwendung einrichten, einschließlich einer Konfiguration für übersichtliches Monitoring einer GraphQL-API, die mit dem Overblog GraphQL Bundle implementiert ist. Für eine ganz einfache Grundkonfiguration genügt eine kleine Anpassung der Datei config/packages/sentry.yaml, und zwar wie folgt

yaml
sentry:
    dsn: '%env(SENTRY_DSN)%'
    options:
        traces_sample_rate: '%env(float:SENTRY_PERFORMANCE_RATIO)%'

Dabei gilt

  • traces_sample_rate im Abschnitt options – der Anteil der gemessenen Transaktionen als Zahl zwischen 0 und 1 (0.2 bedeutet, dass 20 % der Transaktionen gemessen werden). Auf der Dev-Stage setzen wir ruhig 1.0, in der Produktion dann zum Beispiel 0.02. 0 schaltet die Messung ab.

In weiteren Einstellungen lässt sich festlegen, was alles überwacht werden soll (dbal, twig, cache, http_client, console, siehe Einstellungen für Symfony). Welche Messungen Sie ein- oder ausschalten, hängt also vom konkreten Projekt und seinen Anforderungen ab. Jetzt müssen nur noch die Umgebungsvariablen in den .env-Dateien ergänzt werden, zum Beispiel so

yaml
SENTRY_DSN=https://sentry.io/dsn-vaseho-projektu
SENTRY_PERFORMANCE_RATIO=0.05

Ist alles richtig eingerichtet, sollten Ihnen schon mit den ersten Requests an die Anwendung die Performance-Kennzahlen der gemessenen Transaktionen angezeigt werden, identifiziert durch HTTP-Methode und URL des Requests, z. B. GET https://www.id-sign.com/poradna. Das kann für klassische Websites genügen, bei denen jeder Inhalt seine eigene URL hat. Wollen wir jedoch die Performance einer GraphQL-API überwachen, die für sämtliche Inhalte einen einzigen Endpoint hat, müssen wir die einzelnen API-Aufrufe nach der aufgerufenen Query benennen. Idealerweise ergänzen wir dazu auch den authentifizierten Benutzer, den genauen Wortlaut der Query und die verwendeten Variablen.

In unseren Projekten verwenden wir das Overblog GraphQL Bundle, das sich dafür sehr einfach erweitern lässt. Es genügt, eines seiner Events zu nutzen, nämlich graphql.pre_executor. Wir bereiten also einen passenden EventListener vor, zum Beispiel mit dem Namen GraphQLListener:

php
<?php

namespace App\EventListener;

use App\Entity\User;
use Overblog\GraphQLBundle\Event\ExecutorArgumentsEvent;
use Sentry\Breadcrumb;
use Sentry\SentrySdk;
use Sentry\State\Scope;
use Symfony\Bundle\SecurityBundle\Security;

use function Sentry\addBreadcrumb;
use function Sentry\captureException;
use function Sentry\configureScope;

readonly class GraphQLListener
{
    public function __construct(private Security $security)
    {
    }

    public function onPreExecutor(ExecutorArgumentsEvent $event): void
    {
        $name = $event->getOperationName() ?? 'Unknown operation';
        $sentryTransaction = SentrySdk::getCurrentHub()->getTransaction();
        $sentryTransaction?->setName($name);

        $authUser = $this->security->getUser();
        if ($authUser instanceof User) {
            configureScope(function (Scope $scope) use ($authUser): void {
                $scope->setUser([
                    'id' => $authUser->getId(),
                ]);
            });
        }

        try {
            addBreadcrumb(
                new Breadcrumb(
                    Breadcrumb::LEVEL_INFO,
                    Breadcrumb::TYPE_DEFAULT,
                    'GraphQL',
                    'query',
                    [
                        'userId' => $authUser?->getId(),
                        'operationName' => $name,
                        'query' => $event->getRequestString(),
                        'variables' => json_encode($event->getVariableValue(), JSON_THROW_ON_ERROR),
                    ]
                )
            );
        } catch (\JsonException $exception) {
            captureException($exception);
        }
    }
}

und registrieren ihn in config/services.yaml so, dass er für das oben genannte Event aufgerufen wird

yaml
App\EventListener\GraphQLListener:
    tags:
        - { name: kernel.event_listener, event: graphql.pre_executor, method: onPreExecutor }

Mit dieser einfachen Erweiterung sind die einzelnen Transaktionen

  • nach der aufgerufenen Query benannt – siehe $sentryTransaction?->setName(...)
  • um Informationen über den angemeldeten Benutzer ergänzt – siehe $scope->setUser(...)
  • und in den Breadcrumbs der gesamten Transaktion sehen wir auch den genauen Wortlaut der Query und die verwendeten Variablen – siehe addBreadcrumb(...)

Es gibt noch viel mehr Konfigurationsmöglichkeiten; für weitere Details empfehle ich, die offizielle Dokumentation durchzugehen.

Zum Schluss noch ein wichtiger Hinweis, der sich aus dem Wesen von PHP und seiner synchronen Ausführung ergibt. Sämtliche Messungen und die Kommunikation mit Sentry laufen (vermutlich in den meisten Fällen) im selben Prozess wie die Verarbeitung des gemessenen Requests. Daraus folgt, dass die Verarbeitungsgeschwindigkeit des eigentlichen Requests beeinflusst wird. Sentry bietet eine Lösung, die diesen Einfluss nahezu auf null reduziert – einen lokalen Proxy über Relay, siehe wiederum die offizielle Dokumentation.

Teilen
LinkedIn Facebook

Brauchen Sie mehr als einen Artikel?

Melden Sie sich unverbindlich – ein paar Sätze dazu, worum es geht, genügen.

Weiterlesen

Ähnliche Artikel

Weitere Notizen aus Betrieb und Entwicklung.

Gesamter Ratgeber