tags:

views:

1395

answers:

7

I've been bumping into a problem. I have a log on a Linux box in which is written the output from several running processes. This file can get really big sometimes and I need to read the last line from that file.

The problem is this action will be called via an AJAX request pretty often and when the file size of that log gets over 5-6MB it's rather not good for the server. So I'm thinking I have to read the last line but not to read the whole file and pass through it or load it in RAM because that would just load to death my box.

Is there any optimization for this operation so that it run smooth and not harm the server or kill Apache?

Other option that I have is to exec('tail -n 1 /path/to/log') but it doesn't sound so good.

Later edit: I DO NOT want to put the file in RAM because it might get huge. fopen() is not an option.

A: 

Would it be possible to optimize this from the other side? If so, just let the logging application always log the line to a file while truncating it (i.e. > instead of >>)

Some optimization might be achieved by "guessing" though, just open the file and with the average log line width you could guess where the last line would be. Jump to that position with fseek and find the last line.

WoLpH
I don't have control over the application, unfortunately. And the second problem is that the log file must exist for each and every operation, so to log only one line it'd be a messy problem.
Bogdan Constantinescu
+3  A: 

You're looking for the fseek function. There are working examples of how to read the last line of a file in the comments section there.

Reinis I.
A: 

Your problem looks similar to this one

The best approach to avoid loading the whole file into memory seems to be:

$file = escapeshellarg($file); // for the security concious (should be everyone!)
$line = `tail -n 1 $file`;
James
No, it's not that simple. I can't load into RAM every time the file. It could go to 10-20MB easily and the request will be made each second for each client. Not a solution, cause Apache would crash.
Bogdan Constantinescu
The `tail -n 1 /path/to/log` option is wrote in the question body...not very enjoyable, though
Bogdan Constantinescu
There is the slightly shorter option if you prefer:sed -n /path/to/log
James
+5  A: 

Use fseek. You seek to the last position and seek it backward (use ftell to tell the current position) until you find a '\n'.


$fp = fopen(".....");
fseek($fp, -1, SEEK_END); 
$pos = ftell($fp);
$LastLine = ""
// Loop backword util '\n' is found.
while(($C = fgetc($fp)) != '\n') {
    $LastLine = $C.$LastLine;
    fseek($fp, $pos--);
}

NOTE: I've not tested. You may need some adjustment.

:-D

NawaMan
+7  A: 

This should work:

$line = '';

$f = fopen('data.txt', 'r');
$cursor = -1;

fseek($f, $cursor, SEEK_END);
$char = fgetc($f);

/**
 * Trim trailing newline chars of the file
 */
while ($char === "\n" || $char === "\r") {
    fseek($f, $cursor--, SEEK_END);
    $char = fgetc($f);
}

/**
 * Read until the start of file or first newline char
 */
while ($char !== false && $char !== "\n" && $char !== "\r") {
    /**
     * Prepend the new char
     */
    $line = $char . $line;
    fseek($f, $cursor--, SEEK_END);
    $char = fgetc($f);
}

echo $line;
Ionuț G. Stan
Ionut, thank you, but read carefully next time. I don't want to read the file because it's large and I don't want it in RAM.
Bogdan Constantinescu
What do you mean you don't want to read the file? I'm not reading the whole file in memory. I just open a kind of a pointer to it, then seek it char by char. This is the most efficient way to work with large files.
Ionuț G. Stan
`fopen()` does not act like `file_get_contents()`
Ionuț G. Stan
fopen does not load the file in memory, it just creates a file descriptor (a pointer).
Wadih M.
+3  A: 

If you know the upper bound of line length you could do something like this.

$maxLength = 1024;
$fp = fopen('somefile.txt', 'r');
fseek($fp, -$maxLength , SEEK_END); 
$fewLines = explode("\n", fgets($fp, $maxLength));
$lastLine = $fewLines[count($fewLines) - 1];

In response to the edit: fopen just acquires a handle to the file (i.e. make sure it exists, process has permission, lets os know a process is using the file, etc...). In this example only 1024 characters from the file will be read into memory.

Lawrence Barsanti
A: 

I would use file() that reads the file into an array, reverse the array and get the first element or pop the array:

$last_line = array_pop(file($filename));

If you want performance try opening the file and using the file pointer to navigate into it.

andreas
He asked for a way NOT to read the file into memory. If you think that it doesn't matter, please explain why.
gnud