Рецепты
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;