Blinky's Lab
Posted on
Programming

PHP CLI Error Log Viewer

Author
PHP CLI Error Log Viewer

I have been doing quite a bit of PHP programming lately. One thing I have found is repeatedly having to check up on my PHP error log. You know the thing, you write some code, move down the page, write some more, up a bit, little more, down to the bottom and finish. There, all good, and.... HTTP 500. I have two options, go through all the code I have just written and look for that missing semicolon or bracket, or open the log, look at the last entry and go from there.

I have been doing a lot of the latter of late. Something doesn't work so look in the error log. Ooh, line 57.... I thought, wouldn't it be better if I had a live readout of the log. Just a window I can have open on my desktop and if any errors occur, they pop into the error log window. So I wrote a PHP CLI error log viewer.

This will show the last 200k tail and also any new log entries as they happen. In just one day programming it has helped enormously (because I'm a rubbish programmer). Really, it is worth it's weight in code to me. 😂 It updates as new lines are written. You can clear the log down and start over from a button. You can pause it when your code is so broke it it literally raining log lines. And not much else.

First thing to do is to get PHP to save/append it's error log in a location you know. Oh yeah, I forgot to mention, this can be used remotely if you are FTPing your files, or something else. This runs in the browser, so you can get to it anywhere. If live on the net, sort your own security. A PHP error log can expose details or even entry points into your network, so bear that in mind! Alright, back to the job at hand. We need to save the log in a place the webserver can get to it, so pop open your php.ini and either amend or add your logging method, such as:

; log_errors
log_errors = On
error_reporting = E_ALL
error_log = "C:\php\php_cli_errors.log"

This is on Windows so change the path to suit if using linux. Apache on windows (at least my apache on windows) can see the file in C:\php, so all good for me.

Once you have done that and confirm the error log is being created and appended in that location, drop this simple PHP script that AI wrote for me (you didn't think I wrote this did you? 😝 I'm a rubbish programmer, remember!), call it index.php (so you can www.mysite.com/php_cli_errors/ without extra page names) or whatever, but make sure that the $logFile variable at the top is set to exactly as you have PHP saving your logfile. So just create a blank .php file and drop this code in:

<?php
declare(strict_types=1);

$logFile = 'C:/php/php_cli_errors.log'; // <-- Change this to what your logfile is called and located
$maxInitialBytes = 200000; // 200 KB tail on first load

function json_out(array $data): never {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data);
    exit;
}

if (isset($_GET['action'])) {
    $action = $_GET['action'];

    if (!file_exists($logFile)) {
        touch($logFile);
    }

    if ($action === 'read') {
        $offset = max(0, (int)($_GET['offset'] ?? 0));
        $size = filesize($logFile) ?: 0;

        // Log was cleared/truncated.
        if ($offset > $size) {
            $offset = 0;
        }

        // First load: only read the end of a large file.
        if ($offset === 0 && $size > $maxInitialBytes) {
            $offset = $size - $maxInitialBytes;
        }

        $fh = fopen($logFile, 'rb');
        if (!$fh) {
            json_out(['ok' => false, 'error' => 'Could not open log file']);
        }

        fseek($fh, $offset);
        $data = stream_get_contents($fh);
        fclose($fh);

        json_out([
            'ok' => true,
            'content' => $data,
            'offset' => $size,
            'size' => $size,
        ]);
    }

    if ($action === 'clear') {
        file_put_contents($logFile, '');
        json_out(['ok' => true, 'offset' => 0, 'size' => 0]);
    }

    json_out(['ok' => false, 'error' => 'Unknown action']);
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PHP CLI Error Log</title>
<style>
    body {
        margin: 0;
        font-family: system-ui, sans-serif;
        background: #111;
        color: #eee;
    }
    header {
        padding: 12px 16px;
        background: #222;
        display: flex;
        gap: 12px;
        align-items: center;
    }
    button {
        padding: 8px 12px;
        cursor: pointer;
    }
    #status {
        color: #aaa;
        font-size: 14px;
    }
    #log {
        white-space: pre-wrap;
        overflow-wrap: anywhere;
        padding: 16px;
        height: calc(100vh - 58px);
        overflow-y: auto;
        box-sizing: border-box;
        font-family: Consolas, Monaco, monospace;
        font-size: 13px;
        line-height: 1.45;
    }
</style>
</head>
<body>

<header>
    <button id="clearBtn">Clear log</button>
    <button id="pauseBtn">Pause</button>
    <span id="status">Loading…</span>
</header>

<div id="log"></div>

<script>
let offset = 0;
let paused = false;
const logEl = document.getElementById('log');
const statusEl = document.getElementById('status');

async function readLog() {
    if (paused) return;

    try {
        const res = await fetch(`?action=read&offset=${offset}`, { cache: 'no-store' });
        const json = await res.json();

        if (!json.ok) throw new Error(json.error || 'Read failed');

        if (json.offset < offset) {
            logEl.textContent = '';
        }

        if (json.content) {
            const atBottom = logEl.scrollTop + logEl.clientHeight >= logEl.scrollHeight - 20;
            logEl.textContent += json.content;
            if (atBottom) logEl.scrollTop = logEl.scrollHeight;
        }

        offset = json.offset;
        statusEl.textContent = `Size: ${json.size.toLocaleString()} bytes`;
    } catch (err) {
        statusEl.textContent = `Error: ${err.message}`;
    }
}

document.getElementById('clearBtn').onclick = async () => {
    if (!confirm('Clear the PHP CLI error log completely?')) return;

    const res = await fetch('?action=clear', { method: 'POST', cache: 'no-store' });
    const json = await res.json();

    if (json.ok) {
        offset = 0;
        logEl.textContent = '';
        statusEl.textContent = 'Log cleared';
    }
};

document.getElementById('pauseBtn').onclick = e => {
    paused = !paused;
    e.target.textContent = paused ? 'Resume' : 'Pause';
};

readLog();
setInterval(readLog, 1500);
</script>

</body>
</html>

Then save and browse to the new live error log viewer, magic! And if it doesn't work, you know where your php log is now, so go and have a look and see what's broke. 🤣

On a very serious note, despite the shits and giggles, having your PHP error log file exposed to the internet is bad, like really bad. The errors can give insight to an opportunist hacker, or AI, big data, whoever, so do bear that in mind. It would be best to not have the file accessible by anyone bar yourself. Sort your own security out if leaving this live on the internet and don't blame me if something goes awry. If using this live on the net, close it off afterwards when you have finished using it. Other than that, enjoy!

Have at it and all the best!

Add Comment

* Required information
1000
Captcha Image
Powered by Commentics

Comments

No comments yet. Be the first!