Poradniki

Content Security Policy

Jeśli Twoja strona używa Content Security Policy (CSP), musisz dodać do dyrektywy script-src 'nonce-<wartość>' i 'strict-dynamic', żeby Tracy działała poprawnie. Niektóre pluginy zewnętrzne mogą wymagać dodatkowych dyrektyw. Nonce nie jest wspierany w dyrektywie style-src; jeśli tej dyrektywy używasz, musisz dodać 'unsafe-inline', ale w trybie produkcyjnym należy tego unikać.

Przykład konfiguracji dla Nette Framework:

http:
	csp:
		script-src: [nonce, strict-dynamic]

Przykład w czystym PHP:

$nonce = base64_encode(random_bytes(20));
header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';");

Szybsze wczytywanie

Podstawowa integracja jest prosta. Jeśli jednak masz na swojej stronie wolno wczytujące się blokujące skrypty, mogą one spowolnić wczytywanie Tracy. Rozwiązaniem jest umieszczenie w szablonie <?php Tracy\Debugger::renderLoader() ?> przed jakimikolwiek skryptami:

<!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>

Znajdowanie źródła wyjścia

Natrafiłeś kiedyś na Cannot modify header information – headers already sent? Pojawia się, gdy coś (zabłąkana spacja, pusta linia albo BOM na początku pliku) zostanie wysłane do przeglądarki, zanim Twój kod ustawi nagłówek HTTP albo uruchomi sesję. Znalezienie winowajcy jest żmudne.

Pomaga Tracy\OutputDebugger. Włącz go jako pierwszą rzecz w swoim programie:

Tracy\OutputDebugger::enable();

Śledzi całe wyjście i na końcu strony wypisuje listę każdego miejsca, z którego wyjście zostało wysłane, wraz z plikiem, linią i odnośnikiem otwierającym je w Twoim edytorze. Byte order mark (BOM) na początku pliku również jest podświetlany, bo jest częstą niewidoczną przyczyną problemu.

Debugowanie żądań AJAX

Tracy automatycznie przechwytuje żądania AJAX wykonywane przez jQuery albo natywne API fetch. Żądania te wyświetlane są jako dodatkowe wiersze w Tracy Barze, co umożliwia łatwe i wygodne debugowanie AJAX-a.

Jeśli nie chcesz przechwytywać żądań AJAX automatycznie, możesz tę funkcję wyłączyć, ustawiając zmienną JavaScriptową:

window.TracyAutoRefresh = false;

Do ręcznego monitorowania konkretnych żądań AJAX dodaj nagłówek HTTP X-Tracy-Ajax z wartością zwracaną przez Tracy.getAjaxHeader(). Oto przykład użycia z funkcją fetch:

fetch(url, {
    headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'X-Tracy-Ajax': Tracy.getAjaxHeader(),
    }
})

To podejście pozwala na selektywne debugowanie żądań AJAX.

Przechowywanie danych

Tracy potrafi wyświetlać panele Tracy Bara i Bluescreeny dla żądań AJAX i przekierowań. Tracy tworzy własne sesje, przechowuje dane we własnych plikach tymczasowych i używa cookie tracy-session.

Tracy można też skonfigurować tak, żeby używała natywnej sesji PHP, którą trzeba uruchomić przed włączeniem Tracy:

session_start();
Debugger::setSessionStorage(new Tracy\NativeSession);
Debugger::enable();

Jeśli uruchomienie sesji wymaga bardziej złożonej inicjalizacji, możesz uruchomić Tracy natychmiast (żeby mogła obsłużyć ewentualne błędy), a potem zainicjalizować handler sesji. Na koniec poinformuj Tracy, że sesja jest gotowa do użycia, funkcją dispatch():

Debugger::setSessionStorage(new Tracy\NativeSession);
Debugger::enable();

// następnie inicjalizacja sesji
// i uruchomienie sesji
session_start();

Debugger::dispatch();

Funkcja setSessionStorage() istnieje od wersji 2.9; wcześniej Tracy zawsze używała natywnej sesji PHP.

Własny scrubber

Scrubber to filtr zapobiegający wyciekowi wrażliwych danych z dumpów, na przykład haseł albo danych uwierzytelniających. Filtr wywoływany jest dla każdej pozycji dumpowanej tablicy albo obiektu i zwraca true, jeśli wartość jest wrażliwa. W takim przypadku zamiast wartości wypisywane jest *****.

// zapobiega dumpowaniu wartości kluczy i właściwości takich jak `password`,
// `password_repeat`, `check_password`, `DATABASE_PASSWORD` itd.
$scrubber = function(string $key, $value, ?string $class): bool
{
	return preg_match('#password#i', $key) && $value !== null;
};

// używamy go dla wszystkich dumpów wewnątrz BlueScreenu
Tracy\Debugger::getBlueScreen()->scrubber = $scrubber;

Własny logger

Możemy utworzyć własny logger, który będzie logował błędy, nieprzechwycone wyjątki, a także będzie wywoływany metodą Tracy\Debugger::log(). Logger musi implementować interfejs Tracy\ILogger.

use Tracy\ILogger;

class SlackLogger implements ILogger
{
	public function log($value, $priority = ILogger::INFO)
	{
		// wysyła żądanie do Slacka
	}
}

A następnie go aktywujemy:

Tracy\Debugger::setLogger(new SlackLogger);

Jeśli używasz całego Nette Framework, możesz ustawić go w pliku konfiguracyjnym NEON:

services:
	tracy.logger: SlackLogger

Integracja z Monologiem

Pakiet Tracy dostarcza adapter PSR-3, umożliwiając integrację z monolog/monolog.

$monolog = new Monolog\Logger('main-channel');
$monolog->pushHandler(new Monolog\Handler\StreamHandler($logFilePath, Monolog\Logger::DEBUG));

$tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog);
Debugger::setLogger($tracyLogger);
Debugger::enable();

Debugger::log('info'); // zapisze: [<TIMESTAMP>] main-channel.INFO: info [] []
Debugger::log('warning', Debugger::WARNING); // zapisze: [<TIMESTAMP>] main-channel.WARNING: warning [] []

Integracja z Sentry

Możesz przekazywać błędy do usługi takiej jak Sentry, zachowując przy tym własne logowanie Tracy. Pomysł polega na opakowaniu pierwotnego loggera: nowy logger przekazuje komunikat do Sentry, a potem deleguje do poprzedniego, więc logi plikowe i powiadomienia e-mailem działają dalej.

use Sentry\Severity;
use Tracy\Debugger;
use Tracy\ILogger;

class SentryLogger implements ILogger
{
	private ILogger $originalLogger;

	public function __construct(string $dsn)
	{
		$this->originalLogger = Debugger::getLogger();
		\Sentry\init(['dsn' => $dsn]);
	}

	public function log(mixed $value, string $level = self::INFO)
	{
		// wysyłamy do Sentry
		if ($severity = $this->getSeverity($level)) {
			$value instanceof \Throwable
				? \Sentry\captureException($value)
				: \Sentry\captureMessage((string) $value, $severity);
		}

		// zachowujemy pierwotne logowanie Tracy (pliki, e-mail)
		return $this->originalLogger->log($value, $level);
	}

	private function getSeverity(string $level): ?Severity
	{
		return match ($level) {
			ILogger::DEBUG => Severity::debug(),
			ILogger::INFO => Severity::info(),
			ILogger::WARNING => Severity::warning(),
			ILogger::ERROR, ILogger::EXCEPTION => Severity::error(),
			ILogger::CRITICAL => Severity::fatal(),
			default => null,
		};
	}
}

Aktywujesz go tak samo jak każdy inny własny logger:

Debugger::setLogger(new SentryLogger('https://public@sentry.example.com/1'));

W aplikacji Nette zarejestruj go zamiast tego jako usługę tracy.logger:

services:
	tracy.logger: SentryLogger('https://public@sentry.example.com/1')

nginx

Jeśli Tracy nie działa na nginksie, prawdopodobnie jest źle skonfigurowany. Jeśli jest tam coś w rodzaju:

try_files $uri $uri/ /index.php;

zmień to na:

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