<?php
/*
 * Sample code to demonstrate how to pull the last N lines from a (large)
 * file efficiently.
 *
 * Open Source Code:   If you use this code on your site for public
 * access (i.e. on the Internet) then you must attribute the author and
 * source web site:
 *    http://www.pgregg.com/projects/php/code/tail-10.php
 *    http://www.pgregg.com/projects/php/code/tail-10.phps
 *
 * (c) Paul Gregg, 2008
 * 11 January 2008
 * http://www.pgregg.com/projects/
 *
 */


if (isset($_SERVER['REMOTE_ADDR'])) header('Content-Type: text/plain');

## Read the last 10 lines from a file
## Assume max line length is 255 - so we use up 255 * 10 bytes at most
## reading even the largest files

$linecount  10;  // Number of lines we want to read
$linelength 160// Apache's logs are typically ~200+ chars
// I've set this to < 200 to show the dynamic nature of the algorithm
// offset correction.
$file '/usr/local/apache2/logs/access_log.demo';
$fsize filesize($file);

// check if file is smaller than possible max lines
$offset = ($linecount+1) * $linelength;
if (
$offset $fsize$offset $fsize;

$fp fopen($file'r');
if (
$fp === false) exit;

$lines = array(); // array to store the lines we read.

$readloop true;
while(
$readloop) {
  
// we will finish reading when we have read $linecount lines, or the file
  // just doesn't have $linecount lines

  // seek to $offset bytes from the end of the file
  
fseek($fp$offsetSEEK_END);

  
// discard the first line as it won't be a complete line
  // unless we're right at the start of the file
  
if ($offset != $fsizefgets($fp);

  
// tally of the number of bytes in each line we read
  
$linesize 0;

  
// read from here till the end of the file and remember each line
  
while($line fgets($fp)) {
    
array_push($lines$line);
    
$linesize += strlen($line); // total up the char count

    // if we've been able to get more lines than we need
    // lose the first entry in the queue
    // Logically we should decrement $linesize too, but if we
    // hit the magic number of lines, we are never going to use it
    
if (count($lines) > $linecountarray_shift($lines);
  }

  
// We have now read all the lines from $offset until the end of the file
  
if (count($lines) == $linecount) {
    
// perfect - have enough lines, can exit the loop
    
$readloop false;
  } elseif (
$offset >= $fsize) {
    
// file is too small - nothing more we can do, we must exit the loop
    
$readloop false;
  } elseif (
count($lines) < $linecount) {
    
// try again with a bigger offset
    
$offset intval($offset 1.1);  // increase offset 10%
    // but also work out what the offset could be if based on the lines we saw
    
$offset2 intval($linesize/count($lines) * ($linecount+1));
    
// and if it is larger, then use that one instead (self-tuning)
    
if ($offset2 $offset$offset $offset2;
    
// Also remember we can't seek back past the start of the file
    
if ($offset $fsize$offset $fsize;
    echo 
'Trying with a bigger offset: '$offset"\n";
    
// and reset
    
$lines = array();
  }
}

// Let's have a look at the lines we read.
print_r($lines);

?>