Eine eigene Extension für Z-Ray? Kein Problem!

DIY: Redis-Extension für Z-Ray
Kommentare

Im Rahmen des im Juni vorgestellten Zend Server 7 hat Zend Technologies ein neues Feature für Entwickler präsentiert: Z-Ray, eine vom Applikationscode unabhängige Browser-Toolbar, die sich in den HTML-Output einklinkt und vielfältige Informationen des gerade ausgeführten Requests preisgibt.

In der aktuellen Zend-Server-Version 8 hat Z-Ray auf der Featureliste noch einmal mächtig zugelegt: Applikationsspezifische Erweiterungen, Custom Extensions und API-Call-Analyse durch Z-Ray Live. In diesem zweiten Teil der Artikel-Reihe geht es um die Implementierung eigener Extensions.

Ich will mehr!

So weit die Grundfunktionalität von Z-Ray. Jedes beschriebene Feature steht für alle PHP-Applikationen zur Verfügung, unabhängig von eingesetzten Frameworks – so lange sie unter PHP 5.5 und 5.6 laufen. Damit aber nicht genug: Über Extensions lässt sich Z-Ray nach Belieben erweitern. Zend Server liefert standardmäßig eine Zahl von applikations- und frameworkspezifischen Erweiterungen. So ist die Unterstützung für Zend Framework (1 + 2) und Symfony genauso an Bord wie auch für WordPress, Drupal und Magento, die automatisch erkannt werden. Extensions lauschen auf den Aufruf einer definierten Funktion. Wird diese dann ausgeführt, aktiviert sich auch die entsprechende Extension. Weitere Extensions finden sich auf GitHub. Aber auch diese Liste hat natürlich keinen Anspruch auf Vollständigkeit. Deshalb folgt ein kleines Beispiel, wie eine eigene Extension implementiert werden kann.

Im Folgenden sehen wir uns Schritt für Schritt an, wie eine Erweiterung für das populäre Caching-System Redis implementiert werden kann. Als Basisapplikation wird – wie oben beschrieben – eine normale Drupal-Installation mit aktiviertem Redis-Modul genutzt. Dank des Redis-Moduls cacht Drupal einzelne Blöcke weder auf Festplatte noch in der Datenbank, sondern nutzt den schnellen Memory-Cache von Redis. Prinzipiell ist es aber vollkommen egal, welche Applikation Items in Redis speichert. Für unsere Redis Extension brauchen wir nur irgendetwas, das in den Cache schreibt und auch wieder herausliest.

Eine Z-Ray Extension besteht grundsätzlich nur aus zwei Dateien: zray.php und einem Logo, damit die eigenen Tabs in der Toolbar auch schön zur Geltung kommen. In der zray.php steckt die eigentliche Logik für das Lesen, die Analyse und die Ausgabe der gewünschten Informationen. Diese Dateien müssen in das Verzeichnis /usr/local/zend/var/zray/extensions bzw. für Windows-Installationen nach c:Program Files (x86)ZendZend Serverdatazrayextensions in ein weiteres Verzeichnis – in unserem Fall Redis – kopiert werden. Der Namespace der zray.php muss dem Namen des Root-Verzeichnisses der Extension (hier: Redis) entsprechen, da die Extension ansonsten nicht korrekt eingelesen wird. Im nächsten Schritt wird ein Objekt vom Typ ZrayExtension instanziiert: $zre = new ZrayExtension(‚redis‘);. Diesem Objekt kann auch direkt das Logo zugewiesen werden:

 $zre->setMetadata(array(
  'logo' => __DIR__ . DIRECTORY_SEPARATOR . 'redis-logo.png'
));

Bislang wird diese (nicht funktionale) Extension ignoriert. Aktiviert wird sie anhand eines speziell gesetzten Triggers, der auf den Aufruf einer speziell gesetzten Funktion oder Methode (als String) wartet: $zre->setEnabledAfter(‚Redis::connect‘);.

Da ohne Connect zu Redis nicht auf den Cache zugegriffen werden kann, ist das eine optimale Trigger-Funktion. Ab jetzt können wir jede weitere Methode im Code tracen. Die eigentliche Tracing-Funktionalität werden wir in eine Klasse Redi‘ in der Datei Redis.php packen. Das ist nicht notwendig, aber etwas übersichtlicher, als alles in die zray.php zu stecken. Nachdem Redis.php in der zray.php inkludiert wurde, kann die Klasse auch instanziiert werden: $zrayRedis = new Redis();.

Fangen wir nun mit dem Connect auf den Redis-Cache an. Nachdem wir das Tracing gestartet haben, fügen wir den Code aus Listing 1 zur zray.php hinzu.

 $zre->traceFunction(
  'Redis::connect', 
  function($context, &$storage) use ($zrayRedis) {
    $zrayRedis->init(
      $context['functionArgs'][0], 
      $context['functionArgs'][1]
    );

  array($zrayRedis, 'statusInfo')
);

Wir sehen, dass eine Methode ZrayExtension::traceFunction mit drei Parametern existiert. Der erste ist ein String, der der zu tracenden Funktion/Methode entspricht. Die zwei folgenden Parameter sind Callables, die vor und nach dem Aufruf der zu analysierenden Funktion ausgeführt werden. Das Closure ruft eine init()-Methode des Redis-Objekts auf. Die eigentliche Implementierung folgt wenig später unten – interessanter an dieser Stelle sind die übergebenen Parameter. Das Closure selbst bekommt eine Variable $context und eine Referenz auf $storage übergeben. $context beinhaltet in einem Array sehr viele Informationen der zu tracenden Funktion. Darunter fallen z. B. die Häufigkeit der Aufrufe, aus welcher Datei diese Funktion aufgerufen wurde, aber auch die Parameter.

Und genau das machen wir uns hier zu Nutze. Da wir die connect-Funktion tracen, kann an dieser Stelle der Hostname und der Port ausgelesen werden. Aus Gründen der Einfachheit verzichten wir darauf, die optionalen Username- und Passwortinformationen zu übergeben. $storage hingegen enthält alle Daten, die schlussendlich in der Browser-Toolbar angezeigt werden. Diese Variable werden wir jetzt benötigen: Der dritte Parameter von ZrayExtension::traceFuntion() ist wiederum ein Callable, in diesem Fall aber ein zweielementiges Array mit Objekt und Methodennamen. Es wird also nach Aufruf der connect-Methode $zrayRedis->statusInfo() ausgeführt. Auch an diese Methode wird $context und $storage übergeben.

Reflektieren wir an dieser Stelle kurz, was das genau bedeutet: Ohne den eigentlichen Applikationscode zu verändern, injizieren wir Debug- oder Analysemethoden, die die Möglichkeit haben, vielfältige Informationen der aufgerufenen Funktion für eigene Zwecke zu nutzen.

Noch gibt es keine Redis-Klasse, sodass der Aufruf von Z-Ray im Browser zu einem Fehler führen würde. Holen wir das also nach. Im gleichen Namespace wie zray.php können wir jetzt die Klasse Redis und seine Methoden init und statusInfo implementieren (Listing 2).

 namespace Redis;

class Redis
{
  /**
   * @var Redis
   */
  private $redis;

  public function init($host, $port)
  {
    $this->redis = new Redis();
    $this->redis->connect($host, $port);
  }

  public function statusInfo($context, &$storage)
  {
    $config = $this->redis->config("GET", "*");
    $infoServer = $this->redis->info("server");
    $infoMemory = $this->redis->info("memory");

    $redisConfig = array_map(function ($key) use($config)
    {
      return array(
        'property' => $key,
        'value' => $config[$key]
      );
    }, array_keys($config));

    $redisServerInfo = array_map(function ($key) use($infoServer)
    {
      return array(
        'property' => $key,
        'value' => $infoServer[$key]
      );
    }, array_keys($infoServer));

    $redisMemoryInfo = array_map(function ($key) use($infoMemory)
    {
      return array(
        'property' => $key,
        'value' => $infoMemory[$key]
      );
    }, array_keys($infoMemory));

    $storage['config'] = $redisConfig;
    $storage['serverInfo'] = $redisServerInfo;
    $storage['memoryInfo'] = $redisMemoryInfo;
  }
}

Die init-Methode ist einfach. Hostname und Port sind übergeben worden und erlauben so einen Connect zu Redis. Redis::statusInfo() hingegen nutzt die Redis-Connection aus der init-Methode, um Informationen der Konfiguration, Server und Speicher zu bekommen. An dieser Stelle sei der Hinweis gestattet, dass es verschiedene Möglichkeiten gibt, von PHP mit Redis zu kommunizieren. Die bekanntesten sind vermutlich Predis und die Redis-PHP-Extension. Hier wird die PHP-Extension und deren Funktionssatz verwendet, um an die Redis-Infos zu gelangen.

Alles Weitere in dieser Methode ist Kosmetik: Die einzelnen Arrays werden mithilfe der array_map()-Funktion bearbeitet und unter einem bestimmten Key in den Storage gesetzt. Das wars: Keine HTML- und JavaScript-Fummelei. Es reicht, die entsprechenden Daten mit Bezeichnern in den Storage zu setzen. Z-Ray nutzt die Keys, um ansprechende Labels anzuzeigen. So wird aus dem Array-Key serverInfo auch das Label „Server Info“. Die Informations-Arrays selbst werden als Liste von Key-Value-Paaren angezeigt. Bei einem Aufruf im Browser hat die Z-Ray Toolbar ab jetzt drei neue Tabs für „Config“, „Server Info“ und „Memory Info“ (Abb. 2).

Abbildung 2: Die drei neuen Tabs der eigenen Extension

Abbildung 2: Die drei neuen Tabs der eigenen Extension

Das war aber noch nicht alles. Generelle Informationen zum Redis-Server sind zwar nett, gehaltvoller sind jedoch die Daten, die in den Cache geschrieben und wieder hinausgelesen werden. Dazu ergänzen wir drei Zeilen in der zray.php:

 $zre->traceFunction('Redis::hGetAll', function () {}, array($zrayRedis, 'hashGetAll'));
$zre->traceFunction('Redis::hMset', function () {}, array($zrayRedis, 'hSet'));
$zre->traceFunction('Redis::hSet', function () {}, array($zrayRedis, 'hSet'));

Wann immer im Applikationscode die entsprechenden Redis-Methoden aufgerufen werden, schlägt unsere Z-Ray Extension zu und verarbeitet diese im Nachgang (Listing 3).

 public function hashGetAll($context, &$storage) {
  $cacheKey = $context['functionArgs'][0];
  $value = $this->redis->hGetAll($cacheKey);
  $ttl = $this->redis->ttl($cacheKey);

  $item = array(
    'Cache Key' => $cacheKey,
    'value' => $value,
    'TTL' => gmdate('H ', $ttl) . 'hours,' . gmdate(' i ', $ttl) . 'minutes, ' . gmdate(' s ', $ttl) . 'seconds',
    'Item fetch Duration' => $context['durationExclusive'] . ' ms',
    'Called from File' => $context['calledFromFile'],
    'Called from Line' => $context['calledFromLine']
  );

  $storage['cachedItems'][$cacheKey] = $item;
}

public function hSet($context, &$storage) {
  $cacheKey = $context['functionArgs'][0];
  $value = $context['functionArgs'][1];
    
  if (!is_array($value) && isset($context['functionArgs'][2])) {
    $value = array($value => $context['functionArgs'][2]);
  }
    
  $item = array(
    'Cache Key' => $cacheKey,
    'value' => $value,
    'Called from File' => $context['calledFromFile'],
    'Called from Line' => $context['calledFromLine']
  );
    
  $storage['cacheWrite'][$cacheKey] = $item;
}

Für das Auslesen des Caches benutzen wir wiederum die Redis-Connection, um den Inhalt der gecachten Items zu ermitteln; auch der TTL kann erfragt werden. Diese Informationen werden aus dem $context mit Laufzeit- und Aufrufinformationen angereichert. Abhängig vom $cacheKey landet dieses Array dann wieder im Storage. Das Array für einen Cache-Write wird analog erstellt. Zu beachten ist hier nur, ob in der Applikation ein einzelnes Item in den Cache geschrieben wurde (Redis::hSet) oder direkt viele Items (Redis::hMset).

Ein gravierender Unterschied zur ersten getracten Funktion besteht darin, dass im Gegensatz zum Connect vermutlich mehrfach pro Request aus dem Cache gelesen, aber auch in den Cache geschrieben wird. $zrayRedis::hashGetAll() und $zrayRedis::hSet() werden demzufolge auch mehrfach aufgerufen. Daher ist auch die Angabe des $cacheKey als eindeutiger Key notwendig.

Zugegeben, wird ein Item mit einem Key erst gelesen, dann neu geschrieben und wieder gelesen, dann wird in dieser Implementierung in der Toolbar nur der neueste Wert angezeigt. Es wird sich kein vorheriger Zustand gemerkt. Ein Reload im Browser fördert zwei neue Tabs (Abb. 3) zu Tage. Eine beeindruckende Informationsfülle für etwa 100 Zeilen Code, der keinerlei Abhängigkeiten zur Applikation hat. Wer noch mehr Details zur Implementierung einer Z-Ray-Extension wissen möchte, der sollte sich dieses dazu passende Dokument anschauen.

Abbildung 3: Ein Reload, zwei neue Tabs

Abbildung 3: Ein Reload, zwei neue Tabs

Dev oder Ops?

Vielfach sagen Entwickler jetzt schon, dass sie Z-Ray beim täglichen Programmieren nicht mehr missen möchten. Es hat schon fast den Stellenwert eines Debuggers erreicht. Warum sollte man nicht auch im produktiven Betrieb davon profitieren können? Klar ist, Z-Ray sollte auf einem Livesystem nicht dauerhaft eingeschaltet sein, sonst hat ja auch der End-User tiefgreifende Einblicke in die Applikation. Aber es gibt glücklicherweise einen anderen Weg: Im Zend-Server-UI kann Z-Ray aktiviert, deaktiviert aber auch in den so genannten „Secured Mode“ gesetzt werden. In diesem Modus kann, ebenfalls über das UI, ein Token generiert werden.

Dieses Access Token kann den Zugriff auf Z-Ray anhand der IP-Adresse, einem URL und für einen bestimmten Zeitraum freischalten bzw. einschränken. Einmalig generiert, hängt man dieses Token als GET-Parameter an den zu untersuchenden URL, z. B. <a href=“http://example.com/path/page.php?zsdbt=“ class=“elf-external elf-icon“ rel=“nofollow“>http://example.com/path/page.php?zsdbt=. Ab sofort ist das Token in einem Cookie gespeichert, und jeder nachfolgende Request auf das Zielsystem wird ebenfalls die Z-Ray Toolbar öffnen. User, die gleichzeitig auf das System zugreifen, bekommen davon nichts mit. Was für eine traumhafte Situation: Fehlersuche und Analyse auf einem Produktivsystem, ohne dass mit vi im Sourcecode rumgefummelt werden muss.

Natürlich ist das nicht die empfohlene, tägliche Arbeitsweise, aber wer kennt es nicht: Ein neues Feature funktioniert tadellos auf dem Entwicklungsrechner, der Unit Test läuft durch, Test- und Staging-System zeigen auch keine Auffälligkeiten, aber auf dem Livesystem kracht es. Solch eine Fehlersuche macht in der Regel keinen Spaß. Wenn dann aber ein Tool wie Z-Ray zur Hand ist, kann das Problem schneller und mit geringerem Risiko gefunden werden.

International PHP Conference 2015 Spring Edition

International PHP ConferenceDiese Themen – und noch viele mehr – werden auf der International PHP Conference 2015 in Berlin Beachtung finden. Als Bonus warten noch alle Sessions der parallel stattfindenden webinale 2015 auf euch.

Alle weiteren Informationen zum Programm und den Speakern findet ihr unter phpconference.com. Und wer sich bis zum 12. Februar 2015 anmeldet, kann von attraktiven Frühbucherrabatten profitieren.

API und Mobile

Z-Ray ist eine Browser-Toolbar. Was aber, wenn der Client kein Browser ist? Sondern eine native App fürs Smartphone? Oder ein konsumierender Web Service? Dann kommt „Z-Ray Live!“ ins Spiel. Über die Zend-Server-Oberfläche lässt sich Z-Ray in einen Recording-Modus setzen. Jeder einkommende Request auf dem Server wird getracet und direkt im Zend-Server-UI angezeigt. Das ist keine gute Idee für den Livebetrieb, weil tatsächlich jeder Request aufgezeichnet wird. Aber während der Entwicklung eines REST-Service oder einer iPhone-App kann es extrem hilfreich sein.

Eine Kleinigkeit zum Schluss: Zend Server selbst führt Aktionen ausschließlich über REST-Services aus, so auch die von Z-Ray. Sollte also der Wunsch bestehen, Z-Ray-Informationen für eine 3rd-Party-Applikation auszulesen, dann ist kein Browser notwendig: Mithilfe des so genannten Web-API können Request Infos, Backtraces und vieles mehr ausgelesen werden.

Fazit

Z-Ray ist ein äußerst mächtiges Werkzeug, welches in unterschiedlichen Phasen während des Application-Lifecycles eingesetzt werden kann. Sei es zur Performanceanalyse während der Entwicklung, bei der Fehlersuche von mobilen Apps oder für das Listen von Datenbank-Queries im Produktivbetrieb. In allen Bereichen kann Z-Ray die Zeit für die Problemuntersuchung drastisch reduzieren. Dazu ist es mit geringem Aufwand erweiterbar, sodass auch das Big Picture vom eigenen Code nicht fern ist. Alles in allem möchte man es schon nach kurzem Einsatz nicht mehr missen.

Übersicht über alle Artikel dieser Reihe

Dieser Artikel ist Teil einer Reihe zum Thema Z-Ray. Diese umfasst die folgenden Teile:

Teil 1: Installation und Grundfunktionalität von Z-Ray

Teil 2: Eigene Extensions implementieren

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -