ガイド

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 に出くわしたことはありませんか。これは、あなたのコードが HTTP のヘッダーを設定したりセッションを開始したりする前に、何か(紛れ込んだ空白、空行、ファイルの先頭の BOM)がブラウザへ送られたときに出ます。犯人を見つけるのは骨の折れる仕事です。

そこで Tracy\OutputDebugger が助けになります。プログラムのいちばん最初で有効にします。

Tracy\OutputDebugger::enable();

これはすべての出力を見張り、ページの終わりに、出力が送られたすべての場所を、ファイル、行、そしてエディタで開くリンクとともに並べます。ファイルの先頭のバイトオーダーマーク(BOM)も強調されます。目に見えないよくある原因だからです。

AJAX のリクエストをデバッグする

Tracy は jQuery やネイティブの fetch API で行われた AJAX のリクエストを自動的に捕まえます。これらのリクエストは Tracy バーの追加の行として表示されるので、AJAX のデバッグが簡単で快適になります。

AJAX のリクエストを自動的に捕まえたくないなら、JavaScript の変数を設定してこの機能を切れます。

window.TracyAutoRefresh = false;

特定の AJAX のリクエストだけを手で見張るには、HTTP ヘッダー X-Tracy-AjaxTracy.getAjaxHeader() が返す値を付けます。fetch 関数と一緒に使う例です。

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

このやり方なら AJAX のリクエストを選んでデバッグできます。

データの保管

Tracy は AJAX のリクエストやリダイレクトについても、Tracy バーのパネルや BlueScreen を表示できます。Tracy は自分でセッションを作り、データを自分の一時ファイルに保存し、tracy-session のクッキーを使います。

Tracy に PHP のネイティブのセッションを使わせることもできます。その場合、セッションは Tracy を有効にする前に開始しなければなりません。

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

セッションの開始にもっと込み入った準備が要る場合は、まず Tracy を(起きたエラーを扱えるように)すぐ立ち上げてから、セッションのハンドラを用意できます。最後に dispatch() 関数で、セッションが使えるようになったことを Tracy に伝えます。

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 との統合

Tracy 自身の記録を残しつつ、エラーを Sentryのようなサービスへ転送できます。考え方は、もとのロガーを包むことです。新しいロガーはメッセージを 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

nginx で Tracy が動かないなら、たいていは設定が間違っています。次のような記述があれば、

try_files $uri $uri/ /index.php;

これを次のように変えます。

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