Ricette

Content Security Policy

Se il vostro sito usa la Content Security Policy (CSP), dovrete aggiungere alla direttiva script-src i valori 'nonce-<valore>' e 'strict-dynamic' perché Tracy funzioni correttamente. Alcuni plugin di terze parti possono richiedere altre direttive. Il nonce non è supportato nella direttiva style-src; se usate questa direttiva, dovete aggiungere 'unsafe-inline', cosa che però andrebbe evitata in modalità produzione.

Esempio di configurazione per il Nette Framework:

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

Esempio in PHP puro:

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

Caricamento più veloce

L'integrazione di base è semplice. Se però nella vostra pagina web ci sono script bloccanti che si caricano lentamente, possono rallentare il caricamento di Tracy. La soluzione è mettere nel template <?php Tracy\Debugger::renderLoader() ?> prima di qualsiasi script:

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

Individuare la sorgente dell'output

Vi siete mai imbattuti in Cannot modify header information – headers already sent? Compare quando qualcosa (uno spazio sperduto, una riga vuota o un BOM all'inizio di un file) viene inviato al browser prima che il vostro codice imposti un header HTTP o avvii una sessione. Trovare il colpevole è noioso.

Vi aiuta Tracy\OutputDebugger. Attivatelo come primissima cosa nel programma:

Tracy\OutputDebugger::enable();

Sorveglia tutto l'output e alla fine della pagina stampa l'elenco di ogni punto da cui è stato inviato dell'output, con il file, la riga e un link che lo apre nel vostro editor. Viene evidenziato anche un byte order mark (BOM) all'inizio del file, perché è una causa invisibile molto frequente del problema.

Debug delle richieste AJAX

Tracy cattura automaticamente le richieste AJAX effettuate con jQuery o con l'API nativa fetch. Queste richieste vengono mostrate come righe aggiuntive nella barra di Tracy, il che rende il debug AJAX semplice e comodo.

Se non volete catturare automaticamente le richieste AJAX, potete disattivare questa funzione impostando la variabile JavaScript:

window.TracyAutoRefresh = false;

Per monitorare manualmente determinate richieste AJAX, aggiungete l'header HTTP X-Tracy-Ajax con il valore restituito da Tracy.getAjaxHeader(). Ecco un esempio d'uso con la funzione fetch:

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

Questo approccio permette di fare il debug selettivo delle richieste AJAX.

Salvataggio dei dati

Tracy sa mostrare i pannelli della barra e le Bluescreen anche per le richieste AJAX e i redirect. Tracy crea sessioni proprie, salva i dati in file temporanei propri e usa il cookie tracy-session.

Tracy si può anche configurare perché usi la sessione nativa di PHP, che va avviata prima di attivare Tracy:

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

Se avviare la sessione richiede un'inizializzazione più complessa, potete avviare Tracy subito (così può gestire gli eventuali errori) e inizializzare il gestore di sessione solo dopo. Alla fine comunicate a Tracy che la sessione è pronta all'uso con la funzione dispatch():

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

// segue l'inizializzazione della sessione
// e l'avvio della sessione
session_start();

Debugger::dispatch();

La funzione setSessionStorage() esiste dalla versione 2.9; prima Tracy usava sempre la sessione nativa di PHP.

Scrubber personalizzato

Lo Scrubber è un filtro che impedisce ai dati sensibili di uscire dai dump, per esempio password o credenziali. Il filtro viene chiamato per ogni elemento dell'array o dell'oggetto sottoposto a dump e restituisce true se il valore è sensibile. In tal caso al posto del valore viene stampato *****.

// impedisce il dump dei valori di chiavi e proprietà come `password`,
// `password_repeat`, `check_password`, `DATABASE_PASSWORD` ecc.
$scrubber = function(string $key, $value, ?string $class): bool
{
	return preg_match('#password#i', $key) && $value !== null;
};

// lo usiamo per tutti i dump dentro la BlueScreen
Tracy\Debugger::getBlueScreen()->scrubber = $scrubber;

Logger personalizzato

Possiamo creare un logger personalizzato che registrerà gli errori, le eccezioni non catturate e che verrà richiamato anche dal metodo Tracy\Debugger::log(). Il logger deve implementare l'interfaccia Tracy\ILogger.

use Tracy\ILogger;

class SlackLogger implements ILogger
{
	public function log($value, $priority = ILogger::INFO)
	{
		// invia una richiesta a Slack
	}
}

E poi lo attiviamo:

Tracy\Debugger::setLogger(new SlackLogger);

Se usate tutto il Nette Framework, potete impostarlo nel file di configurazione NEON:

services:
	tracy.logger: SlackLogger

Integrazione con Monolog

Il pacchetto Tracy offre un adattatore PSR-3, che permette di integrare 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'); // scrive: [<TIMESTAMP>] main-channel.INFO: info [] []
Debugger::log('warning', Debugger::WARNING); // scrive: [<TIMESTAMP>] main-channel.WARNING: warning [] []

Integrazione con Sentry

Potete inoltrare gli errori a un servizio come Sentry mantenendo il logging proprio di Tracy. L'idea è avvolgere il logger originale: il nuovo logger passa il messaggio a Sentry e poi lo delega al precedente, così i log su file e le notifiche per email continuano a funzionare.

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)
	{
		// invia a Sentry
		if ($severity = $this->getSeverity($level)) {
			$value instanceof \Throwable
				? \Sentry\captureException($value)
				: \Sentry\captureMessage((string) $value, $severity);
		}

		// mantiene il logging originale di Tracy (file, email)
		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,
		};
	}
}

Lo attivate come qualsiasi altro logger personalizzato:

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

In un'applicazione Nette registratelo invece come servizio tracy.logger:

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

nginx

Se Tracy non funziona su nginx, probabilmente è configurato male. Se c'è qualcosa come:

try_files $uri $uri/ /index.php;

cambiatelo in:

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