Skip to content

Add Error Handling and Polling Improvements for Log Streaming #269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 29 additions & 1 deletion var/www/lib/functions-ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -828,4 +828,32 @@ function componentFeatureRepoBrancheStatus($fb_project, $cherry_commits_display
$content.='</span></a>';
return $content;
}
?>

/*
* Convert ANSI color codes to HTML
*
* @param string $text The text with ANSI codes
* @return string The text with HTML tags
*/
function ansi_to_html($text) {
// Escape HTML characters first
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');

// ANSI code to HTML map (common for logback + tomcat)
$ansiMap = [
"\033[0;39m" => '</span>',
"\033[0m" => '</span>',
"\033[1m" => '<span style="font-weight: bold;">',
"\033[31m" => '<span style="color: red;">', // ERROR
"\033[33m" => '<span style="color: orange;">', // WARN
"\033[34m" => '<span style="color: blue;">', // INFO
"\033[32m" => '<span style="color: green;">', // Logger/Context
"\033[36m" => '<span style="color: #5555aa;">', // Thread (e.g., <main>)
"\033[90m" => '<span style="color: gray;">', // DEBUG
];

// Replace known ANSI sequences
return str_replace(array_keys($ansiMap), array_values($ansiMap), $text);
}

?>
39 changes: 39 additions & 0 deletions var/www/logStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
require_once(dirname(__FILE__) . '/lib/functions.php');
require_once(dirname(__FILE__) . '/lib/functions-ui.php');

$file_path = $_GET['file'];
$log_type = $_GET['type'];
$offset = isset($_GET['offset']) ? intval($_GET['offset']) : 0;

header('Content-Type: application/json');

if (!isAuthorizedToReadFile($log_type, $file_path)) {
echo json_encode(['error' => 'Unauthorized']);
exit;
}

if (!file_exists($file_path)) {
echo json_encode(['error' => 'File not found']);
exit;
}

$filesize = filesize($file_path);

if ($filesize > $offset) {
$handle = fopen($file_path, "rb");
fseek($handle, $offset);
$content = fread($handle, $filesize - $offset);
fclose($handle);

echo json_encode([
'offset' => $filesize,
'content' => ansi_to_html($content)
]);
} else {
// Nothing new to read — just return current offset and empty content
echo json_encode([
'offset' => $filesize,
'content' => ''
]);
}
87 changes: 71 additions & 16 deletions var/www/logs.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@
?>
<html>
<head>
<?= pageHeader("log visualization"); ?>
<?= pageHeader("log visualization"); ?>
<style>
#log-output {
background-color: #f8f8f8;
border: 1px solid #ccc;
padding: 10px;
height: 600px;
overflow-y: scroll;
white-space: pre-wrap;
font-family: monospace;
}
</style>
</head>
<body>
<?php pageTracker(); ?>
Expand All @@ -20,26 +31,70 @@
<div class="row-fluid">
<div class="span12">
<?php
// Read file only if the type of file is ok.
if (isAuthorizedToReadFile($log_type, $file_path) == true){
?>
// Read file only if the type of file is ok.
if (isAuthorizedToReadFile($log_type, $file_path) == true){
?>
<div class="instructions">
Download file (<?php printf(human_filesize(filesize($file_path),0)); ?>) : <a target="_blank" href="./logsDownload.php?type=<?=$log_type?>&file=<?=$file_path?>"><?=$file_path?></a>
Download file (<?= human_filesize(filesize($file_path), 0); ?>) :
<a target="_blank" href="./logsDownload.php?type=<?= $log_type ?>&file=<?= $file_path ?>"><?= $file_path ?></a>
</div>
<hr/>
<?php
if (isFileTooLargeToBeViewed($file_path)){
printf("<span style=\"color:red\"><strong>This file is too large to be viewed. Please download it.</strong></span>");
} else {
printf("<pre>");
$data = file_get_contents($file_path);
echo htmlspecialchars($data, ENT_NOQUOTES, 'UTF-8');
printf("</pre>");
}
<?php
if (isFileTooLargeToBeViewed($file_path)){
echo "<span style=\"color:red\"><strong>This file is too large to be viewed. Please download it.</strong></span>";
} else {
printf("<span style=\"color:red\"><strong>Not authorized to read this file.</strong></span>");
?>
<pre id="log-output">Loading logs...</pre>
<script>
let offset = 0;
let polling = true;
const file = "<?= htmlspecialchars($file_path, ENT_QUOTES) ?>";
const type = "<?= htmlspecialchars($log_type, ENT_QUOTES) ?>";

function showError(msg) {
const pre = document.getElementById('log-output');
pre.innerHTML += `\n<span style="color: red; font-weight: bold;">[ERROR]</span> ${msg}`;
pre.scrollTop = pre.scrollHeight;
}

async function fetchLogs() {
if (!polling) return;

try {
const response = await fetch(`logStream.php?file=${encodeURIComponent(file)}&type=${encodeURIComponent(type)}&offset=${offset}`);
if (!response.ok) {
throw new Error(`Server error ${response.status}`);
}

const data = await response.json();

if (data.error) {
showError(data.error);
polling = false; // Stop polling
return;
}

if (data.content) {
const pre = document.getElementById('log-output');
offset = data.offset;
pre.innerHTML += data.content;
pre.scrollTop = pre.scrollHeight;
}

} catch (err) {
showError("Failed to fetch logs: " + err.message);
polling = false; // Stop polling on network/server error
}
}

setInterval(fetchLogs, 2000);
</script>
<?php
}
?>
} else {
echo "<span style=\"color:red\"><strong>Not authorized to read this file.</strong></span>";
}
?>
</div>
</div>
</div>
Expand Down