Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3c1050e
Remove Tracy's asset-response Expires/Pragma headers from TracyDebugg…
adrianbj Oct 21, 2026
272287d
Restore upstream's BlueScreen footer emoji in tracy-2.12.x
adrianbj Oct 21, 2026
1b61438
Add TracyDebugger's bar JS through Debugger::$customJsFiles instead o…
adrianbj Oct 21, 2026
9c9cbab
Remove stale bar JS cache files when barJsFile() writes a new one
adrianbj Oct 21, 2026
069e2ac
Narrow Tracy's hover reset from TracyDebugger's main.js instead of pa…
adrianbj Oct 21, 2026
151df9c
Upgrade the tracy-2.12.x core to Tracy 2.12.2
adrianbj Oct 21, 2026
6068b58
Move dump locations below the dump from TracyDebugger's main.js inste…
adrianbj Oct 21, 2026
547687a
Skip Tracy's shutdown handler for the Console from TracyDebugger inst…
adrianbj Oct 21, 2026
91b30d8
Shorten the bar bundle's browser cache from 10 days to an hour
adrianbj Oct 22, 2026
c30eb71
Always add dumps-polling.js and exception-loader.js to the bar bundle
adrianbj Oct 22, 2026
cbe2265
Use !empty() for the PW Info panel links check that gates font-awesome
adrianbj Oct 22, 2026
819bc4a
Fix the Console error handler logging @-suppressed errors on PHP 8
adrianbj Oct 22, 2026
03a455a
Bump to 5.0.73.
adrianbj Oct 22, 2026
1492a43
Run TracyDebugger's bar setup from Tracy's init hooks instead of poll…
adrianbj Oct 22, 2026
eea2817
Keep barJsFile() from deleting a variant in use, and handle a failed …
adrianbj Oct 22, 2026
37ae9e7
Only build the bar JS file on ?_tracy_bar= requests
adrianbj Oct 22, 2026
83c5c66
Console: report fatal errors raised under @, and show @-suppressed er…
adrianbj Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 139 additions & 94 deletions TracyDebugger.module.php

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions includes/CodeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
set_exception_handler(__NAMESPACE__.'\tracyConsoleExceptionHandler');
}
if(TracyDebugger::getDataValue('use_php_session') === 1 || TracyDebugger::$tracyVersion == '2.7.x') {
Debugger::$disableShutdownHandler = true;
TracyDebugger::$skipTracyShutdownHandler = true;
}
register_shutdown_function(__NAMESPACE__.'\tracyConsoleShutdownHandler');

Expand Down Expand Up @@ -560,8 +560,10 @@ function ...") and, because the raw path never matches the compiled entry in the

// error handler function
function tracyConsoleErrorHandler($errno, $errstr, $errfile, $errline) {
// this prevents silenced(@) errors from being captured by this custom error handler
if(error_reporting() === 0) {
// this prevents silenced(@) errors from being captured by this custom error handler, unless Tracy's
// scream mode (forceScream) asks for them. Since PHP 8, @ lowers error_reporting() to the fatal
// levels instead of 0, so test the error's own level against it
if(!Debugger::$scream && !(error_reporting() & $errno)) {
// continue script execution, skipping standard PHP error handler
return true;
}
Expand Down Expand Up @@ -605,13 +607,9 @@ function tracyConsoleShutdownHandler() {
/* always release the lock first, regardless of error state, so the panel
stops reporting "Running..." the moment this request actually ends */
tracyConsoleReleasePidLock();
// this prevents silenced(@) errors from being captured by this custom error handler
if(error_reporting() === 0) {
// continue script execution, skipping standard PHP error handler
return true;
}

$lasterror = error_get_last();
// when Tracy's shutdown handler is skipped, TracyDebugger's earlier shutdown function has taken the error
$lasterror = TracyDebugger::$skipTracyShutdownHandler ? TracyDebugger::$consoleLastError : error_get_last();

// convert error constants to strings,
// otherwise the error's numerical value (http://php.net/manual/en/errorfunc.constants.php) is returned
Expand Down
9 changes: 4 additions & 5 deletions includes/TD.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,12 +324,11 @@ public static function publishConsoleChunk() {
}
$new = substr($buf, self::$consoleStreamOffset);
if($new === '' || $new === false) return;
/* The run installs tracyConsoleErrorHandler for its whole life, and that
handler's suppression guard is the pre-PHP-8 `error_reporting() === 0`
idiom — under PHP 8 `@` sets a non-zero mask instead, so a failed write
here would reach it and echo a red E_WARNING block into the buffer,
/* The run installs tracyConsoleErrorHandler for its whole life, and a
warning that reached it would echo a red E_WARNING block into the buffer,
i.e. into the delivered result. This side-channel must never be able to
alter canonical output, so silence it with a real handler swap. */
alter canonical output, so silence a failed write with a real handler
swap rather than relying on @ and that handler's suppression check. */
if(self::$consoleStreamOffset + strlen($new) > self::CONSOLE_STREAM_MAX_BYTES) {
set_error_handler(function() { return true; });
file_put_contents(
Expand Down
8 changes: 3 additions & 5 deletions scripts/dumps-polling.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,14 @@
}

function init() {
if(!document.getElementById("tracy-debug-bar")) {
window.requestAnimationFrame(init);
return;
}
// this script is in every bar bundle, so only poll when the Dumps Recorder panel is in this bar
if(!document.getElementById(recorderPanelId)) return;
// lastSeenId stays -1 until the first poll, which fully syncs the panel from
// the polled entries (replacing the server-rendered set) so nothing is duplicated
pollTimer = setTimeout(poll, pollInterval);
}

init();
window.tracyDebuggerOnBar(init);

function resetToMinAndPollSoon() {
pollInterval = minPollInterval;
Expand Down
3 changes: 2 additions & 1 deletion scripts/file-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ if(!tracyFileEditorLoader) {

initializeEditor: function() {
if(!document.getElementById("tfe_recently_opened")) {
window.requestAnimationFrame(tracyFileEditorLoader.initializeEditor);
// the panel's content isn't in the page until Tracy initializes the panel
window.tracyDebuggerOnPanelInit("tracy-debug-panel-ProcessWire-FileEditorPanel", tracyFileEditorLoader.initializeEditor);
}
else {
// populate recently opened files select, normalizing legacy entries with inconsistent
Expand Down
153 changes: 153 additions & 0 deletions scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,159 @@
}
})();

// Run code once Tracy has built the bar, or once a panel's content is in the page, rather than polling for
// their elements on every animation frame: a popup window or a page whose panel is never opened would
// otherwise poll for as long as it stays open
(function() {
var barCallbacks = [];
var panelCallbacks = {};

window.tracyDebuggerOnBar = function(fn) {
if(document.getElementById('tracy-debug-bar')) fn();
else barCallbacks.push(fn);
};

window.tracyDebuggerOnPanelInit = function(panelId, fn) {
(panelCallbacks[panelId] = panelCallbacks[panelId] || []).push(fn);
};

if(!window.Tracy || !Tracy.Debug || !Tracy.DebugPanel) return;

// callbacks run even when Tracy's init throws partway (e.g. on a corrupt panel position in localStorage)
var debugInit = Tracy.Debug.init;
Tracy.Debug.init = function() {
try {
return debugInit.apply(this, arguments);
}
finally {
if(document.getElementById('tracy-debug-bar')) barCallbacks.splice(0).forEach(function(fn) { fn(); });
}
};

var panelInit = Tracy.DebugPanel.prototype.init;
Tracy.DebugPanel.prototype.init = function() {
try {
return panelInit.apply(this, arguments);
}
finally {
var callbacks = this.elem && panelCallbacks[this.elem.id];
if(callbacks) callbacks.splice(0).forEach(function(fn) { fn(); });
}
};
})();

// Tracy's reset.css clears color and background on every hovered element in the bar, panels and
// BlueScreen ("tracy-div:not(a b) *:hover"), which beats many of our panel styles and the Ace
// editor theme (its background turns transparent while hovered). Narrow it to the direct children
// of tracy-div in each Tracy stylesheet, including those an AJAX BlueScreen adds later.
(function() {
function narrowHoverReset() {
document.querySelectorAll('style.tracy-debug').forEach(function(style) {
if(!style.sheet) return;
Array.prototype.forEach.call(style.sheet.cssRules, function(rule) {
if(rule.selectorText && /^tracy-div:not\(a b\) (\*)?:hover$/.test(rule.selectorText)) {
rule.selectorText = 'tracy-div:not(a b) > *:hover';
}
});
});
}
narrowHoverReset();

if(window.Tracy && Tracy.BlueScreen && Tracy.BlueScreen.loadAjax) {
var bsLoadAjax = Tracy.BlueScreen.loadAjax;
Tracy.BlueScreen.loadAjax = function() {
var result = bsLoadAjax.apply(this, arguments);
narrowHoverReset();
return result;
};
}
})();

// Tracy puts a dump's "file and line" link at the top of the dump, ending in a pin emoji. Move it
// below the dump, without the emoji, followed by the file's path as Tracy's Helpers::editorLink()
// shows it. Runs wherever Tracy initializes dumps: the page, panels, popups, AJAX BlueScreens,
// and our Console and Dumps Recorder.
(function() {
if(!window.Tracy || !Tracy.Dumper || !Tracy.Dumper.init) return;

// the file in the link's editor URL has the editor mapping applied (Debugger::$editorMapping),
// which is what editorLink() shows; find it by matching the URL against the editor URL
// templates TracyDebugger can set Debugger::$editor to
function mappedFile(href, file) {
var templates = window.TracyDebuggerEditorTemplates || [];
for(var i = 0; i < templates.length; i++) {
if(!templates[i] || templates[i].indexOf('%file') === -1) continue;
var pattern = templates[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace('%file', '([\\w.~%-]+)')
.replace(/%line/g, '\\d+')
.replace(/%action/g, '\\w*')
.replace(/%(search|replace)/g, '[^&]*');
var match = new RegExp('^' + pattern + '$').exec(href);
if(match) {
try {
return decodeURIComponent(match[1]);
}
catch(e) {}
}
}
return file;
}

function editorLink(href, file, line) {
var shown = mappedFile(href, file);
if(!href || href === '#') {
var span = document.createElement('span');
span.textContent = shown + ':' + line;
return span;
}
var separator = shown.indexOf('\\') !== -1 && shown.indexOf('/') === -1 ? '\\' : '/';
var parts = shown.replace(/\\/g, '/').split('/');
var name = parts.pop();
while(parts.length && name.length < 50) {
name = parts.pop() + '/' + name;
}
name = (parts.length ? '.../' : '') + name;
var slash = name.lastIndexOf('/');
var link = document.createElement('a');
link.setAttribute('href', href);
link.title = file + ':' + line;
link.className = 'tracy-editor';
link.appendChild(document.createTextNode((slash === -1 ? '.' + separator : name.slice(0, slash + 1).replace(/\//g, separator))));
var b = document.createElement('b');
b.textContent = name.slice(slash + 1);
link.appendChild(b);
link.appendChild(document.createTextNode(':' + line));
return link;
}

function moveDumpLocations(context) {
(context || document).querySelectorAll('pre.tracy-dump > a.tracy-dump-location').forEach(function(location) {
var match = /^in file ([\s\S]*) on line (\d+)/.exec(location.title);
if(!match) return;
var pre = location.parentNode;
var last = location.lastChild;
if(last && last.nodeType === 3) last.nodeValue = last.nodeValue.replace(/\s*📍\s*$/, '');

var container = document.createElement('div');
container.className = 'tracy-dump-location-container';
container.appendChild(location);
container.appendChild(document.createTextNode(' '));
var path = document.createElement('span');
path.className = 'tracy-dump-location tracy-dump-location-path';
path.appendChild(editorLink(location.getAttribute('href'), match[1], match[2]));
container.appendChild(path);
pre.parentNode.insertBefore(container, pre.nextSibling);
});
}

var dumperInit = Tracy.Dumper.init;
Tracy.Dumper.init = function(context) {
var result = dumperInit.apply(this, arguments);
moveDumpLocations(context);
return result;
};
})();

function tracyResizePanel(panel) {
panel = document.getElementById("tracy-debug-panel-ProcessWire-" + panel);
var tracyPanel = window.Tracy.Debug.panels[panel.id];
Expand Down
12 changes: 4 additions & 8 deletions tracy-2.12.x/composer.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
{
"name": "tracy/tracy",
"description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.",
"keywords": ["debug", "debugger", "nette", "profiler", "xdebug"],
"description": "😎 Tracy: the addictive PHP debugger. Clear error pages with full context, a debug bar, readable dumps, AJAX and CLI support, and logging with e-mail alerts in production.",
"keywords": ["tracy", "nette", "debugger", "debugging", "debug bar", "error handler", "dump", "var_dump", "logging", "whoops"],
"homepage": "https://tracy.nette.org",
"license": ["BSD-3-Clause"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": "8.2 - 8.5",
"php": "8.2 - 8.6",
"ext-session": "*",
"ext-json": "*"
},
Expand All @@ -27,7 +23,7 @@
"nette/tester": "^2.6",
"latte/latte": "^2.5 || ^3.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"phpstan/phpstan": "^2.1@stable",
"phpstan/phpstan": "^2.3@stable",
"phpstan/extension-installer": "^1.4@stable",
"nette/phpstan-rules": "^1.0"
},
Expand Down
79 changes: 79 additions & 0 deletions tracy-2.12.x/phpstan-stubs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php declare(strict_types=1);

/**
* Class declarations for PHPStan (see scanFiles in phpstan.neon), never loaded at runtime.
* Covers ext-ds 2.0 classes missing from PHPStan's bundled phpstorm-stubs (they cover only ext-ds 1.x).
*/

namespace Ds;

final class Seq implements \IteratorAggregate, \ArrayAccess, \Countable, \JsonSerializable
{
public const MIN_CAPACITY = 8;


public function getIterator(): \Iterator
{
throw new \LogicException;
}


public function offsetExists(mixed $offset): bool
{
throw new \LogicException;
}


public function offsetGet(mixed $offset): mixed
{
throw new \LogicException;
}


public function offsetSet(mixed $offset, mixed $value): void
{
throw new \LogicException;
}


public function offsetUnset(mixed $offset): void
{
throw new \LogicException;
}


public function count(): int
{
throw new \LogicException;
}


public function jsonSerialize(): mixed
{
throw new \LogicException;
}
}


final class Heap implements \IteratorAggregate, \Countable, \JsonSerializable
{
public const MIN_CAPACITY = 8;


public function getIterator(): \Iterator
{
throw new \LogicException;
}


public function count(): int
{
throw new \LogicException;
}


public function jsonSerialize(): mixed
{
throw new \LogicException;
}
}
2 changes: 1 addition & 1 deletion tracy-2.12.x/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ composer require tracy/tracy

Alternatively, you can download the whole package or [tracy.phar](https://github.com/nette/tracy/releases) file.

Tracy is compatible with PHP 8.2 to 8.5.
Tracy is compatible with PHP 8.2 to 8.6.

 <!---->

Expand Down
4 changes: 1 addition & 3 deletions tracy-2.12.x/src/Bridges/Nette/Bridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@

use Nette;
use Tracy;
use Tracy\BlueScreen;
use Tracy\Helpers;
use const ENT_IGNORE;
use Tracy\{BlueScreen, Helpers};


/**
Expand Down
9 changes: 8 additions & 1 deletion tracy-2.12.x/src/Bridges/Psr/PsrToTracyLoggerAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ public function __construct(
public function log(mixed $value, string $level = self::INFO): void
{
if ($value instanceof \Throwable) {
$message = get_debug_type($value) . ': ' . $value->getMessage() . ($value->getCode() ? ' #' . $value->getCode() : '') . ' in ' . $value->getFile() . ':' . $value->getLine();
$message = get_debug_type($value)
. ': '
. $value->getMessage()
. ($value->getCode() ? ' #' . $value->getCode() : '')
. ' in '
. $value->getFile()
. ':'
. $value->getLine();
$context = ['exception' => $value];

} elseif (!is_string($value)) {
Expand Down
Loading