PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Debug Ausgaben im Code vergessen?

with 10 comments

Wahrscheinlich ist es jedem schon einmal passiert: Irgendwo ist ein var_dump() oder file_put_contents(‘/tmp/blub’) im Code vergessen worden, natürlich in einem sehr unwahrscheinlichen Programmzweig, und unverhofft kommt eine Email mit einer “komischen Ausgabe” inklusive internen Informationen von einem Benutzer. Aua!

Doch was kann man dagegen machen? Die beste Lösung ist natürlich: Keine Debug-Ausgaben solcher Art benutzen, sondern nur mittels IDE-Debugging/Breakpoints etc. den Code debuggen. Oder aber man verwendet Zend_Log in Verbindung mit dem Firebug-Writer, dann erhält man auch im Browser Debug-Ausgaben, aber nicht störend mitten im Code, sondern im Firebug/FirePHP Addon des Firefox. Außerdem kann man dieses Plugin dann nur für die Development-Umgebung aktivieren, im Produktivbetrieb sollte der Writer natürlich abgeschaltet werden. Dann ist sichergestellt dass keine internen Informationen nach außen dringen.

$logger = new Zend_Log();

// create the file writer
$formatter = new Zend_Log_Formatter_Simple('%timestamp% %priorityName% (%priority%): %message%' . PHP_EOL);
$fileWriter = new Zend_Log_Writer_Stream($tempDirectory . "/logs/" . date("Y-m-d") . ".log");
$fileWriter->setFormatter($formatter);
$logger->addWriter($fileWriter);

if ($debug) {
	$firebugWriter = new Zend_Log_Writer_Firebug();
	$logger->addWriter($firebugWriter);
}

Damit auch sichergestellt ist dass kein Entwickler doch noch var_dumps nutzt und vergisst, schreiben wir auch gleich noch einen Unit-Test, der den Source-Code nach “var_dump” und “file_put_contents” durchsucht und Alarm schlägt falls die beiden Funktionen an “nicht erlaubten Stellen” genutzt werden. Im unten stehenden Beispiel habe ich auch noch einen Test für “console.log()” Aufrufe im Javascript, denn die können Probleme machen wenn der Browser diese Funktion nicht unterstützt.

Das Script hier ist abgespeckt und muss natürlich an die Gegebenheiten angepasst werden. Es findet zum Beispiel auch auskommentierte var_dumps, das könnte man noch optimieren.

<?php
/*
 * This tests check in testing and stable if there are
 * console.log()  function calls in javascript
 * var_dump()     function calls in php
 *
 **/
class CheckDebuggingOutputTest extends PHPUnit_Framework_TestCase
{
	public function testJavascriptConsoleLog()
	{
		if (strpos(APPLICATION_PATH, 'testing') !== false || strpos(APPLICATION_PATH, 'stable') !== false) {
			$errorString = '';
			$jsList = file(APPLICATION_PATH . '/configs/js_includes.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
			foreach ($jsList as $jsFile) {
				$content = file(realpath(APPLICATION_PATH . '/../public/js/'.$jsFile), FILE_IGNORE_NEW_LINES);
				foreach ($content as $lineNumber => $line) {
					if (stripos($line, 'console.log') !== false) {
						$errorString .= 'console.log() call found in file '.$jsFile.' line '.($lineNumber+1)."\n";
					}
				}
			}
			if (!empty($errorString)) {
				$this->fail($errorString);
			}
		} else {
			$this->markTestSkipped('Debugging output should only be checked in testing and stable branch');
		}
	}

	public function testPhpVarDump()
	{
		if (strpos(APPLICATION_PATH, 'testing') !== false || strpos(APPLICATION_PATH, 'stable') !== false) {
			$errorString = '';
			// get all files with .php or .phtml extension, except sandbox/ folder
			$iterator = new RecursiveDirectoryIterator(realpath(APPLICATION_PATH.'/../'));
			foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) {
				if ($file->isFile() &&
					strpos($file->getPathname(), '/sandbox')===false &&
					$file->getFilename() != 'CheckDebuggingOutputTest.php' &&
					(substr($file->getFilename(), -3) == 'php' || substr($file->getFilename(), -4) == 'phtml')
				) {
					$content = file($file->getPathname(), FILE_IGNORE_NEW_LINES);
					foreach ($content as $lineNumber => $line) {
						if (stripos($line, 'var_dump') !== false) {
							$errorString .= 'var_dump() call found in file '.$file->getPathname().' line '.($lineNumber+1)."\n";
						}
					}
				}
			}
			if (!empty($errorString)) {
				$this->fail($errorString);
			}
		} else {
			$this->markTestSkipped('Debugging output should only be checked in testing and stable branch');
		}
	}
}

Findet ihr, dass ein solcher Test in die Testsuite reingehört, und der Build fehlschlagen darf wegen eines solchen “Fehlers”? Wie prüft ihr, ob ihr Debug-Ausgaben im Code vergessen habt? Oder nutzt ihr vor jedem Commit/Release die “projektweite Suche” der IDE, um nach soetwas zu suchen?

Written by Michael Kliewe

April 6th, 2010 at 11:04 am

10 Responses to 'Debug Ausgaben im Code vergessen?'

Subscribe to comments with RSS or TrackBack to 'Debug Ausgaben im Code vergessen?'.

  1. Ich finde ein Unittest ist ein Unittest und dieser überprüft nicht die Einhaltung von Konventionen. Dafür kann man sich doch besser eine PHP_CodeSniffer oder phpmd Regel schreiben, dort gehört soetwas mE hin.

    Ich würde in jedem Fall einen CI-Server für die Kontrolle nutzen. Insb. bei mehr als einem Entickler ist eine zentrale Instanz sinnvoll.

    Viele Grüße

    Norbert

    Norbert Bartels

    6 Apr 10 at 12:50

  2. Um das Problem mit dem console.log zu umgehen:

    try {
    console.log( ‘firefighting console…’ );
    }
    catch ( e ) {
    var console = { log: function() {} };
    }

    Thorsten

    6 Apr 10 at 12:59

  3. @Norbert: PHP_CodeSniffer wäre eine gute Idee. Kann man cruisecontrol/phpundercontrol so einrichten dass der Build fehlschlägt wenn diese eine CodeSniffer-Regel fehlschlägt? Mit phpmd hab ich mich auch noch nicht näher beschäftigt, also wie man da eigene Regeln/Berechnungen erstellt.

    CI ist klar, wir nutzen cruisecontrol/phpundercontrol, sodass die Unittests automatisch ausgeführt werden und es kein automatisches Deployment auf die Testserver gibt wenn ein Unittest fehlschlägt.

    Mit PHP_CodeSniffer/phpmd kann man die “console.log”-Suche nicht umsetzen nehme ich an…

    @Thorsten: Das geht sicher, aber ob die Lösung “schön” ist bezweifel ich, aber es ist eine schnelle und effektive Lösung. Debug-Code gehört meines Erachtens nicht in Produktiv-Code den der User zu Gesicht bekommt. Debugzeilen sollte man nach dem Debuggen wieder entfernen (müssen). Ich weiß garnicht wie das in bekannten Open-Source-Produkten geregelt ist…

    Michael Kliewe

    6 Apr 10 at 14:26

  4. Für javascript binde ich einfach eine leere dummy debug.js ein, try catch macht das nur unnötig langsam. Da meine javascript dateien bei größeren projekten ekelhaft lang werden, sind die zumeist auch komprimiert (d.h. dann auch ohne debug ausgaben). Um die debug funktionen automatisch zu entfernen, setz ich im js teil vor den Funktionsaufruf zwei Strichpunkte, dann wird die Zeile beim Komprimieren automatisch gelöscht.

    Wenn man sich das mal angewöhnt hat, ist das kein Streß mehr und verhindert firebug ausgaben im Produktivsystem.

    Im Backend rufe ich meine eigene Debug-Klasse auf, in der vor dem dump die Umgebung abgefragt wird. staging und production returnen nur, testing und development dumpen den output.

    Einen Unit test halte ich dafür deswegen für unnötig.

    An der Stelle mal ein Danke für deine Beiträge, dein Blog ist immer schön zu lesen.

    grüße

    Basti

    Basti

    6 Apr 10 at 15:16

  5. @Basti: solange du der einzige bist, der an dem Code arbeitet, funktioniert diese Vorgehensweise. Sobald aber ein Dutzend weitere Programmierer (entweder in der Firma oder gar bei einem Opensource-Projekt) mitarbeiten und einer sich evtl. nicht an deine Vorgehensweise hält, weil es es aus Gewohnheit anders macht, landen plötzlich var_dumps/console.logs im Code und du merkst es wahrscheinlich nicht, je größer das Projekt wird.

    Je größer das Projekt wird, umso mehr braucht man irgendeine automatisierte Lösung… Die Frage ist nur, welche.

    Danke für das Lob. Im Moment weiß ich noch nicht wie die nächsten Artikel aussehen, wenn ihr Ideen oder Wünsche habt, was man mal beleuchten könnte…

    Michael Kliewe

    6 Apr 10 at 21:36

  6. Im Build-Prozess den Code auf verdächtige Squenzen untersuchen und vermeiden.

    Persönliche Klassiker des PHP Debuggings:

    * var_dump() – gerne, da xdebug auf der Dev
    * die() – einfach mal wegbrechen
    * print_r() – seit ich var_dump() kenne benutze ich das nur noch selten.

    Ich würde nach solchen Befehlen Ausschau halten und im Build einen Fehler schmeissen, falls diese vorhanden sind.

    Build Prozesse können u.a. mit PHING automatisiert werden, geht aber auch mit Batch Dateien oder Shellskripten.

    Malte

    7 Apr 10 at 07:49

  7. Der Buildprozess wird bei cruisecontrol (oder auch beim Hudson) üblicherweise durch ein ant-Skript realisiert. Der Codesniffer wird dann durch einen exec-task aufgerufen und dieser kennt das Attribut failonerror. Wenn also der Codesniffer mit einem Fehler beendet wird, würde dieser Task den gesamten Build abbrechen. Ich denke durch geschickte Nutzung, kommt man hier sehr weit.

    Nach var_dump könnte man auch mit grep suchen :D

    Viele Grüße

    Norbert

    Norbert Bartels

    7 Apr 10 at 09:32

  8. @Malte: die() und print_r() ist auch eine sehr gute Idee, hab ich ewig nicht mehr im Code gesehen, aber danach suchen könnte man.

    @Norbert: D.h. ich müßte nur für diesen einen CodeSniff einen eigenen exec-Task erstellen, denn ich möchte nicht dass der Build-Prozess bei den anderen Sniffs fehlschlägt die wir aktuell haben.
    grep geht auch, aber es wird schwer, damit Ausnahmen zu definieren, d.h. Stellen an denen ein var_dump erlaubt ist und wo es keinen Fehler geben soll.

    Mit dem CodeSniffer und Javascript habe ich auch noch keine Erfahrungen, ob er da korrekt arbeitet, laut Doku soll er damit umgehen können, man könnte also evtl. auch nach console.log suchen. Wohl ist mir dabei aber nicht.

    Ist es relativ einfach, dem CodeSniffer Ausnahmen beizubringen? Also “Suche nach var_dump in .php Dateien, aber in der Datei xy.php ist es erlaubt”.

    Außerdem habe ich die CodeSniffs aktuell nicht mit im Repository, die sind statisch auf der CI-Maschine abgelegt. Müßte man dann auch überlegen wie man die ins Repository packt damit sie leicht erweiterbar sind.

    Michael Kliewe

    7 Apr 10 at 09:50

  9. Servus zusammen,

    ich hätte da noch ne Frage zum Logger im ZF:
    Ich erstelle meinen Logger nämlich in der Bootstrap und speichere sie anschließend in der Registry. Von dort aus wird er dann aus den Controllern etc. aufgerufen.
    –> Gibts da nen besseren Weg, als den Logger in der Registry stehen zu haben?

    Lukas

    7 Apr 10 at 17:22

  10. Seit 1.10 kann man es glaube ich auch als Ressource initialisieren, und dann zB im Controller via
    $this->getFrontController()->getParam(‘bootstrap’)->getResource(‘log’)
    oder
    $this->getInvokeArg(‘bootstrap’)->getResource(‘log’);

    Aber ich mache es aktuell auch via Zend_Registry, damit ich auch aus Service-Klassen darauf zugreifen kann wenn ich möchte.

    Michael Kliewe

    8 Apr 10 at 21:15

Leave a Reply

You can add images to your comment by clicking here.