PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘fseek’ tag

Was ist gerade auf meinem Server los?

with 4 comments

Ich möchte hier ein kleines PHP-Script vorstellen das ich mir vor einiger Zeit mal geschrieben habe um einen schnellen Überblick meines Servers zu bekommen. Ich möchte dazu die letzten Zeilen einiger Logdateien anzeigen lassen. Anstatt mich dazu via SSH zum Server zu verbinden und alle Dateien durchzuschauen (tail -f), habe ich hier eine kleine Admin-Webseite, die mir das ganze auf einen Blick zeigt, und zwar fast in Echtzeit (je nachdem wie man die Refresh-Rate einstellt).

So sieht das ganze dann aus:

Logdateien werden häufig sehr groß, mehrere hundert MB sind keine Seltenheit. Beim Auslesen der Dateien muss man also darauf achten, nicht die ganze Datei in den Speicher zu laden. Funktionen wie file_get_contents() oder file() etc. fallen schonmal als Lösung raus. Des Rätsels Lösung sind Streams bzw. Streamwrapper. Wir öffnen die Datei mit Hilfe der Funktion fopen(), und können uns dann innerhalb der Datei bewegen (fseek()) bzw. Zeichen lesen (fgets() bzw. fgetc())

Doch wie finden wir die Punkte, an der die letzten Zeilen beginnen? Ganz einfach, wie fangen ganz hinten an und tasten uns zurück. Immer wenn wir auf ein \n stossen haben wir eine vollständige Zeile gefunden und speichern diese in einem Array. Das machen wir so lange bis wir die Anzahl der gewünschten Zeilen haben und können diese dann ausgeben.

Ein Stolperstein könnte noch entstehen aufgrund von restriktiven PHP-Einstellungen. Häufig untersagt man seinen Apache-VHosts, aus ihren Document-Roots auszubrechen und aktiviert noch weitere Sicherheitsmaßnahmen. Damit dieses Script funktioniert muss zumindestens die OpenBaseDir Einstellung angepasst werden, ansonsten kommt das Script nicht an die entsprechenden Verzeichnisse falls sie außerhalb der erlaubten Verzeichnisse liegen.

Hier das Script. Es hat nur eine sehr einfache Authentifizierung eingebaut, und auch die Ausgabe ist nicht W3C konform, aber es funktioniert in allen Browsern und das reicht mir. Außerdem werden <iframe>s benutzt, um die einzelnen Logdateien anzuzeigen. Wer mehr Zeit investieren möchte kann das ganze natürlich auch mittels <div>s und AJAX umbauen.

<?php
authenticate();

$files = array(
	'/var/log/messages' => 15,
	'/var/log/phperrors.log' => 15,
	'/var/log/syslog' => 15,
	'/var/log/mail.log' => 15,
	'/var/log/auth.log' => 10,
	'/var/kunden/logs/phpgangsta-error.log' => 5,
);

if (isset($_GET['logfile']) && isset($files[$_GET['logfile']])) {
	$lines = readLastLinesOfFile($_GET['logfile'], $files[$_GET['logfile']]); ?>
	<html>
		<head>
			<meta http-equiv="refresh" content="5">
		</head>
		<body style="margin: 0">
			<div style="font-size: 0.6em; white-space: nowrap">
				Filesize: <?=round(filesize($_GET['logfile'])/1024/1024,2) ?> MB<br/>
			<?	foreach ($lines as $line) {
					echo "<nowrap>$line</nowrap><br/>\n";
				} ?>
			</div>
		</body>
	</html>
<? } else { ?>
		<html>
			<head>
			</head>
			<body>
			<?	foreach($files as $filename => $lines) {
					echo $filename; ?>
					<iframe src="tail.php?logfile=<?=urlencode($filename)?>" style="height: <?=$lines*13+15?>px; width: 100%;"></iframe>
			<?	} ?>
			</body>
		</html>
<? }

function readLastLinesOfFile($filePath, $lines = 10) {
    //global $fsize;
    $handle = fopen($filePath, "r");
    if (!$handle) {
    	return array();
    }
    $linecounter = $lines;
    $pos = -2;
    $beginning = false;
    $text = array();
    while ($linecounter > 0) {
        $t = " ";
        while ($t != "\n") {
            if(fseek($handle, $pos, SEEK_END) == -1) {
                $beginning = true;
                break;
            }
            $t = fgetc($handle);
            $pos--;
        }
        $linecounter--;
        if ($beginning) {
            rewind($handle);
        }
        $text[$lines-$linecounter-1] = str_replace(array("\r", "\n", "\t"), '', fgets($handle));
        if ($beginning) break;
    }
    fclose($handle);
    return array_reverse($text);
}

function authenticate() {
	if (!isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] != 'padmin' || $_SERVER['PHP_AUTH_PW'] != 'meinadminpwd') {
	    header('WWW-Authenticate: Basic realm="Mein Bereich!"');
	    header('HTTP/1.0 401 Unauthorized');
	    echo 'Tja, so nicht mein Freund!';
	    exit;
	}
} ?>

Sicherlich nicht der weltbeste Code, aber es erfüllt seit langer Zeit seinen Zweck und benötigt keine speziellen PHP-Extensions oder Betriebssysteme. Man könnte sich z.B. auch eine Lösung erstellen, wo mittels

system('tail -n 10 /var/log/syslog')

gearbeitet wird, aber das läuft eben nicht überall.

Sehr interessant sieht auch diese tail-Lösung mittels libevent unter PHP aus. Dazu muss man allerdings libevent und die PHP-extension libevent installieren, es läuft also nicht direkt auf jedem Server. Es dürfte allerdings deutlich performanter sein bei sehr großen Dateien würde ich tippen.

Written by Michael Kliewe

Februar 1st, 2010 at 9:32 am