Knihovna Tracy, která zdomácněla pod jménem Laděnka, je užitečnou každodenní pomocnicí PHP programátora. Pomůže vám

  • rychle odhalit a opravit chyby
  • logovat chyby
  • vypisovat proměnné
  • a měřit čas skriptů a databázových dotazů
  • a sledovat paměťové nároky

PHP je jazyk na sekání těžko odhalitelných chyb jako stvořený, neboť dává vývojářům značnou volnost. O to cennější je ladící nástroj Tracy. Mezi diagnostickými nástroji pro PHP představuje naprostou špičku. Pokud se dnes setkáváte s Laděnkou poprvé, tak věřte, že váš život se začne dělit na ten před Laděnkou a ten s ní. Vítejte v té lepší části!

Instalace

Nejlepší způsob, jak nainstalovat Tracy je stáhnout nejnovější balíček, nebo použit Composer:

composer require tracy/tracy

Můžete si také stáhnout celý balíček jako soubor tracy.phar.

Tracy kompatibilní s PHP kompatibilní s prohlížeči
Tracy 2.7 PHP 7.1 – 7.4 Chrome 49+, Firefox 45+, MS Edge 14+, Safari 10+ a iOS Safari 10.2+
Tracy 2.6 PHP 7.1 – 7.4 Chrome 49+, Firefox 45+, MS Edge 14+, Safari 10+ a iOS Safari 10.2+
Tracy 2.5 PHP 5.4 – 7.3 Chrome 49+, Firefox 45+, MS Edge 12+, Safari 10+ a iOS Safari 10.2+
Tracy 2.4 PHP 5.4 – 7.2 Chrome 29+, Firefox 28+, IE 11+ (kromě AJAXu), MS Edge 12+, Safari 9+ a iOS Safari 9.2+

Použití

Laděnku aktivujeme snadno. Stačí přidat do kódu, nejlépe hned za načtení knihovny (např. require 'vendor/autoload.php') a dříve než se odešle jakýkoliv výstup, následující:

use Tracy\Debugger;

Debugger::enable();

První, čeho si můžete na stránce všimnout, Debugger Bar.

(Pokud nic nevidíte, znamená to, že Tracy běží v produkčním režimu. Tracy je z bezpečnostních důvodů viditelná pouze na localhost. Můžete vynutit, aby se Tracy běžela ve vývojovém režimu předáním Debugger::DEVELOPMENT jako prvního parametru metody enable().)

enable() změní úrovneň hlášených chyb na E_ALL.

Debugger Bar

Debugger Bar je plovoucí panel, který se zobrazí v pravém dolním rohu stránky. Můžeme jej přesouvat myší a po znovunačtení stránky si bude pamatovat svou pozici.

Do Debugger Baru lze přidávat další užitečné panely. Řadu jich najdete v doplňcích, nebo si dokonce můžete napsat vlastní.

Pokud nechcete zobrazovat Debugger Bar, nastavte:

Debugger::$showBar = false;

Vizualizace chyb a výjimek

Jistě dobře víte, jak PHP oznamuje chyby: do zdrojového kódu stránky vypíše něco takového:

Parse error:  syntax error, unexpected '}' in HomepagePresenter.php on line 15

nebo při nezachycené výjimce:

Fatal error:  Uncaught Nette\MemberAccessException: Call to undefined method Nette\Application\UI\Form::addTest()? in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php:100
Stack trace:
#0 /sandbox/vendor/nette/utils/src/Utils/Object.php(75): Nette\Utils\ObjectMixin::call(Object(Nette\Application\UI\Form), 'addTest', Array)
#1 /sandbox/app/forms/SignFormFactory.php(32): Nette\Object->__call('addTest', Array)
#2 /sandbox/app/presenters/SignPresenter.php(21): App\Forms\SignFormFactory->create()
#3 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(181): App\Presenters\SignPresenter->createComponentSignInForm('signInForm')
#4 /sandbox/vendor/nette/component-model/src/ComponentModel/Container.php(139): Nette\ComponentModel\Container->createComponent('signInForm')
#5 /sandbox/temp/cache/latte/15206b353f351f6bfca2c36cc.php(17): Nette\ComponentModel\Co in /sandbox/vendor/nette/utils/src/Utils/ObjectMixin.php on line 100

V takovém výpisu se zorientovat není zrovna snadné. Pokud zapneme Laděnku, chyba nebo výjimka se zobrazí dočista v jiné podobě:

Nezachycená výjimka v provedení Tracy

Chybová zpráva doslova křičí, vidíme část zdrojového kódu se zvýrazněným řádkem, kde k chybě došlo, informace Call to undefined method Nette\Http\User::isLogedIn() srozumitelně vysvětluje, o jakou chybu se jedná. Celá stránka je navíc živá, můžeme se proklikávat k větším podrobnostem. Zkuste si to.

A víte co? Tímto způsobem zachytí a zobrazí i fatální chyby. Bez nutnosti instalovat jakékoliv rozšíření.

Chyby jako překlep v názvu proměnné nebo pokus o otevření neexistujícího souboru generují hlášení úrovně E_NOTICE nebo E_WARNING. Ty lze v grafice stránky snadno přehlédnout, dokonce nemusí být viditelná vůbec (leda pohledem do kódu stránky).

Nebo mohou být zobrazeny stejně jako chyby:

Debugger::$strictMode = true;
Debugger::$strictMode = true; // zobraz všechny chyby
Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // všechny chyby kromě deprecate oznámení

Abychom odhalili i překlepy při přiřazení do proměnné objektu, použijeme traitu Nette\SmartObject.

Content Security Policy

Pokud váš web používá Content Security Policy, budete muset přidat stejné 'nonce-<value>' do script-src a style-src, aby Tracy správně fungovala. Některé doplňky třetích stran mohou vyžadovat další nastavení. V produkčním režimu 'unsafe-inline' & 'unsafe-eval' nepřidávejte, pokud nemusíte.

Příklad konfigurace pro Nette Framework:

http:
	csp:
		script-src: nonce
		style-src: nonce

Rychlejší načítání

Spuštění je přímočaré, pokud však máte na webové stránce pomalu načítající se blokující skripty, mohou zpomalit načítání Tracy. Řešením je umístit <?php Tracy\Debugger::renderLoader() ?> do vaší šablony před všechny skripty:

<!DOCTYPE html>
<html>
<head>
	<title>...<title>
	<?php Tracy\Debugger::renderLoader() ?>
	<link rel="stylesheet" href="assets/style.css">
	<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>

AJAX a přesměrování

Tracy je schopena zobrazit Debugger Bar a Bluescreen pro AJAX a přesměrované požadavky. Musíte jen spustit session dříve než Tracy:

session_start();
Debugger::enable();

V případě, že používáte nestandardní obsluhu session, můžete Tracy spustit okamžitě (aby mohla logovat případné chyby), pak spustit session a nakonec informovat Tracy, že session je připravena k použití prostřednictvím dispatch():

Debugger::enable();

// inicializace session
session_start();

Debugger::dispatch();

Otevírání v editoru

Při zobrazení chybové stránky lze kliknout na jména souborů a ty se otevřou ve vašem editoru s kurzorem na příslušné řádce. Také lze soubory vytvářet (akce create file) nebo v nich opravovat chyby (akce fix it). Aby tak fungovalo, stačí nakonfigurovat prohlížeč a systém.

Produkční režim a logování chyb

Jak vidíte, Laděnka je poměrně výřečná, což lze ocenit ve vývojovém prostředí, zatímco na produkčním serveru by to způsobilo hotové neštěstí. Tam se totiž žádné ladící informace vypsat nesmí. Laděnka proto disponuje autodetekcí prostředí a pokud příklad spustíme na ostrém serveru, chyba se místo zobrazení zaloguje a návštěvník uvidí jen uživatelsky srozumitelnou hlášku:

Produkční režim potlačí zobrazování všech ladících informacích, které posíláme ven pomocí dump() nebo Debugger::fireLog(), a samozřejmě také všech chybových zpráv, které generuje PHP. Pokud jste tedy v kódu zapomněli nějaké dump($obj), nemusíte se obávat, na produkčním serveru se nic nevypíše.

K nastavování režimu se používá první parametr metody Debugger::enable(). Režim lze napevno nastavit konstantou Debugger::PRODUCTION nebo Debugger::DEVELOPMENT. Další možnost je, že bude vývojový režim zapnutý při přístupu z dané IP adresy s danou hodnotou tracy-debug cookie. Používá se syntaxe hodnota-cookie@ip-adresa.

Pokud jej neuvedeme, má výchozí hodnotu Debugger::DETECT a v takovém případě se detekuje režim podle IP adresy serveru – je-li dostupný přes veřejnou IP adresu, běží v produkčním režimu, je-li na lokální, tak ve vývojářském. V drtivé většině případů tak není potřeba režim nastavovat a správně se rozezná podle toho, jestli aplikaci spouštíme na svém lokálním serveru nebo v ostrém provozu.

V produkčním režimu Laděnka automaticky všechny chyby a zachycené výjimky zaznamenává do textového logu. Pokud neurčíme jinak, půjde o soubor log/error.log. Logování chyb je přitom nesmírně užitečné. Představte si, že všichni uživatelé vaší aplikace jsou vlastně betatesteři, kteří zdarma odvádějí špičkovou práci v hledání chyb a vy byste udělal hloupost, kdybyste jejich cenné reporty zahodil bez povšimnutí do odpadkového koše.

Pokud potřebujeme zalogovat vlastní zprávu nebo vámi zachycenou výjimku, použijeme k tomu metodu log():

Debugger::log('Doslo k necekane chybe'); // textová zpráva

try {
	kritickaOperace();
} catch (Exception $e) {
	Debugger::log($e); // logovat lze i výjimku
	// nebo Debugger::log($e, Debugger::ERROR) odešle i e-mailovou notifikaci
}

Jiný adresář pro logování chyb lze nastavit druhým parametrem metody enable():

Debugger::enable(Debugger::DETECT, __DIR__ . '/mylog');

Pokud chcete, aby Tracy logovala PHP chyby jako E_NOTICE nebo E_WARNING s podrobnými informacemi (HTML report), nastavte Debugger::$logSeverity:

Debugger::$logSeverity = E_NOTICE | E_WARNING;

Pro skutečného profíka je error log klíčovým zdrojem informací a chce být ihned informován o každé nové chybě. Laděnka mu v tom vychází vstříc, umí totiž o novém záznamu v logu informovat e-mailem. Kam odesílat emaily určíme proměnnou $email:

Debugger::$email = 'admin@example.com';

Pokud používáte celý Nette Framework, lze toto a další nastavit v konfiguračním souboru.

Aby vám však nezaplavila e-mailovou schránku, pošle vždy pouze jednu zprávu a vytvoří soubor email-sent. Vývojář po přijetí e-mailové notifikace zkontroluje log, opraví aplikaci a smaže monitorovací soubor, čímž se opět aktivuje odesílání e-mailů.

Dump proměnných

Každý ladič je dobrým kamarádem s funkcí var_dump, která podrobně vypíše obsah proměnné. Bohužel v prostředí HTML výpis pozbude formátování a slije se do jednoho řádku, o sanitizaci HTML kódu ani nemluvě. V praxi je nezbytné var_dump nahradit šikovnější funkcí. Tou je právě dump().

$arr = [10, 20.2, true, null, 'hello'];

dump($arr);
// nebo Tracy\Debugger::dump($arr);

vygeneruje výstup:

Dále můžeme změnit hloubku zanoření pomocí Debugger::$maxDepth a délku zobrazovaných popisek pomocí Debugger::$maxLength. Nižší hodnoty laděnku přirozeně zrychlí.

Debugger::$maxDepth = 2; // default: 3
Debugger::$maxLength = 50; // default: 150

Funkce dump() umí vypsat i další užitečné informace. Konstanta Tracy\Dumper::LOCATION_SOURCE přidá tooltip s cestou k místu, kde byla funkce zavolána. Tracy\Dumper::LOCATION_LINK nám poskytne odkaz na ono místo. Tracy\Dumper::LOCATION_CLASS u každého dumpovaného objektu vypíše tooltip s cestou k souboru, ve kterém je definována jeho třída. Konstanty se nastavují do proměnné Debugger::$showLocation ještě před voláním dump(). Pokud chceme nastavit více hodnot najednou, spojíme je pomocí operátoru |.

Debugger::$showLocation = Tracy\Dumper::LOCATION_SOURCE; // Nastaví pouze výpis o místě volání funkce
Debugger::$showLocation = Tracy\Dumper::LOCATION_CLASS | Tracy\Dumper::LOCATION_LINK; // Nastaví zároveň výpis odkazu a cestu ke třídě
Debugger::$showLocation = false; // Vypne výpis dodatečných informací
Debugger::$showLocation = true; // Zapne výpis všech dodatečných informací

Praktickou alternativou k dump() je dumpe() (dump & exit) a bdump(). Ten nám umožňuje vypsat hodnotu proměnné v panelu Debugger Baru. To je velmi šikovné, jelikož jsou dumpy oddělené od rozložení stránky a také k nim můžeme umístit komentář.

bdump([2, 4, 6, 8], 'sudá čísla do deseti');
bdump([1, 3, 5, 7, 9], 'lichá čísla do deseti');

Měření času

Dalším užitečným nástrojem ladiče jsou stopky s přesností na mikrosekundy:

Debugger::timer();

// princi můj malinký spi, ptáčkové sladce již sní...
sleep(2);

$elapsed = Debugger::timer();
// $elapsed ? 2

Volitelným parametrem je možno dosáhnout vícenásobných měření.

Debugger::timer('page-generating');
// nějaký kód
Debugger::timer('rss-generating');

// nějaký kód
$rssElapsed = Debugger::timer('rss-generating');
$pageElapsed = Debugger::timer('page-generating');
Debugger::timer(); // zapne stopky

... // časově náročná operace

echo Debugger::timer(); // vypíše uplynulý čas v sekundách

FireLogger

Ne vždy lze ladící informace posílat do okna prohlížeče. Týká se to AJAXových požadavků, generování XML výstupu či obrázků. V takovým případě můžeme zasílat zprávy samostatným kanálem do FireLoggeru. Chyby úrovně Notice a Warning jsou do okna FireLoggeru dokonce zasílány automaticky. Taktéž je možné logovat výjimky, které sice aplikace zachytila, ale stojí za to na ně upozornit.

Jak na to?

  • nainstalujte si rozšíření FireLogger for Chrome
  • zapněte Chrome DevTools (klávesou Ctrl-Shift-I) a otevřete Konzoli

Otevřete si demo stránku a ve uvidíte zprávy odeslané z PHP.

Protože Tracy komunikuje s Firebugem přes HTTP hlavičky, je nutné volat logovací funkce ještě před tím, než PHP skript cokoliv odešle na výstup. Také je možné zapnout output buffering a tím výstup oddálit.

use Tracy\Debugger;

Debugger::fireLog('Hello World'); // vypíšeme řetězec do konzoly FireLoggeru

Debugger::fireLog($_SERVER); // do konzoly lze vypsat i pole nebo objekty

Debugger::fireLog(new Exception('Test Exception')); // či dokonce výjimky

Výsledek vypadá asi takto:

Vlastní logger

Můžeme si vytvořit vlastní logger, který bude logovat chyby, nezachycené výjimky a také bude vyvolán metodou Tracy\Debugger::log(). Logger implementuje rozhraní Tracy\ILogger.

use Tracy\ILogger;

class SlackLogger implements ILogger
{
	public function log($value, $priority = ILogger::INFO)
	{
		// pošle request na Slack
	}
}

A následně jej aktivujeme:

Tracy\Debugger::setLogger(new SlackLogger);

Pokud používáme plný Nette Framework, můžete jej nastavit v konfiguračním NEON souboru:

services:
	tracy.logger: SlackLogger

nginx

Pokud vám nefunguje Tracy na serveru nginx, nespíš je špatně nakonfigurovaný. Pokud je v konfiguraci něco jako

try_files $uri $uri/ /index.php;

změňte to na

try_files $uri $uri/ /index.php$is_args$args;

Porty

Toto je seznam neoficiálních portů pro další frameworky a CMS:

Související články na blogu