20, "wing" => "Building", "title" => "Errors you can read", "promise" => "An app's error log is visible to its creator without a shell.", "block" => "PHP warnings and job output land in RC_FACTS/log.txt within a minute, readable by the app and shown on the creator's dashboard.", "show" => function (?array $me): string { $l = error_lines(); return '
It appears below within a minute.
' . '' . h($l ? implode("\n", $l) : "no deliberate errors in the log yet") . "";
},
"api" => function (string $do, ?array $me, array $in, bool $post): ?array {
if ($do !== "error" || !$post) return null;
$mark = bin2hex(random_bytes(4));
trigger_error(ERROR_MARK . ", " . env("RC_ENV") . ", $mark", E_USER_WARNING);
return ["made" => $mark];
},
// Each run makes an error, and looks for the one the previous run made (root copies the log
// every minute, and the check runs every five).
"check" => function (): array {
$prev = meta("error_mark");
[, , $j] = (new Browser())->post("https://" . env("RC_HOST") . "/api/20/error");
if (empty($j["made"])) return [false, "the \"make an error\" button did not answer"];
meta_set("error_mark", $j["made"] . "@" . time());
if (!$prev) return [false, "made the first error; the next check looks for it in the log"];
[$mark, $at] = explode("@", $prev);
$found = (bool)preg_grep("/" . preg_quote($mark, "/") . "/", error_lines());
if (!$found && time() - (int)$at < 70) return [true, "made an error; the previous one is too recent to look for"];
if (!$found) return [false, "the error made " . intdiv(time() - (int)$at, 60) . " minutes ago is not in RC_FACTS/log.txt"];
return [true, "the error made " . intdiv(time() - (int)$at, 60) . " minutes ago is in RC_FACTS/log.txt"];
},
"script" => <<<'JS'
document.getElementById("err")?.addEventListener("click", async () => {
const j = await zoo.call("/api/20/error", {});
document.getElementById("err-out").textContent = j.error || ("Made error " + j.made + ". Reload in a minute to see it below.");
});
JS,
];