[Patch] Log file handler: limit log size by number of lines (patch)
| From: | Trevor Burnham | Date: | Sat, 14 Oct 2006 01:43:02 +0000 |
| Subject: | [Patch] Log file handler: limit log size by number of lines (patch) | ||
| Groups: | php.pear.dev | ||
| Request: | Send a blank email to pear-dev+get-44510@lists.php.net to get a copy of this message | ||
The .diff doesn't seem to have successfully gone through on my
previous message; I've attached it with a .txt extension.
Trevor Burnham
? .DS_Store ? Log.diff ? Log/.DS_Store Index: Log/file.php =================================================================== RCS file: /repository/pear/Log/Log/file.php,v retrieving revision 1.45 diff -u -r1.45 file.php --- Log/file.php 11 Jan 2006 07:56:37 -0000 1.45 +++ Log/file.php 13 Oct 2006 23:22:50 -0000 @@ -34,6 +34,14 @@ var $_fp = false; /** + * Integer with the number of lines added to the log file (or, if not in + * append mode, the total number of lines in the log). + * @var integer + * @access private + */ + var $_linesAdded = 0; + + /** * Should new log entries be append to an existing log file, or should the * a new log file overwrite an existing one? * @var boolean @@ -78,9 +86,21 @@ * @access private */ var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Integer giving a number of lines to limit the log file size to. When + * the file is closed, enough old lines are deleted to reduce the file to + * this number of lines. The default value of 0 means no limit. Note that + * using this parameter reduces performance, since the log file must be + * read into memory each time it is closed and, if it exceeds the limit, + * must be completely re-written. + * @var integer + * @access private + */ + var $_lineLimit = 0; /** - * String containing the end-on-line character sequence. + * String containing the end-of-line character sequence. * @var string * @access private */ @@ -136,6 +156,10 @@ if (!empty($conf['timeFormat'])) { $this->_timeFormat = $conf['timeFormat']; } + + if (!empty($conf['lineLimit'])) { + $this->_lineLimit = $conf['lineLimit']; + } if (!empty($conf['eol'])) { $this->_eol = $conf['eol']; @@ -148,12 +172,12 @@ /** * Destructor + * + * Simply calls $this->close() */ function _Log_file() { - if ($this->_opened) { - $this->close(); - } + $this->close(); } /** @@ -213,7 +237,22 @@ $creating = !file_exists($this->_filename); /* Obtain a handle to the log file. */ - $this->_fp = fopen($this->_filename, ($this->_append) ? 'a' : 'w'); + if ($this->_append) { + if ($this->_lineLimit > 0) { + /* Create file manually, if needed, for r+ mode. */ + if ($creating) { + $this->_fp = fopen($this->_filename, 'w+'); + } else { + $this->_fp = fopen($this->_filename, 'r+'); + fseek($this->_fp, 0, SEEK_END); + } + } else { + $this->_fp = fopen($this->_filename, 'a'); + } + } else { + $this->_fp = fopen($this->_filename, + ($this->_lineLimit > 0) ? 'w+' : 'w'); + } /* We consider the file "opened" if we have a valid file pointer. */ $this->_opened = ($this->_fp !== false); @@ -230,17 +269,94 @@ /** * Closes the log file if it is open. * + * This function also calls reduceLog() if needed. + * * @access public */ function close() { - /* If the log file is open, close it. */ - if ($this->_opened && fclose($this->_fp)) { - $this->_opened = false; + if ($this->_opened) { + /* If the line limit is non-zero, enforce it. */ + if ($this->_lineLimit > 0) { + if ($this->_append) { + if ($this->_linesAdded > 0) { + // reduceLog() tests the file and rewrites if necessary + $this->reduceLog(); + $this->_linesAdded = 0; + } + } else { + if ($this->_linesAdded > $this->_lineLimit) { + $this->reduceLog(); + $this->_linesAdded = 0; + } + } + } + + /* Attempt to close the file. */ + if (fclose($this->_fp)) { + $this->_opened = false; + } } - + return ($this->_opened === false); } + + /** + * Reduces the log file to a given number of lines. + * + * This function is called by close() if it may be necessary. It may + * optionally be called at other times. + * + * @param integer logLines The number of lines to reduce the log file + * to; if not given, lineLimit is used. + * @return True if the file was reduced + * @access public + */ + function reduceLog($logLines = -1) + { + if ($this->_opened == false) { + return false; + } + + if ($logLines == -1) { + $logLines = $this->_lineLimit; + } + + /* Use the pointer location to determine the length of the file. */ + $fileLength = ftell($this->_fp); + + /* Read the file contents into a string, then parse it by line. */ + rewind($this->_fp); + $fileContents = fread($this->_fp, $fileLength); + $lineArray = explode($this->_eol, $fileContents); + $lineCount = count($lineArray); + + if ($lineCount > $logLines) { + /* Extract the last x lines from $lineArray, where x = lineLimit */ + $offset = -1 * $logLines - 1; + $lineArray = array_slice($lineArray, $offset); + $fileContents = implode($this->_eol, $lineArray); + + /* If locking is enabled, lock the file. */ + if ($this->_locking) { + flock($this->_fp, LOCK_EX); + } + + /* Truncate the file down to its final size, then rewrite it. */ + ftruncate($this->_fp, strlen($fileContents)); + rewind($this->_fp); + fwrite($this->_fp, $fileContents); + + /* If locking is enabled, unlock the file. */ + if ($this->_locking) { + flock($this->_fp, LOCK_UN); + } + + return true; + } else { + return false; + } + } /** * Flushes all pending data to the file handle. @@ -297,6 +413,9 @@ /* Write the log line to the log file. */ $success = (fwrite($this->_fp, $line) !== false); + if ($success) { + $this->_linesAdded++; + } /* Unlock the file now that we're finished writing to it. */ if ($this->_locking) {
? .DS_Store ? Log.diff ? Log/.DS_Store Index: Log/file.php =================================================================== RCS file: /repository/pear/Log/Log/file.php,v retrieving revision 1.45 diff -u -r1.45 file.php --- Log/file.php 11 Jan 2006 07:56:37 -0000 1.45 +++ Log/file.php 13 Oct 2006 23:22:50 -0000 @@ -34,6 +34,14 @@ var $_fp = false; /** + * Integer with the number of lines added to the log file (or, if not in + * append mode, the total number of lines in the log). + * @var integer + * @access private + */ + var $_linesAdded = 0; + + /** * Should new log entries be append to an existing log file, or should the * a new log file overwrite an existing one? * @var boolean @@ -78,9 +86,21 @@ * @access private */ var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Integer giving a number of lines to limit the log file size to. When + * the file is closed, enough old lines are deleted to reduce the file to + * this number of lines. The default value of 0 means no limit. Note that + * using this parameter reduces performance, since the log file must be + * read into memory each time it is closed and, if it exceeds the limit, + * must be completely re-written. + * @var integer + * @access private + */ + var $_lineLimit = 0; /** - * String containing the end-on-line character sequence. + * String containing the end-of-line character sequence. * @var string * @access private */ @@ -136,6 +156,10 @@ if (!empty($conf['timeFormat'])) { $this->_timeFormat = $conf['timeFormat']; } + + if (!empty($conf['lineLimit'])) { + $this->_lineLimit = $conf['lineLimit']; + } if (!empty($conf['eol'])) { $this->_eol = $conf['eol']; @@ -148,12 +172,12 @@ /** * Destructor + * + * Simply calls $this->close() */ function _Log_file() { - if ($this->_opened) { - $this->close(); - } + $this->close(); } /** @@ -213,7 +237,22 @@ $creating = !file_exists($this->_filename); /* Obtain a handle to the log file. */ - $this->_fp = fopen($this->_filename, ($this->_append) ? 'a' : 'w'); + if ($this->_append) { + if ($this->_lineLimit > 0) { + /* Create file manually, if needed, for r+ mode. */ + if ($creating) { + $this->_fp = fopen($this->_filename, 'w+'); + } else { + $this->_fp = fopen($this->_filename, 'r+'); + fseek($this->_fp, 0, SEEK_END); + } + } else { + $this->_fp = fopen($this->_filename, 'a'); + } + } else { + $this->_fp = fopen($this->_filename, + ($this->_lineLimit > 0) ? 'w+' : 'w'); + } /* We consider the file "opened" if we have a valid file pointer. */ $this->_opened = ($this->_fp !== false); @@ -230,17 +269,94 @@ /** * Closes the log file if it is open. * + * This function also calls reduceLog() if needed. + * * @access public */ function close() { - /* If the log file is open, close it. */ - if ($this->_opened && fclose($this->_fp)) { - $this->_opened = false; + if ($this->_opened) { + /* If the line limit is non-zero, enforce it. */ + if ($this->_lineLimit > 0) { + if ($this->_append) { + if ($this->_linesAdded > 0) { + // reduceLog() tests the file and rewrites if necessary + $this->reduceLog(); + $this->_linesAdded = 0; + } + } else { + if ($this->_linesAdded > $this->_lineLimit) { + $this->reduceLog(); + $this->_linesAdded = 0; + } + } + } + + /* Attempt to close the file. */ + if (fclose($this->_fp)) { + $this->_opened = false; + } } - + return ($this->_opened === false); } + + /** + * Reduces the log file to a given number of lines. + * + * This function is called by close() if it may be necessary. It may + * optionally be called at other times. + * + * @param integer logLines The number of lines to reduce the log file + * to; if not given, lineLimit is used. + * @return True if the file was reduced + * @access public + */ + function reduceLog($logLines = -1) + { + if ($this->_opened == false) { + return false; + } + + if ($logLines == -1) { + $logLines = $this->_lineLimit; + } + + /* Use the pointer location to determine the length of the file. */ + $fileLength = ftell($this->_fp); + + /* Read the file contents into a string, then parse it by line. */ + rewind($this->_fp); + $fileContents = fread($this->_fp, $fileLength); + $lineArray = explode($this->_eol, $fileContents); + $lineCount = count($lineArray); + + if ($lineCount > $logLines) { + /* Extract the last x lines from $lineArray, where x = lineLimit */ + $offset = -1 * $logLines - 1; + $lineArray = array_slice($lineArray, $offset); + $fileContents = implode($this->_eol, $lineArray); + + /* If locking is enabled, lock the file. */ + if ($this->_locking) { + flock($this->_fp, LOCK_EX); + } + + /* Truncate the file down to its final size, then rewrite it. */ + ftruncate($this->_fp, strlen($fileContents)); + rewind($this->_fp); + fwrite($this->_fp, $fileContents); + + /* If locking is enabled, unlock the file. */ + if ($this->_locking) { + flock($this->_fp, LOCK_UN); + } + + return true; + } else { + return false; + } + } /** * Flushes all pending data to the file handle. @@ -297,6 +413,9 @@ /* Write the log line to the log file. */ $success = (fwrite($this->_fp, $line) !== false); + if ($success) { + $this->_linesAdded++; + } /* Unlock the file now that we're finished writing to it. */ if ($this->_locking) {