710 lines
19 KiB
PHP
710 lines
19 KiB
PHP
|
<?php namespace ProcessWire;
|
|||
|
|
|||
|
/**
|
|||
|
* ProcessWire FileLog
|
|||
|
*
|
|||
|
* Creates and maintains a text-based log file.
|
|||
|
*
|
|||
|
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
|||
|
* https://processwire.com
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
class FileLog extends Wire {
|
|||
|
|
|||
|
/**
|
|||
|
* Default size of chunks used for reading from logs
|
|||
|
*
|
|||
|
*/
|
|||
|
const defaultChunkSize = 12288;
|
|||
|
|
|||
|
/**
|
|||
|
* Debug mode used during development of this class
|
|||
|
*
|
|||
|
*/
|
|||
|
const debug = false;
|
|||
|
|
|||
|
/**
|
|||
|
* Chunk size used when reading from logs and not overridden
|
|||
|
*
|
|||
|
* @var int
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $chunkSize = self::defaultChunkSize;
|
|||
|
|
|||
|
/**
|
|||
|
* Full path to log file or false when not yet set
|
|||
|
*
|
|||
|
* @var bool|string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $logFilename = false;
|
|||
|
|
|||
|
/**
|
|||
|
* Log items saved during this request where array keys are md5 hash of log entries and values ignored
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $itemsLogged = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Delimiter used in log entries
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $delimeter = "\t";
|
|||
|
|
|||
|
/**
|
|||
|
* Maximum allowed line length for a single log line
|
|||
|
*
|
|||
|
* @var int
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $maxLineLength = 8192;
|
|||
|
|
|||
|
/**
|
|||
|
* File extension used for log files
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $fileExtension = 'txt';
|
|||
|
|
|||
|
/**
|
|||
|
* Path where log files are stored
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $path = '';
|
|||
|
|
|||
|
/**
|
|||
|
* Construct the FileLog
|
|||
|
*
|
|||
|
* @param string $path Path where the log will be stored (path should have trailing slash)
|
|||
|
* This may optionally include the filename if you intend to leave the second param blank.
|
|||
|
* @param string $identifier Basename for the log file, not including the extension.
|
|||
|
*
|
|||
|
*/
|
|||
|
public function __construct($path, $identifier = '') {
|
|||
|
|
|||
|
if($identifier) {
|
|||
|
$path = rtrim($path, '/') . '/';
|
|||
|
$this->logFilename = "$path$identifier.{$this->fileExtension}";
|
|||
|
} else {
|
|||
|
$this->logFilename = $path;
|
|||
|
$path = dirname($path) . '/';
|
|||
|
}
|
|||
|
$this->path = $path;
|
|||
|
if(!is_dir($path)) $this->wire('files')->mkdir($path);
|
|||
|
}
|
|||
|
|
|||
|
public function __get($key) {
|
|||
|
if($key == 'delimiter') return $this->delimeter; // @todo learn how to spell
|
|||
|
return parent::__get($key);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Clean a string for use in a log file entry
|
|||
|
*
|
|||
|
* @param $str
|
|||
|
* @return mixed|string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function cleanStr($str) {
|
|||
|
$str = str_replace(array("\r\n", "\r", "\n"), ' ', trim($str));
|
|||
|
if(strlen($str) > $this->maxLineLength) $str = substr($str, 0, $this->maxLineLength);
|
|||
|
if(strpos($str, ' ^+') !== false) $str = str_replace(' ^=', ' ^ +', $str); // disallowed sequence
|
|||
|
return $str;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Save the given log entry string
|
|||
|
*
|
|||
|
* @param string $str
|
|||
|
* @param array $options options to modify behavior (Added 3.0.143)
|
|||
|
* - `allowDups` (bool): Allow duplicating same log entry in same runtime/request? (default=true)
|
|||
|
* - `mergeDups` (int): Merge previous duplicate entries that also appear near end of file?
|
|||
|
* To enable, specify int for quantity of bytes to consider from EOF, value of 1024 or higher (default=0, disabled)
|
|||
|
* - `maxTries` (int): If log entry fails to save, maximum times to re-try (default=20)
|
|||
|
* - `maxTriesDelay` (int): Micro seconds (millionths of a second) to delay between re-tries (default=2000)
|
|||
|
* @return bool Success state: true if log written, false if not.
|
|||
|
*
|
|||
|
*/
|
|||
|
public function save($str, array $options = array()) {
|
|||
|
|
|||
|
$defaults = array(
|
|||
|
'mergeDups' => 0,
|
|||
|
'allowDups' => true,
|
|||
|
'maxTries' => 20,
|
|||
|
'maxTriesDelay' => 2000,
|
|||
|
);
|
|||
|
|
|||
|
if(!$this->logFilename) return false;
|
|||
|
|
|||
|
$options = array_merge($defaults, $options);
|
|||
|
$hash = md5($str);
|
|||
|
$ts = date("Y-m-d H:i:s");
|
|||
|
$str = $this->cleanStr($str);
|
|||
|
$line = $this->delimeter . $str; // log entry, excluding timestamp
|
|||
|
$hasLock = false; // becomes true when lock obtained
|
|||
|
$fp = false; // becomes resource when file is open
|
|||
|
|
|||
|
// if we've already logged this during this instance, then don't do it again
|
|||
|
if(!$options['allowDups'] && isset($this->itemsLogged[$hash])) return true;
|
|||
|
|
|||
|
// determine write mode
|
|||
|
$mode = file_exists($this->logFilename) ? 'a' : 'w';
|
|||
|
if($mode === 'a' && $options['mergeDups']) $mode = 'r+';
|
|||
|
|
|||
|
// open the log file
|
|||
|
for($tries = 0; $tries <= $options['maxTries']; $tries++) {
|
|||
|
$fp = fopen($this->logFilename, $mode);
|
|||
|
if($fp) break;
|
|||
|
// if unable to open for reading/writing, see if we can open for append instead
|
|||
|
if($mode === 'r+' && $tries > ($options['maxTries'] / 2)) $mode = 'a';
|
|||
|
usleep($options['maxTriesDelay']);
|
|||
|
}
|
|||
|
|
|||
|
// if unable to open, exit now
|
|||
|
if(!$fp) return false;
|
|||
|
|
|||
|
// obtain a lock
|
|||
|
for($tries = 0; $tries <= $options['maxTries']; $tries++) {
|
|||
|
$hasLock = flock($fp, LOCK_EX);
|
|||
|
if($hasLock) break;
|
|||
|
usleep($options['maxTriesDelay']);
|
|||
|
}
|
|||
|
|
|||
|
// if unable to obtain a lock, we cannot write to the log
|
|||
|
if(!$hasLock) {
|
|||
|
fclose($fp);
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
// if opened for reading and writing, merge duplicates of $line
|
|||
|
if($mode === 'r+' && $options['mergeDups']) {
|
|||
|
// do not repeat the same log entry in the same chunk
|
|||
|
$chunkSize = (int) $options['mergeDups'];
|
|||
|
if($chunkSize < 1024) $chunkSize = 1024;
|
|||
|
fseek($fp, -1 * $chunkSize, SEEK_END);
|
|||
|
$chunk = fread($fp, $chunkSize);
|
|||
|
// check if our log line already appears in the immediate earlier chunk
|
|||
|
if(strpos($chunk, $line) !== false) {
|
|||
|
// this log entry already appears 1+ times within the last chunk of the file
|
|||
|
// remove the duplicates and replace the chunk
|
|||
|
$chunkLength = strlen($chunk);
|
|||
|
$this->removeLineFromChunk($line, $chunk, $chunkSize);
|
|||
|
fseek($fp, 0, SEEK_END);
|
|||
|
$oldLength = ftell($fp);
|
|||
|
$newLength = $chunkLength > $oldLength ? $oldLength - $chunkLength : 0;
|
|||
|
ftruncate($fp, $newLength);
|
|||
|
fseek($fp, 0, SEEK_END);
|
|||
|
fwrite($fp, $chunk);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// already at EOF because we are appending or creating
|
|||
|
}
|
|||
|
|
|||
|
// add the log line
|
|||
|
$result = fwrite($fp, "$ts$line\n");
|
|||
|
|
|||
|
// release the lock and close the file
|
|||
|
flock($fp, LOCK_UN);
|
|||
|
fclose($fp);
|
|||
|
|
|||
|
if($result && !$options['allowDups']) $this->itemsLogged[$hash] = true;
|
|||
|
|
|||
|
// if we were creating the file, make sure it has the right permission
|
|||
|
if($mode === 'w') {
|
|||
|
$files = $this->wire('files'); /** @var WireFileTools $files */
|
|||
|
$files->chmod($this->logFilename);
|
|||
|
}
|
|||
|
|
|||
|
return (int) $result > 0;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Remove given $line from $chunk and add counter to end of $line indicating quantity that was removed
|
|||
|
*
|
|||
|
* @param string $line
|
|||
|
* @param string $chunk
|
|||
|
* @param int $chunkSize
|
|||
|
* @since 3.0.143
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function removeLineFromChunk(&$line, &$chunk, $chunkSize) {
|
|||
|
|
|||
|
$qty = 0;
|
|||
|
$chunkLines = explode("\n", $chunk);
|
|||
|
|
|||
|
foreach($chunkLines as $key => $chunkLine) {
|
|||
|
|
|||
|
$x = 1;
|
|||
|
if($key === 0 && strlen($chunk) >= $chunkSize) continue; // skip first line since it’s likely a partial line
|
|||
|
|
|||
|
// check if line appears in this chunk line
|
|||
|
if(strpos($chunkLine, $line) === false) continue;
|
|||
|
|
|||
|
// check if line also indicates a previous quantity that we should add to our quantity
|
|||
|
if(strpos($chunkLine, ' ^+') !== false) {
|
|||
|
list($chunkLine, $n) = explode(' ^+', $chunkLine, 2);
|
|||
|
if(ctype_digit($n)) $x += (int) $n;
|
|||
|
}
|
|||
|
|
|||
|
// verify that these are the same line
|
|||
|
if(strpos(trim($chunkLine) . "\n", trim($line) . "\n") === false) continue;
|
|||
|
|
|||
|
// remove the line
|
|||
|
unset($chunkLines[$key]);
|
|||
|
|
|||
|
// update the quantity
|
|||
|
$qty += $x;
|
|||
|
}
|
|||
|
|
|||
|
if($qty) {
|
|||
|
// append quantity to line, i.e. “^+2” indicating 2 more indentical lines were above
|
|||
|
$chunk = implode("\n", array_values($chunkLines));
|
|||
|
$line .= " ^+$qty";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public function size() {
|
|||
|
return filesize($this->logFilename);
|
|||
|
}
|
|||
|
|
|||
|
public function filename() {
|
|||
|
return basename($this->logFilename);
|
|||
|
}
|
|||
|
|
|||
|
public function pathname() {
|
|||
|
return $this->logFilename;
|
|||
|
}
|
|||
|
|
|||
|
public function mtime() {
|
|||
|
return filemtime($this->logFilename);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get lines from the end of a file based on chunk size (deprecated)
|
|||
|
*
|
|||
|
* @param int $chunkSize
|
|||
|
* @param int $chunkNum
|
|||
|
* @return array
|
|||
|
* @deprecated Use find() instead
|
|||
|
*
|
|||
|
*/
|
|||
|
public function get($chunkSize = 0, $chunkNum = 1) {
|
|||
|
return $this->getChunkArray($chunkNum, $chunkSize);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get lines from the end of a file based on chunk size
|
|||
|
*
|
|||
|
* @param int $chunkSize
|
|||
|
* @param int $chunkNum
|
|||
|
* @param bool $reverse
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getChunkArray($chunkNum = 1, $chunkSize = 0, $reverse = true) {
|
|||
|
if($chunkSize < 1) $chunkSize = $this->chunkSize;
|
|||
|
$lines = explode("\n", $this->getChunk($chunkNum, $chunkSize, $reverse));
|
|||
|
foreach($lines as $key => $line) {
|
|||
|
$line = trim($line);
|
|||
|
if(!strlen($line)) {
|
|||
|
unset($lines[$key]);
|
|||
|
} else {
|
|||
|
$lines[$key] = $line;
|
|||
|
}
|
|||
|
}
|
|||
|
if($reverse) $lines = array_reverse($lines);
|
|||
|
return $lines;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get a chunk of data (string) from the end of the log file
|
|||
|
*
|
|||
|
* Returned string is automatically adjusted at the beginning and
|
|||
|
* ending to contain only full log lines.
|
|||
|
*
|
|||
|
* @param int $chunkNum Current chunk/pagination number (default=1, first)
|
|||
|
* @param int $chunkSize Number of bytes to retrieve (default=0, which assigns default chunk size of 12288)
|
|||
|
* @param bool $reverse True=pull from end of file, false=pull from beginning (default=true)
|
|||
|
* @param bool $clean Get a clean chunk that starts at the beginning of a line? (default=true)
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getChunk($chunkNum = 1, $chunkSize = 0, $reverse = true, $clean = true) {
|
|||
|
|
|||
|
if($chunkSize < 1) $chunkSize = $this->chunkSize;
|
|||
|
|
|||
|
if($reverse) {
|
|||
|
$offset = -1 * ($chunkSize * $chunkNum);
|
|||
|
} else {
|
|||
|
$offset = $chunkSize * ($chunkNum-1);
|
|||
|
}
|
|||
|
|
|||
|
if(self::debug) {
|
|||
|
$this->message("chunkNum=$chunkNum, chunkSize=$chunkSize, offset=$offset, filesize=" . filesize($this->logFilename));
|
|||
|
}
|
|||
|
|
|||
|
$data = '';
|
|||
|
$totalChunks = $this->getTotalChunks($chunkSize);
|
|||
|
if($chunkNum > $totalChunks) return $data;
|
|||
|
|
|||
|
if(!$fp = fopen($this->logFilename, "r")) return $data;
|
|||
|
|
|||
|
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
|
|||
|
|
|||
|
if($clean) {
|
|||
|
// make chunk include up to beginning of first line
|
|||
|
fseek($fp, -1, SEEK_CUR);
|
|||
|
while(ftell($fp) > 0) {
|
|||
|
$chr = fread($fp, 1);
|
|||
|
if($chr == "\n") break;
|
|||
|
fseek($fp, -2, SEEK_CUR);
|
|||
|
$data = $chr . $data;
|
|||
|
}
|
|||
|
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
|
|||
|
}
|
|||
|
|
|||
|
// get the big part of the chunk
|
|||
|
$data .= fread($fp, $chunkSize);
|
|||
|
|
|||
|
if($clean) {
|
|||
|
// remove last partial line
|
|||
|
$pos = strrpos($data, "\n");
|
|||
|
if($pos) $data = substr($data, 0, $pos);
|
|||
|
}
|
|||
|
|
|||
|
fclose($fp);
|
|||
|
|
|||
|
return $data;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the total number of chunks in the file
|
|||
|
*
|
|||
|
* @param int $chunkSize
|
|||
|
* @return int
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getTotalChunks($chunkSize = 0) {
|
|||
|
if($chunkSize < 1) $chunkSize = $this->chunkSize;
|
|||
|
$filesize = filesize($this->logFilename);
|
|||
|
return $filesize > 0 ? ceil($filesize / $chunkSize) : 0;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get total number of lines in the log file
|
|||
|
*
|
|||
|
* @return int
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getTotalLines() {
|
|||
|
|
|||
|
if(!is_readable($this->logFilename)) return 0;
|
|||
|
|
|||
|
if(filesize($this->logFilename) < $this->chunkSize) {
|
|||
|
$data = file($this->logFilename);
|
|||
|
return count($data);
|
|||
|
}
|
|||
|
|
|||
|
if(!$fp = fopen($this->logFilename, "r")) return 0;
|
|||
|
$totalLines = 0;
|
|||
|
|
|||
|
while(!feof($fp)) {
|
|||
|
$data = fread($fp, $this->chunkSize);
|
|||
|
$totalLines += substr_count($data, "\n");
|
|||
|
}
|
|||
|
|
|||
|
fclose($fp);
|
|||
|
|
|||
|
return $totalLines;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get log lines that lie within a date range
|
|||
|
*
|
|||
|
* @param int $dateFrom Starting date (unix timestamp or strtotime compatible string)
|
|||
|
* @param int $dateTo Ending date (unix timestamp or strtotime compatible string)
|
|||
|
* @param int $pageNum Current pagination number (default=1)
|
|||
|
* @param int $limit Items per pagination (default=100), or specify 0 for no limit.
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getDate($dateFrom, $dateTo = 0, $pageNum = 1, $limit = 100) {
|
|||
|
$options = array(
|
|||
|
'dateFrom' => $dateFrom,
|
|||
|
'dateTo' => $dateTo,
|
|||
|
);
|
|||
|
return $this->find($limit, $pageNum, $options);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Return lines from the end of the log file, with various options
|
|||
|
*
|
|||
|
* @param int $limit Number of items to return (per pagination), or 0 for no limit.
|
|||
|
* @param int $pageNum Current pagination (default=1)
|
|||
|
* @param array $options
|
|||
|
* - text (string): Return only lines containing the given string of text
|
|||
|
* - reverse (bool): True=find from end of file, false=find from beginning (default=true)
|
|||
|
* - toFile (string): Send results to the given basename (default=none)
|
|||
|
* - dateFrom (unix timestamp): Return only lines newer than the given date (default=oldest)
|
|||
|
* - dateTo (unix timestamp): Return only lines older than the given date (default=now)
|
|||
|
* Note: dateFrom and dateTo may be combined to return a range.
|
|||
|
* @return int|array of strings (associative), each indexed by string containing slash separated
|
|||
|
* numeric values of: "current/total/start/end/total" which is useful with pagination.
|
|||
|
* If the 'toFile' option is used, then return value is instead an integer qty of lines written.
|
|||
|
* @throws \Exception on fatal error
|
|||
|
*
|
|||
|
*/
|
|||
|
public function find($limit = 100, $pageNum = 1, array $options = array()) {
|
|||
|
|
|||
|
$defaults = array(
|
|||
|
'text' => null,
|
|||
|
'dateFrom' => 0,
|
|||
|
'dateTo' => 0,
|
|||
|
'reverse' => true,
|
|||
|
'toFile' => '',
|
|||
|
);
|
|||
|
|
|||
|
$options = array_merge($defaults, $options);
|
|||
|
$hasFilters = !empty($options['text']);
|
|||
|
|
|||
|
if($options['dateFrom'] || $options['dateTo']) {
|
|||
|
if(!$options['dateTo']) $options['dateTo'] = time();
|
|||
|
if(!ctype_digit("$options[dateFrom]")) $options['dateFrom'] = strtotime($options['dateFrom']);
|
|||
|
if(!ctype_digit("$options[dateTo]")) $options['dateTo'] = strtotime($options['dateTo']);
|
|||
|
$hasFilters = true;
|
|||
|
}
|
|||
|
|
|||
|
if($options['toFile']) {
|
|||
|
$toFile = $this->path . basename($options['toFile']);
|
|||
|
$fp = fopen($toFile, 'w');
|
|||
|
if(!$fp) throw new \Exception("Unable to open file for writing: $toFile");
|
|||
|
} else {
|
|||
|
$toFile = '';
|
|||
|
$fp = null;
|
|||
|
}
|
|||
|
|
|||
|
$lines = array();
|
|||
|
$start = ($pageNum-1) * $limit;
|
|||
|
$end = $start + $limit;
|
|||
|
$cnt = 0; // number that will be written or returned by this
|
|||
|
$n = 0; // number total
|
|||
|
$chunkNum = 0;
|
|||
|
$totalChunks = $this->getTotalChunks($this->chunkSize);
|
|||
|
$stopNow = false;
|
|||
|
$chunkLineHashes = array();
|
|||
|
|
|||
|
while($chunkNum <= $totalChunks && !$stopNow) {
|
|||
|
|
|||
|
$chunk = $this->getChunkArray(++$chunkNum, 0, $options['reverse']);
|
|||
|
if(empty($chunk)) break;
|
|||
|
|
|||
|
foreach($chunk as $line) {
|
|||
|
|
|||
|
$line = trim($line);
|
|||
|
$hash = md5($line);
|
|||
|
$valid = !isset($chunkLineHashes[$hash]);
|
|||
|
$chunkLineHashes[$hash] = 1;
|
|||
|
if($valid) $valid = $this->isValidLine($line, $options, $stopNow);
|
|||
|
if(!$hasFilters && $limit && count($lines) >= $limit) $stopNow = true;
|
|||
|
if($stopNow) break;
|
|||
|
if(!$valid) continue;
|
|||
|
|
|||
|
$n++;
|
|||
|
if($limit && ($n <= $start || $n > $end)) continue;
|
|||
|
$cnt++;
|
|||
|
if($fp) {
|
|||
|
fwrite($fp, $line . "\n");
|
|||
|
} else {
|
|||
|
if(self::debug) $line .= " (line $n, chunk $chunkNum, hash=$hash)";
|
|||
|
$lines[$n] = $line;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$total = $hasFilters ? $n : $this->getTotalLines();
|
|||
|
$end = $start + count($lines);
|
|||
|
if($end > $total) $end = $total;
|
|||
|
if(count($lines) < $limit && $total > $end) $total = $end;
|
|||
|
|
|||
|
if($fp) {
|
|||
|
fclose($fp);
|
|||
|
$this->wire('files')->chmod($toFile);
|
|||
|
return $cnt;
|
|||
|
}
|
|||
|
|
|||
|
foreach($lines as $key => $line) {
|
|||
|
unset($lines[$key]);
|
|||
|
$lines["$key/$total/$start/$end/$limit"] = $line;
|
|||
|
}
|
|||
|
return $lines;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns whether the given log line is valid to be considered a log entry
|
|||
|
*
|
|||
|
* @param $line
|
|||
|
* @param array $options
|
|||
|
* @param bool $stopNow Populates this with true when it can determine no more lines are necessary.
|
|||
|
* @return bool|int Returns boolean true if valid, false if not.
|
|||
|
* If valid as a result of a date comparison, the unix timestmap for the line is returned.
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function isValidLine($line, array $options, &$stopNow) {
|
|||
|
// 4 7 10
|
|||
|
// $test = '2013-10-22 15:18:43';
|
|||
|
if(strlen($line) < 20) return false;
|
|||
|
if($line[19] != $this->delimeter) return false;
|
|||
|
if($line[4] != "-") return false;
|
|||
|
if($line[7] != "-") return false;
|
|||
|
if($line[10] != " ") return false;
|
|||
|
|
|||
|
if(!empty($options['text'])) {
|
|||
|
if(stripos($line, $options['text']) === false) return false;
|
|||
|
}
|
|||
|
|
|||
|
if(!empty($options['dateFrom']) && !empty($options['dateTo'])) {
|
|||
|
$parts = explode($this->delimeter, $line);
|
|||
|
$date = strtotime($parts[0]);
|
|||
|
if($date >= $options['dateFrom'] && $date <= $options['dateTo']) return true;
|
|||
|
if($date < $options['dateFrom'] && $options['reverse']) $stopNow = true;
|
|||
|
if($date > $options['dateTo'] && !$options['reverse']) $stopNow = true;
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Prune to number of bytes
|
|||
|
*
|
|||
|
* @param $bytes
|
|||
|
* @return bool|int
|
|||
|
* @deprecated use pruneBytes() or pruneLines() instead
|
|||
|
*
|
|||
|
*/
|
|||
|
public function prune($bytes) {
|
|||
|
return $this->pruneBytes($bytes);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Prune log file to specified number of bytes (from the end)
|
|||
|
*
|
|||
|
* @param int $bytes
|
|||
|
* @return int|bool positive integer on success, 0 if no prune necessary, or boolean false on failure.
|
|||
|
*
|
|||
|
*/
|
|||
|
public function pruneBytes($bytes) {
|
|||
|
|
|||
|
$filename = $this->logFilename;
|
|||
|
|
|||
|
if(!$filename || !file_exists($filename) || filesize($filename) <= $bytes) return 0;
|
|||
|
|
|||
|
$fpr = fopen($filename, "r");
|
|||
|
$fpw = fopen("$filename.new", "w");
|
|||
|
if(!$fpr || !$fpw) return false;
|
|||
|
|
|||
|
fseek($fpr, ($bytes * -1), SEEK_END);
|
|||
|
fgets($fpr, $this->maxLineLength); // first line likely just a partial line, so skip it
|
|||
|
$cnt = 0;
|
|||
|
|
|||
|
while(!feof($fpr)) {
|
|||
|
$line = fgets($fpr, $this->maxLineLength);
|
|||
|
fwrite($fpw, $line);
|
|||
|
$cnt++;
|
|||
|
}
|
|||
|
|
|||
|
fclose($fpw);
|
|||
|
fclose($fpr);
|
|||
|
|
|||
|
if($cnt) {
|
|||
|
$this->wire('files')->unlink($filename, true);
|
|||
|
$this->wire('files')->rename("$filename.new", $filename, true);
|
|||
|
$this->wire('files')->chmod($filename);
|
|||
|
} else {
|
|||
|
$this->wire('files')->unlink("$filename.new", true);
|
|||
|
}
|
|||
|
|
|||
|
return $cnt;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Prune log file to contain only entries newer than $oldestDate
|
|||
|
*
|
|||
|
* @param int|string $oldestDate
|
|||
|
* @return int Number of lines written
|
|||
|
*
|
|||
|
*/
|
|||
|
public function pruneDate($oldestDate) {
|
|||
|
$toFile = $this->logFilename . '.new';
|
|||
|
$qty = $this->find(0, 1, array(
|
|||
|
'reverse' => false,
|
|||
|
'toFile' => $toFile,
|
|||
|
'dateFrom' => $oldestDate,
|
|||
|
'dateTo' => time(),
|
|||
|
));
|
|||
|
if(file_exists($toFile)) {
|
|||
|
$this->wire('files')->unlink($this->logFilename, true);
|
|||
|
$this->wire('files')->rename($toFile, $this->logFilename, true);
|
|||
|
return $qty;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Delete the log file
|
|||
|
*
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function delete() {
|
|||
|
return $this->wire('files')->unlink($this->logFilename, true);
|
|||
|
}
|
|||
|
|
|||
|
public function __toString() {
|
|||
|
return $this->filename();
|
|||
|
}
|
|||
|
|
|||
|
public function setDelimiter($c) {
|
|||
|
$this->delimeter = $c;
|
|||
|
}
|
|||
|
|
|||
|
public function setDelimeter($c) {
|
|||
|
$this->setDelimiter($c);
|
|||
|
}
|
|||
|
|
|||
|
public function setMaxLineLength($c) {
|
|||
|
$this->maxLineLength = (int) $c;
|
|||
|
}
|
|||
|
|
|||
|
public function getMaxLineLength() {
|
|||
|
return $this->maxLineLength;
|
|||
|
}
|
|||
|
|
|||
|
public function setFileExtension($ext) {
|
|||
|
$this->fileExtension = $ext;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get or set the default chunk size used when reading from logs and not overridden by method argument
|
|||
|
*
|
|||
|
* @param int $chunkSize Specify chunk size to set, or omit to get
|
|||
|
* @return int
|
|||
|
* @since 3.0.143
|
|||
|
*
|
|||
|
*/
|
|||
|
public function chunkSize($chunkSize = 0) {
|
|||
|
if($chunkSize > 0) $this->chunkSize = (int) $chunkSize;
|
|||
|
return $this->chunkSize;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|