Recettes

Content Security Policy

Si votre site utilise une Content Security Policy (CSP), vous devrez ajouter 'nonce-<valeur>' et 'strict-dynamic' à la directive script-src pour que Tracy fonctionne correctement. Certains plugins tiers peuvent exiger des directives supplémentaires. Le nonce n'est pas pris en charge dans la directive style-src ; si vous utilisez cette directive, vous devez ajouter 'unsafe-inline', mais cela devrait être évité en mode production.

Exemple de configuration pour Nette Framework :

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

Exemple en PHP pur :

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

Chargement plus rapide

L'intégration de base est simple. Si votre page web comporte cependant des scripts bloquants qui se chargent lentement, ils peuvent ralentir le chargement de Tracy. La solution est de placer <?php Tracy\Debugger::renderLoader() ?> dans votre template avant tout 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>

Localiser la source d'une sortie

Avez-vous déjà rencontré Cannot modify header information – headers already sent ? Cela apparaît quand quelque chose (une espace égarée, une ligne vide ou un BOM au début d'un fichier) est envoyé au navigateur avant que votre code ne pose un en-tête HTTP ou ne démarre une session. Trouver le coupable est fastidieux.

Tracy\OutputDebugger vous aide. Activez-le tout au début de votre programme :

Tracy\OutputDebugger::enable();

Il surveille toutes les sorties et, à la fin de la page, affiche la liste de tous les endroits d'où une sortie a été envoyée, avec le fichier, la ligne et un lien qui l'ouvre dans votre éditeur. Un byte order mark (BOM) au début d'un fichier est mis en évidence lui aussi, car c'est une cause invisible fréquente du problème.

Déboguer les requêtes AJAX

Tracy capture automatiquement les requêtes AJAX émises par jQuery ou par l'API native fetch. Ces requêtes s'affichent comme des lignes supplémentaires dans la Tracy Bar, ce qui rend le débogage AJAX simple et commode.

Si vous ne voulez pas capturer automatiquement les requêtes AJAX, vous pouvez désactiver cette fonctionnalité en définissant la variable JavaScript :

window.TracyAutoRefresh = false;

Pour surveiller manuellement certaines requêtes AJAX, ajoutez l'en-tête HTTP X-Tracy-Ajax avec la valeur renvoyée par Tracy.getAjaxHeader(). Voici un exemple d'utilisation avec la fonction fetch :

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

Cette approche permet de déboguer les requêtes AJAX de façon sélective.

Stockage des données

Tracy sait afficher les panneaux de la Tracy Bar et les écrans bleus pour les requêtes AJAX et les redirections. Tracy crée ses propres sessions, stocke les données dans ses propres fichiers temporaires et utilise un cookie tracy-session.

Tracy peut aussi être configurée pour utiliser une session native de PHP, qui doit être démarrée avant l'activation de Tracy :

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

Si le démarrage d'une session exige une initialisation plus complexe, vous pouvez démarrer Tracy immédiatement (afin qu'elle puisse traiter les erreurs qui surviennent), puis initialiser le gestionnaire de session. Informez enfin Tracy que la session est prête à l'emploi à l'aide de la fonction dispatch() :

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

// suivi de l'initialisation de la session
// et du démarrage de la session
session_start();

Debugger::dispatch();

La fonction setSessionStorage() existe depuis la version 2.9 ; avant cela, Tracy utilisait toujours la session native de PHP.

Scrubber personnalisé

Un Scrubber est un filtre qui empêche les données sensibles de fuir depuis les dumps, comme les mots de passe ou les identifiants. Le filtre est appelé pour chaque élément du tableau ou de l'objet affiché et renvoie true si la valeur est sensible. Dans ce cas, ***** est affiché à la place de la valeur.

// empêche l'affichage des valeurs des clés et propriétés comme `password`,
// `password_repeat`, `check_password`, `DATABASE_PASSWORD`, etc.
$scrubber = function(string $key, $value, ?string $class): bool
{
	return preg_match('#password#i', $key) && $value !== null;
};

// l'utilise pour tous les dumps du BlueScreen
Tracy\Debugger::getBlueScreen()->scrubber = $scrubber;

Logger personnalisé

Nous pouvons créer un logger personnalisé qui journalisera les erreurs et les exceptions non attrapées, et qui sera aussi invoqué par la méthode Tracy\Debugger::log(). Le logger doit implémenter l'interface Tracy\ILogger.

use Tracy\ILogger;

class SlackLogger implements ILogger
{
	public function log($value, $priority = ILogger::INFO)
	{
		// envoie une requête à Slack
	}
}

Puis nous l'activons :

Tracy\Debugger::setLogger(new SlackLogger);

Si vous utilisez tout Nette Framework, vous pouvez le définir dans le fichier de configuration NEON :

services:
	tracy.logger: SlackLogger

Intégration de Monolog

Le paquet Tracy fournit un adaptateur PSR-3, qui permet d'intégrer 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'); // écrit : [<TIMESTAMP>] main-channel.INFO: info [] []
Debugger::log('warning', Debugger::WARNING); // écrit : [<TIMESTAMP>] main-channel.WARNING: warning [] []

Intégration de Sentry

Vous pouvez transmettre les erreurs à un service comme Sentry tout en conservant la journalisation propre à Tracy. L'idée est d'envelopper le logger d'origine : le nouveau logger transmet le message à Sentry, puis délègue au précédent, si bien que les journaux fichier et les notifications par e-mail continuent de fonctionner.

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

		// conserve la journalisation d'origine de Tracy (fichiers, 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,
		};
	}
}

Activez-le comme n'importe quel autre logger personnalisé :

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

Dans une application Nette, enregistrez-le plutôt comme service tracy.logger :

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

nginx

Si Tracy ne fonctionne pas sous nginx, celui-ci est probablement mal configuré. S'il y a quelque chose comme :

try_files $uri $uri/ /index.php;

changez-le en :

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