HyderA
HyderA

Reputation: 21371

PHP: How to read a file live that is constantly being written to

I want to read a log file that is constantly being written to. It resides on the same server as the application. The catch is the file gets written to every few seconds, and I basically want to tail the file on the application in real-time.

Is this possible?

Upvotes: 19

Views: 19057

Answers (6)

iRaS
iRaS

Reputation: 2136

Yes, you need to sleep some time in the loop but you don't have to reopen the file. I was just looking for a similar problem. I wanted to read a file that might have been changed since last read.

The problem is that the resource has reached end of file (EOF). And does not continue to read. The solution is to reset the pointer with fseek($fh, ftell($fh)).

A complete program that waits for input in a text file might look like this one:

<?php

$fh = fopen('/var/log/system', 'r');
while (true) {
    $line = fgets($fh);
    if ($line !== false) {
      // show the line or send it via email or to a websocket..
    } else {
        // sleep for 0.1 seconds (or more?)
        usleep(0.1 * 1000000);
        fseek($fh, ftell($fh));
    }
}

log files and rotation

As log files grow to big they often get truncated or renamed. In most cases it is enough to check if the filesize is less than the current position of the pointer and either reset (start from beginning) or reopen the file (when the log rotation uses rename the file pointer is still open).

<?php

$path = '/var/log/nginx/access.log';
$fh = fopen($path, 'r');
while (true) {
  if (filesize($path) < ftell($fh)) {
    // reset the file handler (faster)
    fseek($fh);
    // reopen the file (safer; if reset is not working)
    fclose($fh);
    $fh = fopen($path, 'r');
  }
  // ...
}

If that is not enough (e. g. because the rotation is daily and sometimes the log file is empty) you could implement a reopen after the rotation (for example 1 minute after midnight).

Upvotes: 7

William Desportes
William Desportes

Reputation: 1701

<?php

$fp = fopen('/var/log/syslog', 'r');// Read only
while (true) {
    $line = stream_get_line($fp, 1024 * 1024, "\n");// Full line found ? (searches for a line break)
    if ($line === false) {
        usleep(100000);// 100ms
        continue;
    }
    echo 'line:' . $line . PHP_EOL;
}
// -- Code impossible to reach --
// fclose($fp);

Upvotes: 0

Artefacto
Artefacto

Reputation: 97815

You need to loop with sleep:

$file='/home/user/youfile.txt';
$lastpos = 0;
while (true) {
    usleep(300000); //0.3 s
    clearstatcache(false, $file);
    $len = filesize($file);
    if ($len < $lastpos) {
        //file deleted or reset
        $lastpos = $len;
    }
    elseif ($len > $lastpos) {
        $f = fopen($file, "rb");
        if ($f === false)
            die();
        fseek($f, $lastpos);
        while (!feof($f)) {
            $buffer = fread($f, 4096);
            echo $buffer;
            flush();
        }
        $lastpos = ftell($f);
        fclose($f);
    }
}

(tested.. it works)

Upvotes: 32

Miroslav Asenov
Miroslav Asenov

Reputation: 183

For example :

$log_file = '/tmp/test/log_file.log';

$f = fopen($log_file, 'a+');
$fr = fopen($log_file, 'r' );

for ( $i = 1; $i < 10; $i++ )
{
    fprintf($f, "Line: %u\n", $i);
    sleep(2);
    echo fread($fr, 1024) . "\n";
}

fclose($fr);
fclose($f);

//Or if you want use tail

$f = fopen($log_file, 'a+');

for ( $i = 1; $i < 10; $i++ )
{
    fprintf($f, "Line: %u\n", $i);
    sleep(2);
    $result = array();
    exec( 'tail -n 1 ' . $log_file, $result );
    echo "\n".$result[0];
}

fclose($f);

Upvotes: 1

Sergey Eremin
Sergey Eremin

Reputation: 11080

you can close the file handle when it is not used(once a portion of data has been written). or you can use a buffer to store the data and put it to the file only when it's full. this way you won't have the file open all the time.

if you want to get everything that is written to the file as soon as it is written there, you might need to extend the code, writing the data, so that it would output to other places too(screen, some variable, other file...)

Upvotes: 0

Andreas
Andreas

Reputation: 5335

Just an idea..

Did you think of using the *nix tail command? execute the command from php (with a param that will return a certain number of lines) and process the results in your php script.

Upvotes: -1

Related Questions