diff options
Diffstat (limited to 'library/Icinga/Protocol/File')
-rw-r--r-- | library/Icinga/Protocol/File/Exception/FileReaderException.php | 12 | ||||
-rw-r--r-- | library/Icinga/Protocol/File/FileIterator.php | 81 | ||||
-rw-r--r-- | library/Icinga/Protocol/File/FileQuery.php | 86 | ||||
-rw-r--r-- | library/Icinga/Protocol/File/FileReader.php | 208 | ||||
-rw-r--r-- | library/Icinga/Protocol/File/LogFileIterator.php | 149 |
5 files changed, 536 insertions, 0 deletions
diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php new file mode 100644 index 0000000..237352c --- /dev/null +++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php @@ -0,0 +1,12 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ +namespace Icinga\Protocol\File; + +use Icinga\Exception\IcingaException; + +/** + * Exception thrown if a file reader specific error occurs + */ +class FileReaderException extends IcingaException +{ +} diff --git a/library/Icinga/Protocol/File/FileIterator.php b/library/Icinga/Protocol/File/FileIterator.php new file mode 100644 index 0000000..64b6600 --- /dev/null +++ b/library/Icinga/Protocol/File/FileIterator.php @@ -0,0 +1,81 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Protocol\File; + +use Icinga\Util\EnumeratingFilterIterator; +use Icinga\Util\File; + +/** + * Class FileIterator + * + * Iterate over a file, yielding only fields of non-empty lines which match a PCRE expression + */ +class FileIterator extends EnumeratingFilterIterator +{ + /** + * A PCRE string with the fields to extract from the file's lines as named subpatterns + * + * @var string + */ + protected $fields; + + /** + * An associative array of the current line's fields ($field => $value) + * + * @var array + */ + protected $currentData; + + public function __construct($filename, $fields) + { + $this->fields = $fields; + $f = new File($filename); + $f->setFlags( + File::DROP_NEW_LINE | + File::READ_AHEAD | + File::SKIP_EMPTY + ); + parent::__construct($f); + } + + /** + * Return the current data + * + * @return array + */ + public function current(): array + { + return $this->currentData; + } + + /** + * Accept lines matching the given PCRE pattern + * + * @return bool + * + * @throws FileReaderException If PHP failed parsing the PCRE pattern + */ + public function accept(): bool + { + $data = array(); + $matched = preg_match( + $this->fields, + $this->getInnerIterator()->current(), + $data + ); + + if ($matched === false) { + throw new FileReaderException('Failed parsing regular expression!'); + } elseif ($matched === 1) { + foreach ($data as $key => $value) { + if (is_int($key)) { + unset($data[$key]); + } + } + $this->currentData = $data; + return true; + } + return false; + } +} diff --git a/library/Icinga/Protocol/File/FileQuery.php b/library/Icinga/Protocol/File/FileQuery.php new file mode 100644 index 0000000..504de2e --- /dev/null +++ b/library/Icinga/Protocol/File/FileQuery.php @@ -0,0 +1,86 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Protocol\File; + +use Icinga\Data\SimpleQuery; +use Icinga\Data\Filter\Filter; + +/** + * Class FileQuery + * + * Query for Datasource Icinga\Protocol\File\FileReader + * + * @package Icinga\Protocol\File + */ +class FileQuery extends SimpleQuery +{ + /** + * Sort direction + * + * @var int + */ + private $sortDir; + + /** + * Filters to apply on result + * + * @var array + */ + private $filters = array(); + + /** + * Nothing to do here + */ + public function applyFilter(Filter $filter) + { + } + + /** + * Sort query result chronological + * + * @param string $dir Sort direction, 'ASC' or 'DESC' (default) + * + * @return FileQuery + */ + public function order($field, $direction = null) + { + $this->sortDir = ( + $direction === null || strtoupper(trim($direction)) === 'DESC' + ) ? self::SORT_DESC : self::SORT_ASC; + return $this; + } + + /** + * Return true if sorting descending, false otherwise + * + * @return bool + */ + public function sortDesc() + { + return $this->sortDir === self::SORT_DESC; + } + + /** + * Add an mandatory filter expression to be applied on this query + * + * @param string $expression the filter expression to be applied + * + * @return FileQuery + */ + public function andWhere($expression) + { + $this->filters[] = $expression; + return $this; + } + + /** + * Get filters currently applied on this query + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } +} diff --git a/library/Icinga/Protocol/File/FileReader.php b/library/Icinga/Protocol/File/FileReader.php new file mode 100644 index 0000000..a06494c --- /dev/null +++ b/library/Icinga/Protocol/File/FileReader.php @@ -0,0 +1,208 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Protocol\File; + +use Countable; +use ArrayIterator; +use Icinga\Data\Selectable; +use Icinga\Data\ConfigObject; + +/** + * Read file line by line + */ +class FileReader implements Selectable, Countable +{ + /** + * A PCRE string with the fields to extract from the file's lines as named subpatterns + * + * @var string + */ + protected $fields; + + /** + * Name of the target file + * + * @var string + */ + protected $filename; + + /** + * Cache for static::count() + * + * @var int + */ + protected $count = null; + + /** + * Create a new reader + * + * @param ConfigObject $config + * + * @throws FileReaderException If a required $config directive (filename or fields) is missing + */ + public function __construct(ConfigObject $config) + { + foreach (array('filename', 'fields') as $key) { + if (isset($config->{$key})) { + $this->{$key} = $config->{$key}; + } else { + throw new FileReaderException('The directive `%s\' is required', $key); + } + } + } + + /** + * Instantiate a FileIterator object with the target file + * + * @return FileIterator + */ + public function iterate() + { + return new LogFileIterator($this->filename, $this->fields); + } + + /** + * Instantiate a FileQuery object + * + * @return FileQuery + */ + public function select() + { + return new FileQuery($this); + } + + /** + * Fetch and return all rows of the given query's result set using an iterator + * + * @param FileQuery $query + * + * @return ArrayIterator + */ + public function query(FileQuery $query) + { + return new ArrayIterator($this->fetchAll($query)); + } + + /** + * Return the number of available valid lines. + * + * @return int + */ + public function count(): int + { + if ($this->count === null) { + $this->count = iterator_count($this->iterate()); + } + return $this->count; + } + + /** + * Fetch result as an array of objects + * + * @param FileQuery $query + * + * @return array + */ + public function fetchAll(FileQuery $query) + { + $all = array(); + foreach ($this->fetchPairs($query) as $index => $value) { + $all[$index] = (object) $value; + } + return $all; + } + + /** + * Fetch result as a key/value pair array + * + * @param FileQuery $query + * + * @return array + */ + public function fetchPairs(FileQuery $query) + { + $skip = $query->getOffset(); + $read = $query->getLimit(); + if ($skip === null) { + $skip = 0; + } + $lines = array(); + if ($query->sortDesc()) { + $count = $this->count(); + if ($count <= $skip) { + return $lines; + } elseif ($count < ($skip + $read)) { + $read = $count - $skip; + $skip = 0; + } else { + $skip = $count - ($skip + $read); + } + } + foreach ($this->iterate() as $index => $line) { + if ($index >= $skip) { + if ($index >= $skip + $read) { + break; + } + $lines[] = $line; + } + } + if ($query->sortDesc()) { + $lines = array_reverse($lines); + } + return $lines; + } + + /** + * Fetch first result row + * + * @param FileQuery $query + * + * @return object + */ + public function fetchRow(FileQuery $query) + { + $all = $this->fetchAll($query); + if (isset($all[0])) { + return $all[0]; + } + return null; + } + + /** + * Fetch first result column + * + * @param FileQuery $query + * + * @return array + */ + public function fetchColumn(FileQuery $query) + { + $column = array(); + foreach ($this->fetchPairs($query) as $pair) { + foreach ($pair as $value) { + $column[] = $value; + break; + } + } + return $column; + } + + /** + * Fetch first column value from first result row + * + * @param FileQuery $query + * + * @return mixed + */ + public function fetchOne(FileQuery $query) + { + $pairs = $this->fetchPairs($query); + if (isset($pairs[0])) { + foreach ($pairs[0] as $value) { + return $value; + } + } + return null; + } +} diff --git a/library/Icinga/Protocol/File/LogFileIterator.php b/library/Icinga/Protocol/File/LogFileIterator.php new file mode 100644 index 0000000..67a4d99 --- /dev/null +++ b/library/Icinga/Protocol/File/LogFileIterator.php @@ -0,0 +1,149 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Protocol\File; + +use Icinga\Exception\IcingaException; +use SplFileObject; +use Iterator; + +/** + * Iterate over a log file, yielding the regex fields of the log messages + */ +class LogFileIterator implements Iterator +{ + /** + * Log file + * + * @var SplFileObject + */ + protected $file; + + /** + * A PCRE string with the fields to extract + * from the log messages as named subpatterns + * + * @var string + */ + protected $fields; + + /** + * Value for static::current() + * + * @var array + */ + protected $current; + + /** + * Index for static::key() + * + * @var int + */ + protected $index; + + /** + * Value for static::valid() + * + * @var boolean + */ + protected $valid; + + /** + * @var string + */ + protected $next = null; + + /** + * @param string $filename The log file's name + * @param string $fields A PCRE string with the fields to extract + * from the log messages as named subpatterns + */ + public function __construct($filename, $fields) + { + $this->file = new SplFileObject($filename); + $this->file->setFlags( + SplFileObject::DROP_NEW_LINE | + SplFileObject::READ_AHEAD + ); + $this->fields = $fields; + } + + public function rewind(): void + { + $this->file->rewind(); + $this->index = 0; + $this->nextMessage(); + } + + public function next(): void + { + $this->file->next(); + ++$this->index; + $this->nextMessage(); + } + + public function current(): array + { + return $this->current; + } + + public function key(): int + { + return $this->index; + } + + public function valid(): bool + { + return $this->valid; + } + + protected function nextMessage() + { + $message = $this->next === null ? array() : array($this->next); + $this->valid = null; + while ($this->file->valid()) { + if (false === ($res = preg_match( + $this->fields, + $current = $this->file->current() + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if (empty($message)) { + if ($res === 1) { + $message[] = $current; + } + } elseif ($res === 1) { + $this->next = $current; + $this->valid = true; + break; + } else { + $message[] = $current; + } + + $this->file->next(); + } + if ($this->valid === null) { + $this->next = null; + $this->valid = ! empty($message); + } + + if ($this->valid) { + while (! empty($message)) { + $matches = array(); + if (false === ($res = preg_match( + $this->fields, + implode(PHP_EOL, $message), + $matches + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if ($res === 1) { + $this->current = $matches; + return; + } + array_pop($message); + } + $this->valid = false; + } + } +} |