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;