Skip to content

Commit 6a21ea5

Browse files
committed
BlueScreen: added support for AI agents
1 parent 5f20283 commit 6a21ea5

File tree

13 files changed

+648
-1
lines changed

13 files changed

+648
-1
lines changed

src/Tracy/Bar/assets/bar.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
* This file is part of the Tracy (https://tracy.nette.org)
33
*/
44

5+
if (navigator.webdriver) {
6+
document.cookie = 'tracy-webdriver=1;path=/;SameSite=Lax';
7+
}
8+
59
let requestId = document.currentScript.dataset.id,
610
ajaxCounter = 1,
711
baseUrl = location.href.split('#')[0];

src/Tracy/BlueScreen/BlueScreen.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ public function render(\Throwable $exception): void
122122
}
123123

124124

125+
/**
126+
* Captures blue screen as plain text (markdown).
127+
*/
128+
public function renderAgent(\Throwable $exception): string
129+
{
130+
return Helpers::capture(fn() => $this->renderTemplate($exception, __DIR__ . '/dist/agent.phtml'));
131+
}
132+
133+
125134
/** @internal */
126135
public function renderToAjax(\Throwable $exception, DeferredContent $defer): void
127136
{
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
{use Tracy\Debugger}
2+
{use Tracy\Dumper}
3+
{use Tracy\Helpers}
4+
{varType Tracy\BlueScreen $blueScreen}
5+
{varType Throwable $exception}
6+
{varType bool $showEnvironment}
7+
{varType ?array{type: int, message: string, file: string, line: int} $lastError}
8+
{varType array<string, string> $httpHeaders}
9+
{varType string $source}
10+
This is an error page generated by Tracy (https://tracy.nette.org).
11+
12+
{define renderSource $file, $line}
13+
{do $srcLines = @file($file)}
14+
{exitIf !$srcLines}
15+
{do $start = (int) max(0, $line - 4)}
16+
{do $end = (int) min(count($srcLines), $line + 3)}
17+
18+
```php
19+
{foreach array_slice($srcLines, $start, $end - $start, true) as $i => $srcLine}
20+
{=sprintf('%s%4d | %s', ($i + 1 === $line) ? '' : ' ', $i + 1, rtrim($srcLine))}
21+
{/foreach}
22+
```
23+
{do $mapped = Debugger::mapSource($file, $line)}
24+
{exitIf !$mapped || !@is_file($mapped['file'])}
25+
{do $mLine = $mapped['line']}
26+
27+
Mapped source: {=$mapped['file'] . ($mLine ? ':' . $mLine : '')}
28+
{do $mappedLines = @file($mapped['file'])}
29+
{if $mappedLines && $mLine}
30+
{do $mStart = (int) max(0, $mLine - 4)}
31+
{do $mEnd = (int) min(count($mappedLines), $mLine + 3)}
32+
33+
```
34+
{foreach array_slice($mappedLines, $mStart, $mEnd - $mStart, true) as $i => $srcLine}
35+
{=sprintf('%s%4d | %s', ($i + 1 === $mLine) ? '' : ' ', $i + 1, rtrim($srcLine))}
36+
{/foreach}
37+
```
38+
{/if}
39+
{/define}
40+
41+
{foreach Helpers::getExceptionChain($exception) as $i => $ex}
42+
{do $title = $blueScreen->getExceptionTitle($ex)}
43+
{do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''}
44+
{if $i === 0}
45+
# {$title}: {$ex->getMessage()}{$code}
46+
{else}
47+
48+
## Caused by: {$title}: {$ex->getMessage()}{$code}
49+
{/if}
50+
51+
in {$ex->getFile()}:{$ex->getLine()}
52+
{include renderSource $ex->getFile(), $ex->getLine()}
53+
54+
{do $base = new \Exception}
55+
{if count(get_mangled_object_vars($ex)) > count(get_mangled_object_vars($base))}
56+
57+
## Exception Properties
58+
59+
{=Dumper::toText($ex, [Dumper::DEPTH => $blueScreen->maxDepth, Dumper::TRUNCATE => $blueScreen->maxLength, Dumper::ITEMS => $blueScreen->maxItems, Dumper::SCRUBBER => $blueScreen->scrubber, Dumper::KEYS_TO_HIDE => $blueScreen->keysToHide])}
60+
{/if}
61+
62+
{do $stack = $blueScreen->cleanStackTrace($ex->getTrace())}
63+
{if $stack}
64+
65+
## Stack Trace
66+
67+
{foreach $stack as $j => $row}
68+
{do $funcName = isset($row['class']) ? $row['class'] . ($row['type'] ?? '::') . $row['function'] : $row['function']}
69+
{do $location = isset($row['file']) ? $row['file'] . ':' . ($row['line'] ?? '?') : 'inner-code'}
70+
{do $inlineArgs = ''}
71+
{do $complexArgs = []}
72+
{if !empty($row['args'])}
73+
{try}
74+
{do $params = isset($row['class']) ? (new \ReflectionMethod($row['class'], $row['function']))->getParameters() : (new \ReflectionFunction($row['function']))->getParameters()}
75+
{rollback}
76+
{do $params = []}
77+
{/try}
78+
{do $allSimple = true}
79+
{do $argParts = []}
80+
{foreach $row['args'] as $k => $v}
81+
{do $name = isset($params[$k]) && !$params[$k]->isVariadic() ? '$' . $params[$k]->getName() : '#' . $k}
82+
{do $dump = rtrim(Dumper::toText($v, [Dumper::DEPTH => 2, Dumper::TRUNCATE => 100]))}
83+
{if str_contains($dump, "\n") || strlen($dump) > 50}
84+
{do $allSimple = false}
85+
{do $complexArgs[] = ' ' . $name . ' = ' . str_replace("\n", "\n ", $dump)}
86+
{else}
87+
{do $argParts[] = $name . ' = ' . $dump}
88+
{do $complexArgs[] = ' ' . $name . ' = ' . $dump}
89+
{/if}
90+
{/foreach}
91+
{if $allSimple}
92+
{do $inlineArgs = implode(', ', $argParts)}
93+
{/if}
94+
{/if}
95+
{if $inlineArgs}
96+
{=$j + 1}. {$funcName}({$inlineArgs})
97+
{else}
98+
{=$j + 1}. {$funcName}()
99+
{/if}
100+
in {$location}
101+
{if !$inlineArgs && $complexArgs}
102+
{foreach $complexArgs as $arg}
103+
{=$arg}
104+
{/foreach}
105+
{/if}
106+
{if isset($row['file'], $row['line']) && @is_file($row['file'])}
107+
{include renderSource $row['file'], $row['line']}
108+
{/if}
109+
110+
{/foreach}
111+
{/if}
112+
{/foreach}
113+
114+
{if $lastError}
115+
116+
## Last Muted Error
117+
118+
{=Helpers::errorTypeToString($lastError['type'])}: {$lastError['message']}
119+
in {$lastError['file']}:{=$lastError['line']}
120+
{if @is_file($lastError['file'])}
121+
{include renderSource $lastError['file'], $lastError['line']}
122+
{/if}
123+
{/if}
124+
125+
{if !Helpers::isCli() && isset($_SERVER['REQUEST_METHOD'])}
126+
127+
## HTTP Request
128+
129+
{$_SERVER['REQUEST_METHOD']} {$source}
130+
{if $httpHeaders}
131+
132+
### Headers
133+
134+
{foreach $httpHeaders as $k => $v}
135+
- {$k}: {$v}
136+
{/foreach}
137+
{/if}
138+
139+
{if !empty($_GET)}
140+
141+
### $_GET
142+
143+
{=Dumper::toText($_GET, [Dumper::DEPTH => 3, Dumper::TRUNCATE => 200])}
144+
{/if}
145+
146+
{if !empty($_POST)}
147+
148+
### $_POST
149+
150+
{=Dumper::toText($_POST, [Dumper::DEPTH => 3, Dumper::TRUNCATE => 200, Dumper::SCRUBBER => $blueScreen->scrubber, Dumper::KEYS_TO_HIDE => $blueScreen->keysToHide])}
151+
{/if}
152+
153+
{if !empty($_COOKIE)}
154+
155+
### $_COOKIE
156+
157+
{=Dumper::toText($_COOKIE, [Dumper::DEPTH => 3, Dumper::TRUNCATE => 200, Dumper::SCRUBBER => $blueScreen->scrubber, Dumper::KEYS_TO_HIDE => $blueScreen->keysToHide])}
158+
{/if}
159+
{/if}
160+
161+
{if $showEnvironment}
162+
163+
## Environment
164+
165+
| Key | Value |
166+
|-----|-------|
167+
| PHP | {=PHP_VERSION . ' (' . (PHP_ZTS ? 'TS' : 'NTS') . ')'} |
168+
{if isset($_SERVER['REQUEST_METHOD'])}
169+
| Method | {$_SERVER['REQUEST_METHOD']} |
170+
{/if}
171+
| Source | {$source} |
172+
{if isset($_SERVER['SERVER_SOFTWARE'])}
173+
| Server | {$_SERVER['SERVER_SOFTWARE']} |
174+
{/if}
175+
| Tracy | {=Debugger::Version} |
176+
{/if}

src/Tracy/BlueScreen/assets/page.latte

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
'use strict';
3838
{str_replace(['<!--', '</s'], ['<\!--', '<\/s'], $js)|noescape}
3939
Tracy.BlueScreen.init();
40+
41+
{if Helpers::isAgent()}
42+
document.title={json_encode('Tracy: ' . $title . ': ' . $exception->getMessage() . $code)|noescape};
43+
console.error({json_encode($blueScreen->renderAgent($exception))|noescape});
44+
{/if}
4045
</script>
4146
</body>
4247
</html>

0 commit comments

Comments
 (0)