Рецепты

Content Security Policy

Если ваш сайт использует Content Security Policy (CSP), для правильной работы Tracy вам нужно добавить в директиву script-src значения 'nonce-<value>' и 'strict-dynamic'. Некоторые сторонние плагины могут требовать дополнительных директив. В директиве style-src nonce не поддерживается; если вы её используете, придётся добавить 'unsafe-inline', но в продакшн-режиме этого стоит избегать.

Пример конфигурации для Nette Framework:

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

Пример на чистом PHP:

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

Более быстрая загрузка

Базовая интеграция проста. Однако если на вашей странице есть медленно загружающиеся блокирующие скрипты, они могут замедлить и загрузку Tracy. Решение – поместить в шаблон <?php Tracy\Debugger::renderLoader() ?> перед любыми скриптами:

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

Поиск источника вывода

Сталкивались ли вы когда-нибудь с Cannot modify header information – headers already sent? Это происходит, когда что-то (случайный пробел, пустая строка или BOM в начале файла) отправляется в браузер до того, как ваш код задаёт HTTP-заголовок или запускает сессию. Искать виновника утомительно.

Поможет Tracy\OutputDebugger. Включите его самым первым делом в программе:

Tracy\OutputDebugger::enable();

Он следит за всем выводом и в конце страницы выводит список всех мест, откуда был отправлен вывод, вместе с файлом, строкой и ссылкой, открывающей это место в вашем редакторе. Подсвечивается и маркер порядка байтов (BOM) в начале файла, потому что это частая невидимая причина проблемы.

Отладка AJAX-запросов

Tracy автоматически перехватывает AJAX-запросы, выполненные через jQuery или нативный API fetch. Эти запросы отображаются дополнительными строками в панели Tracy, что делает отладку AJAX простой и удобной.

Если вы не хотите перехватывать AJAX-запросы автоматически, эту возможность можно отключить, задав JavaScript-переменную:

window.TracyAutoRefresh = false;

Для ручного наблюдения за конкретными AJAX-запросами добавьте HTTP-заголовок X-Tracy-Ajax со значением, которое возвращает Tracy.getAjaxHeader(). Вот пример его использования с функцией fetch:

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

Такой подход позволяет отлаживать AJAX-запросы выборочно.

Хранение данных

Tracy умеет показывать панели Tracy Bar и красные экраны для AJAX-запросов и перенаправлений. Tracy создаёт собственные сессии, хранит данные в собственных временных файлах и использует cookie tracy-session.

Tracy можно настроить и на использование нативной сессии PHP, которую нужно запустить до включения Tracy:

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

Если запуск сессии требует более сложной подготовки, вы можете запустить Tracy сразу (чтобы она могла обработать возникающие ошибки), а затем инициализировать обработчик сессии. Наконец, сообщите Tracy, что сессия готова к использованию, функцией dispatch():

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

// далее идёт инициализация сессии
// и запуск сессии
session_start();

Debugger::dispatch();

Функция setSessionStorage() существует начиная с версии 2.9; до этого Tracy всегда использовала нативную сессию PHP.

Собственный scrubber

Scrubber – это фильтр, не дающий утечь из дампов конфиденциальным данным, например паролям или учётным данным. Фильтр вызывается для каждого элемента выводимого массива или объекта и возвращает true, если значение конфиденциально. В этом случае вместо значения выводится *****.

// запрещает выводить значения ключей и свойств вроде `password`,
// `password_repeat`, `check_password`, `DATABASE_PASSWORD` и т. п.
$scrubber = function(string $key, $value, ?string $class): bool
{
	return preg_match('#password#i', $key) && $value !== null;
};

// применяем его ко всем дампам внутри BlueScreen
Tracy\Debugger::getBlueScreen()->scrubber = $scrubber;

Собственный логгер

Мы можем создать собственный логгер, который будет записывать ошибки и неперехваченные исключения, а также вызываться методом Tracy\Debugger::log(). Логгер должен реализовывать интерфейс Tracy\ILogger.

use Tracy\ILogger;

class SlackLogger implements ILogger
{
	public function log($value, $priority = ILogger::INFO)
	{
		// отправляет запрос в Slack
	}
}

А затем мы его включаем:

Tracy\Debugger::setLogger(new SlackLogger);

Если вы используете весь Nette Framework, задать его можно в конфигурационном файле NEON:

services:
	tracy.logger: SlackLogger

Интеграция с Monolog

Пакет Tracy предоставляет адаптер PSR-3, позволяющий интегрировать 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'); // запишет: [<TIMESTAMP>] main-channel.INFO: info [] []
Debugger::log('warning', Debugger::WARNING); // запишет: [<TIMESTAMP>] main-channel.WARNING: warning [] []

Интеграция с Sentry

Вы можете пересылать ошибки в сервис вроде Sentry, сохранив при этом собственное логирование Tracy. Идея в том, чтобы обернуть исходный логгер: новый логгер передаёт сообщение в Sentry, а затем делегирует предыдущему, так что запись в файлы и уведомления по почте продолжают работать.

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)
	{
		// отправляем в Sentry
		if ($severity = $this->getSeverity($level)) {
			$value instanceof \Throwable
				? \Sentry\captureException($value)
				: \Sentry\captureMessage((string) $value, $severity);
		}

		// сохраняем исходное логирование Tracy (файлы, почта)
		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,
		};
	}
}

Включается он так же, как любой другой собственный логгер:

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

В приложении на Nette зарегистрируйте его вместо этого как сервис tracy.logger:

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

nginx

Если Tracy не работает на nginx, он, вероятно, неверно настроен. Если там есть что-то вроде:

try_files $uri $uri/ /index.php;

замените это на:

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