mirror of
https://github.com/processwire/processwire.git
synced 2025-08-10 08:44:46 +02:00
Update FileLog to support some new save options, including the ability to collapse duplicate log ones to 1. Also update ProcessLogger to improve output in some instances.
This commit is contained in:
@@ -12,14 +12,72 @@
|
||||
|
||||
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 = '';
|
||||
|
||||
/**
|
||||
@@ -58,52 +116,157 @@ class FileLog extends Wire {
|
||||
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 $str
|
||||
* @return bool Success state
|
||||
* @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? Specify int for bytes from EOF (default=1024)
|
||||
* - `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) {
|
||||
public function save($str, array $options = array()) {
|
||||
|
||||
$defaults = array(
|
||||
'mergeDups' => 1024,
|
||||
'allowDups' => true,
|
||||
'maxTries' => 20,
|
||||
'maxTriesDelay' => 2000,
|
||||
);
|
||||
|
||||
if(!$this->logFilename) return false;
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
$hash = md5($str);
|
||||
|
||||
// if we've already logged this during this instance, then don't do it again
|
||||
if(in_array($hash, $this->itemsLogged)) return true;
|
||||
|
||||
$ts = date("Y-m-d H:i:s");
|
||||
$str = $this->cleanStr($str);
|
||||
$fp = fopen($this->logFilename, "a");
|
||||
$line = $this->delimeter . $str; // log entry, excluding timestamp
|
||||
$hasLock = false; // becomes true when lock obtained
|
||||
$fp = false; // becomes resource when file is open
|
||||
|
||||
if($fp) {
|
||||
$trys = 0;
|
||||
$stop = false;
|
||||
// if we've already logged this during this instance, then don't do it again
|
||||
if(!$options['allowDups'] && isset($this->itemsLogged[$hash])) return true;
|
||||
|
||||
while(!$stop) {
|
||||
if(flock($fp, LOCK_EX)) {
|
||||
fwrite($fp, "$ts{$this->delimeter}$str\n");
|
||||
flock($fp, LOCK_UN);
|
||||
$this->itemsLogged[] = $hash;
|
||||
$stop = true;
|
||||
} else {
|
||||
usleep(2000);
|
||||
if($trys++ > 20) $stop = 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);
|
||||
$this->wire('files')->chmod($this->logFilename);
|
||||
return true;
|
||||
} else {
|
||||
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 = is_int($options['mergeDups']) ? $options['mergeDups'] : $this->chunkSize;
|
||||
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() {
|
||||
@@ -145,7 +308,7 @@ class FileLog extends Wire {
|
||||
*
|
||||
*/
|
||||
protected function getChunkArray($chunkNum = 1, $chunkSize = 0, $reverse = true) {
|
||||
if($chunkSize < 1) $chunkSize = self::defaultChunkSize;
|
||||
if($chunkSize < 1) $chunkSize = $this->chunkSize;
|
||||
$lines = explode("\n", $this->getChunk($chunkNum, $chunkSize, $reverse));
|
||||
foreach($lines as $key => $line) {
|
||||
$line = trim($line);
|
||||
@@ -165,15 +328,16 @@ class FileLog extends Wire {
|
||||
* Returned string is automatically adjusted at the beginning and
|
||||
* ending to contain only full log lines.
|
||||
*
|
||||
* @param int $chunkNum Current pagination number (default=1)
|
||||
* @param int $chunkSize Number of bytes to retrieve (default=12288)
|
||||
* @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) {
|
||||
protected function getChunk($chunkNum = 1, $chunkSize = 0, $reverse = true, $clean = true) {
|
||||
|
||||
if($chunkSize < 1) $chunkSize = self::defaultChunkSize;
|
||||
if($chunkSize < 1) $chunkSize = $this->chunkSize;
|
||||
|
||||
if($reverse) {
|
||||
$offset = -1 * ($chunkSize * $chunkNum);
|
||||
@@ -181,7 +345,9 @@ class FileLog extends Wire {
|
||||
$offset = $chunkSize * ($chunkNum-1);
|
||||
}
|
||||
|
||||
if(self::debug) $this->message("chunkNum=$chunkNum, chunkSize=$chunkSize, offset=$offset, filesize=" . filesize($this->logFilename));
|
||||
if(self::debug) {
|
||||
$this->message("chunkNum=$chunkNum, chunkSize=$chunkSize, offset=$offset, filesize=" . filesize($this->logFilename));
|
||||
}
|
||||
|
||||
$data = '';
|
||||
$totalChunks = $this->getTotalChunks($chunkSize);
|
||||
@@ -191,6 +357,7 @@ class FileLog extends Wire {
|
||||
|
||||
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) {
|
||||
@@ -199,14 +366,17 @@ class FileLog extends Wire {
|
||||
fseek($fp, -2, SEEK_CUR);
|
||||
$data = $chr . $data;
|
||||
}
|
||||
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
|
||||
}
|
||||
|
||||
// get the big part of the chunk
|
||||
fseek($fp, $offset, ($reverse ? SEEK_END : SEEK_SET));
|
||||
$data .= fread($fp, $chunkSize);
|
||||
|
||||
if($clean) {
|
||||
// remove last partial line
|
||||
$pos = strrpos($data, "\n");
|
||||
if($pos) $data = substr($data, 0, $pos);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
|
||||
@@ -221,9 +391,9 @@ class FileLog extends Wire {
|
||||
*
|
||||
*/
|
||||
protected function getTotalChunks($chunkSize = 0) {
|
||||
if($chunkSize < 1) $chunkSize = self::defaultChunkSize;
|
||||
if($chunkSize < 1) $chunkSize = $this->chunkSize;
|
||||
$filesize = filesize($this->logFilename);
|
||||
return ceil($filesize / $chunkSize);
|
||||
return $filesize > 0 ? ceil($filesize / $chunkSize) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,7 +404,7 @@ class FileLog extends Wire {
|
||||
*/
|
||||
public function getTotalLines() {
|
||||
|
||||
if(filesize($this->logFilename) < self::defaultChunkSize) {
|
||||
if(filesize($this->logFilename) < $this->chunkSize) {
|
||||
$data = file($this->logFilename);
|
||||
return count($data);
|
||||
}
|
||||
@@ -243,7 +413,7 @@ class FileLog extends Wire {
|
||||
$totalLines = 0;
|
||||
|
||||
while(!feof($fp)) {
|
||||
$data = fread($fp, self::defaultChunkSize);
|
||||
$data = fread($fp, $this->chunkSize);
|
||||
$totalLines += substr_count($data, "\n");
|
||||
}
|
||||
|
||||
@@ -323,7 +493,7 @@ class FileLog extends Wire {
|
||||
$cnt = 0; // number that will be written or returned by this
|
||||
$n = 0; // number total
|
||||
$chunkNum = 0;
|
||||
$totalChunks = $this->getTotalChunks(self::defaultChunkSize);
|
||||
$totalChunks = $this->getTotalChunks($this->chunkSize);
|
||||
$stopNow = false;
|
||||
$chunkLineHashes = array();
|
||||
|
||||
@@ -517,6 +687,19 @@ class FileLog extends Wire {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -8,12 +8,11 @@
|
||||
*
|
||||
* #pw-summary Enables creation of logs, logging of events, and management of logs.
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @method bool save($name, $text, $options = array())
|
||||
*
|
||||
* @todo option to avoid saving same log entry text back-to-back
|
||||
* @todo option to disable logs by name
|
||||
*
|
||||
*/
|
||||
@@ -22,6 +21,14 @@ class WireLog extends Wire {
|
||||
|
||||
protected $logExtension = 'txt';
|
||||
|
||||
/**
|
||||
* FileLog instances indexed by filename
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $fileLogs = array();
|
||||
|
||||
/**
|
||||
* Record an informational or 'success' message in the message log (messages.txt)
|
||||
*
|
||||
@@ -192,20 +199,29 @@ class WireLog extends Wire {
|
||||
*
|
||||
* #pw-group-retrieval
|
||||
*
|
||||
* @param bool $sortNewest Sort by newest to oldest rather than by name? (default=false) Added 3.0.143
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getLogs() {
|
||||
public function getLogs($sortNewest = false) {
|
||||
|
||||
$logs = array();
|
||||
$dir = new \DirectoryIterator($this->wire('config')->paths->logs);
|
||||
|
||||
foreach($dir as $file) {
|
||||
if($file->isDot() || $file->isDir()) continue;
|
||||
if($file->getExtension() != 'txt') continue;
|
||||
$name = basename($file, '.txt');
|
||||
if($file->getExtension() != $this->logExtension) continue;
|
||||
$name = basename($file, '.' . $this->logExtension);
|
||||
if($name != $this->wire('sanitizer')->pageName($name)) continue;
|
||||
$logs[$name] = array(
|
||||
|
||||
if($sortNewest) {
|
||||
$sortKey = $file->getMTime();
|
||||
while(isset($logs[$sortKey])) $sortKey++;
|
||||
} else {
|
||||
$sortKey = $name;
|
||||
}
|
||||
|
||||
$logs[$sortKey] = array(
|
||||
'name' => $name,
|
||||
'file' => $file->getPathname(),
|
||||
'size' => $file->getSize(),
|
||||
@@ -213,7 +229,12 @@ class WireLog extends Wire {
|
||||
);
|
||||
}
|
||||
|
||||
if($sortNewest) {
|
||||
krsort($logs);
|
||||
} else {
|
||||
ksort($logs);
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
@@ -448,9 +469,13 @@ class WireLog extends Wire {
|
||||
*
|
||||
*/
|
||||
public function getFileLog($name, array $options = array()) {
|
||||
$log = $this->wire(new FileLog($this->getFilename($name)));
|
||||
if(isset($options['delimiter'])) $log->setDelimeter($options['delimiter']);
|
||||
else $log->setDelimeter("\t");
|
||||
$delimiter = isset($options['delimiter']) ? $options['delimiter'] : "\t";
|
||||
$filename = $this->getFilename($name);
|
||||
$key = "$filename$delimiter";
|
||||
if(isset($this->fileLogs[$key])) return $this->fileLogs[$key];
|
||||
$log = $this->wire(new FileLog($filename));
|
||||
$log->setDelimiter($delimiter);
|
||||
$this->fileLogs[$key] = $log;
|
||||
return $log;
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* ProcessWire Logger (Logs Viewer)
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
*
|
||||
@@ -15,7 +15,7 @@ class ProcessLogger extends Process {
|
||||
return array(
|
||||
'title' => __('Logs', __FILE__),
|
||||
'summary' => __('View and manage system logs.', __FILE__),
|
||||
'version' => 1,
|
||||
'version' => 2,
|
||||
'author' => 'Ryan Cramer',
|
||||
'icon' => 'tree',
|
||||
'permission' => 'logs-view',
|
||||
@@ -50,12 +50,16 @@ class ProcessLogger extends Process {
|
||||
$options['itemLabel2'] = 'when';
|
||||
$options['add'] = false;
|
||||
$options['edit'] = 'view/{name}/';
|
||||
$options['items'] = $this->wire('log')->getLogs();
|
||||
$options['items'] = $this->wire('log')->getLogs(true);
|
||||
$options['sort'] = false;
|
||||
|
||||
foreach($options['items'] as $key => $item) {
|
||||
$item['when'] = wireRelativeTimeStr($item['modified'], true, false);
|
||||
if(time() - $item['modified'] > 86400) $item['icon'] = 'file-text-o';
|
||||
else $item['icon'] = 'file-text';
|
||||
if(time() - $item['modified'] > 86400) {
|
||||
$item['icon'] = 'file-text-o';
|
||||
} else {
|
||||
$item['icon'] = 'file-text';
|
||||
}
|
||||
$options['items'][$key] = $item;
|
||||
}
|
||||
|
||||
@@ -63,6 +67,7 @@ class ProcessLogger extends Process {
|
||||
}
|
||||
|
||||
public function ___execute() {
|
||||
/** @var MarkupAdminDataTable $table */
|
||||
$table = $this->wire('modules')->get('MarkupAdminDataTable');
|
||||
$table->setEncodeEntities(false);
|
||||
$table->headerRow(array(
|
||||
@@ -179,7 +184,10 @@ class ProcessLogger extends Process {
|
||||
|
||||
if($this->wire('config')->ajax) return $this->renderLogAjax($items, $name);
|
||||
|
||||
/** @var InputfieldForm $form */
|
||||
$form = $this->wire('modules')->get('InputfieldForm');
|
||||
|
||||
/** @var InputfieldFieldset $fieldset */
|
||||
$fieldset = $this->wire('modules')->get('InputfieldFieldset');
|
||||
$fieldset->attr('id', 'FieldsetTools');
|
||||
$fieldset->label = $this->_('Helpers');
|
||||
@@ -187,6 +195,7 @@ class ProcessLogger extends Process {
|
||||
$fieldset->icon = 'sun-o';
|
||||
$form->add($fieldset);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->wire('modules')->get('InputfieldText');
|
||||
$f->attr('name', 'q');
|
||||
$f->label = $this->_('Text Search');
|
||||
@@ -194,6 +203,7 @@ class ProcessLogger extends Process {
|
||||
$f->columnWidth = 50;
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldDatetime $f */
|
||||
$f = $this->wire('modules')->get('InputfieldDatetime');
|
||||
$f->attr('name', 'date_from');
|
||||
$f->label = $this->_('Date From');
|
||||
@@ -203,6 +213,7 @@ class ProcessLogger extends Process {
|
||||
$f->attr('placeholder', 'yyyy-mm-dd');
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldDatetime $f */
|
||||
$f = $this->wire('modules')->get('InputfieldDatetime');
|
||||
$f->attr('name', 'date_to');
|
||||
$f->icon = 'calendar';
|
||||
@@ -212,6 +223,7 @@ class ProcessLogger extends Process {
|
||||
$f->datepicker = InputfieldDatetime::datepickerFocus;
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldSelect $f */
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', 'action');
|
||||
$f->label = $this->_('Actions');
|
||||
@@ -227,6 +239,7 @@ class ProcessLogger extends Process {
|
||||
$f->addOption('prune', $this->_('Chop (Prune)'));
|
||||
$f->addOption('delete', $this->_('Burn (Delete)'));
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
$f = $this->wire('modules')->get('InputfieldInteger');
|
||||
$f->attr('name', 'prune_days');
|
||||
$f->label = $this->_('Chop To # Days');
|
||||
@@ -236,6 +249,7 @@ class ProcessLogger extends Process {
|
||||
$f->showIf = "action=prune";
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->wire('modules')->get('InputfieldText');
|
||||
$f->attr('name', 'add_text');
|
||||
$f->label = $this->_('New Log Entry');
|
||||
@@ -243,6 +257,7 @@ class ProcessLogger extends Process {
|
||||
$f->showIf = "action=add";
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldSubmit $f */
|
||||
$f = $this->wire('modules')->get('InputfieldSubmit');
|
||||
$f->value = $this->_('Chop this log file now');
|
||||
$f->icon = 'cut';
|
||||
@@ -321,8 +336,10 @@ class ProcessLogger extends Process {
|
||||
|
||||
protected function renderLog(array $items, $name, $time = 0) {
|
||||
|
||||
/** @var Sanitizer $sanitizer */
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
|
||||
/** @var MarkupAdminDataTable $table */
|
||||
$table = $this->wire('modules')->get('MarkupAdminDataTable');
|
||||
$table->setSortable(false);
|
||||
$table->setEncodeEntities(false);
|
||||
@@ -354,6 +371,10 @@ class ProcessLogger extends Process {
|
||||
$date = "<i class='fa fa-leaf ProcessLogNew'></i> $date";
|
||||
}
|
||||
|
||||
if(strpos($entry['text'], '&') !== false) {
|
||||
$entry['text'] = $this->wire('sanitizer')->unentities($entry['text']);
|
||||
}
|
||||
|
||||
foreach($entry as $key => $value) {
|
||||
$entry[$key] = $sanitizer->entities($value);
|
||||
}
|
||||
@@ -362,18 +383,19 @@ class ProcessLogger extends Process {
|
||||
|
||||
if(count($templateItem) >= 4) {
|
||||
|
||||
$url = preg_replace('{^https?://[^/]+}', '', $entry['url']);
|
||||
$row[] = $entry['user'];
|
||||
|
||||
$entry['url'] = preg_replace('{^https?://[^/]+}', '', $entry['url']);
|
||||
$url = $entry['url'];
|
||||
if($url == '/?/') {
|
||||
$url = 2; // array key
|
||||
$entry['url'] = '?';
|
||||
}
|
||||
|
||||
if(strlen($url) > 50) $url = substr($url, 0, 50) . '…';
|
||||
$row[] = $entry['user'];
|
||||
$row[$url] = $entry['url'];
|
||||
$urlLabel = $this->formatLogUrlLabel($entry['url']);
|
||||
$row[$urlLabel] = $url;
|
||||
}
|
||||
|
||||
$row[] = $entry['text'];
|
||||
$row[] = $this->formatLogText($entry['text'], $name);
|
||||
|
||||
$table->row($row);
|
||||
}
|
||||
@@ -384,10 +406,12 @@ class ProcessLogger extends Process {
|
||||
reset($items);
|
||||
$key = key($items);
|
||||
list($n, $total, $start, $end, $limit) = explode('/', $key);
|
||||
if($n && $end) {} // ignore
|
||||
$entries->import($items);
|
||||
$entries->setLimit($limit);
|
||||
$entries->setStart($start);
|
||||
$entries->setTotal($total);
|
||||
/** @var MarkupPagerNav $pager */
|
||||
$pager = $this->wire('modules')->get('MarkupPagerNav');
|
||||
$options = array('baseUrl' => "../$name/");
|
||||
$pagerOut = $pager->render($entries, $options);
|
||||
@@ -420,5 +444,110 @@ class ProcessLogger extends Process {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Format log URL label
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function formatLogUrlLabel($url) {
|
||||
|
||||
if($url === '?') return $url;
|
||||
|
||||
if(strpos($url, '://') !== false) {
|
||||
$url = preg_replace('{^https?://[^/]+}', '', $url);
|
||||
}
|
||||
|
||||
$config = $this->wire('config');
|
||||
$rootUrl = $config->urls->root;
|
||||
$adminUrl = $config->urls->admin;
|
||||
$isAdmin = false;
|
||||
|
||||
if(strpos($url, $adminUrl) === 0) {
|
||||
$isAdmin = true;
|
||||
$url = substr($url, strlen($adminUrl));
|
||||
} else if($rootUrl !== '/' && strpos($url, $rootUrl) === 0) {
|
||||
$url = substr($url, strlen($rootUrl)-1);
|
||||
}
|
||||
|
||||
if($isAdmin && strpos($url, 'page/edit/') !== false && preg_match('/[?&]id=(\d+)/', $url, $matches)) {
|
||||
$url = 'page/edit/?id=' . $matches[1];
|
||||
} else if($url === '/http404/') {
|
||||
$url = $this->_('404 not found');
|
||||
}
|
||||
|
||||
if(strlen($url) > 50) {
|
||||
$url = substr($url, 0, 50) . '…';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format log line txt
|
||||
*
|
||||
* @param string $text
|
||||
* @param string $logName
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function formatLogText($text, $logName = '') {
|
||||
|
||||
$config = $this->wire('config');
|
||||
|
||||
// shorten paths
|
||||
foreach(array('site', 'wire') as $name) {
|
||||
if(strpos($text, "/$name/") === false) continue;
|
||||
$path = $config->paths($name);
|
||||
if(strpos($text, $path) !== false) {
|
||||
$text = str_replace($path, "/$name/", $text);
|
||||
} else {
|
||||
// $text = preg_replace('![-_/\\:a-zA-Z0-9]+/' . $name . '/!', "/$name/", $text);
|
||||
}
|
||||
}
|
||||
|
||||
// shorten assumed namespaces
|
||||
if(strpos($text, 'ProcessWire\\') !== false) {
|
||||
$text = str_replace('ProcessWire\\', '', $text);
|
||||
}
|
||||
|
||||
// formatting of stack traces in errors/exceptions logs
|
||||
if($logName === 'errors' || $logName === 'exceptions') {
|
||||
if(strpos($text, '(line ') && preg_match('/\((line \d+ of [^)]+)\)/', $text, $matches)) {
|
||||
$text = str_replace($matches[0], "<br /><span class='notes'>" . ucfirst($matches[1]) . "</span>", $text);
|
||||
} else if(strpos($text, '(in ') && preg_match('!\((in /[^)]+? line \d+)\)!', $text, $matches)) {
|
||||
$text = str_replace($matches[0], "<br /><span class='notes'>" . ucfirst($matches[1]) . "</span>", $text);
|
||||
}
|
||||
if(strpos($text, ' #0 /')) {
|
||||
list($text, $traces) = explode(' #0 /', $text, 2);
|
||||
$traces = preg_split('! #\d+ /!', $traces);
|
||||
$text .= "<span class='detail'>";
|
||||
foreach($traces as $key => $trace) {
|
||||
$n = $key + 1;
|
||||
$text .= "<br />$n. /$trace";
|
||||
}
|
||||
$text .= "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
// identify recurring instances
|
||||
if(strpos($text, ' ^+')) {
|
||||
$_text = $text;
|
||||
list($text, $qty) = explode(' ^+', $text, 2);
|
||||
if(ctype_digit($qty)) {
|
||||
$text .= "<br />" .
|
||||
"<span class='detail'>" .
|
||||
sprintf($this->_n('Plus %d ealier duplicate ', 'Plus %d earlier duplicates', $qty), $qty) .
|
||||
"</span>";
|
||||
} else {
|
||||
// oops, restore
|
||||
$text = $_text;
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user