123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- <?php
- namespace FPM;
- class LogReader
- {
- /**
- * Log debugging.
- *
- * @var bool
- */
- private bool $debug;
- /**
- * Log descriptor.
- *
- * @var string|null
- */
- private ?string $currentSourceName;
- /**
- * Log descriptors.
- *
- * @var LogSource[]
- */
- private array $sources = [];
- /**
- * Log reader constructor.
- *
- * @param bool $debug
- */
- public function __construct(bool $debug = false)
- {
- $this->debug = $debug;
- }
- /**
- * Returns log descriptor source.
- *
- * @return LogSource
- * @throws \Exception
- */
- private function getSource(): LogSource
- {
- if ( ! $this->currentSourceName) {
- throw new \Exception('Log descriptor is not set');
- }
- return $this->sources[$this->currentSourceName];
- }
- /**
- * Set current stream source and create it if it does not exist.
- *
- * @param string $name Stream name.
- * @param resource $stream The actual stream.
- */
- public function setStreamSource(string $name, $stream)
- {
- $this->currentSourceName = $name;
- if ( ! isset($this->sources[$name])) {
- $this->sources[$name] = new LogStreamSource($stream);
- }
- }
- /**
- * Set file source as current and create it if it does not exist.
- *
- * @param string $name Source name.
- * @param string $filePath Source file path.s
- */
- public function setFileSource(string $name, string $filePath)
- {
- $this->currentSourceName = $name;
- if ( ! isset($this->sources[$name])) {
- $this->sources[$name] = new LogFileSource($filePath);
- }
- }
- /**
- * Get a single log line.
- *
- * @param int $timeoutSeconds
- * @param int $timeoutMicroseconds
- *
- * @return null|string
- * @throws \Exception
- */
- public function getLine(int $timeoutSeconds = 3, int $timeoutMicroseconds = 0): ?string
- {
- $line = $this->getSource()->getLine($timeoutSeconds, $timeoutMicroseconds);
- $this->trace(is_null($line) ? "LINE - null" : "LINE: $line");
- return $line;
- }
- /**
- * Print separation line.
- */
- public function printSeparator(): void
- {
- echo str_repeat('-', 68) . "\n";
- }
- /**
- * Print all logs.
- */
- public function printLogs(): void
- {
- $hasMultipleDescriptors = count($this->sources) > 1;
- echo "LOGS:\n";
- foreach ($this->sources as $name => $source) {
- if ($hasMultipleDescriptors) {
- echo ">>> source: $name\n";
- }
- $this->printSeparator();
- foreach ($source->getAllLines() as $line) {
- echo $line;
- }
- $this->printSeparator();
- }
- }
- /**
- * Print error and logs.
- *
- * @param string|null $errorMessage Error message to print before the logs.
- *
- * @return false
- */
- private function printError(?string $errorMessage): bool
- {
- if (is_null($errorMessage)) {
- return false;
- }
- echo "ERROR: " . $errorMessage . "\n\n";
- $this->printLogs();
- echo "\n";
- return false;
- }
- /**
- * Read log until matcher matches the log message or there are no more logs.
- *
- * @param callable $matcher Callback to identify a match
- * @param string|null $notFoundMessage Error message if matcher does not succeed.
- * @param bool $checkAllLogs Whether to also check past logs.
- * @param int $timeoutSeconds Timeout in seconds for reading of all messages.
- * @param int $timeoutMicroseconds Additional timeout in microseconds for reading of all messages.
- *
- * @return bool
- * @throws \Exception
- */
- public function readUntil(
- callable $matcher,
- string $notFoundMessage = null,
- bool $checkAllLogs = false,
- int $timeoutSeconds = 3,
- int $timeoutMicroseconds = 0
- ): bool {
- $startTime = microtime(true);
- $endTime = $startTime + $timeoutSeconds + ($timeoutMicroseconds / 1_000_000);
- if ($checkAllLogs) {
- foreach ($this->getSource()->getAllLines() as $line) {
- if ($matcher($line)) {
- return true;
- }
- }
- }
- do {
- if (microtime(true) > $endTime) {
- return $this->printError($notFoundMessage);
- }
- $line = $this->getLine($timeoutSeconds, $timeoutMicroseconds);
- if ($line === null || microtime(true) > $endTime) {
- return $this->printError($notFoundMessage);
- }
- } while ( ! $matcher($line));
- return true;
- }
- /**
- * Print tracing message - only in debug .
- *
- * @param string $msg Message to print.
- */
- private function trace(string $msg): void
- {
- if ($this->debug) {
- print "LogReader - $msg";
- }
- }
- }
- abstract class LogSource
- {
- /**
- * Get single line from the source.
- *
- * @param int $timeoutSeconds Read timeout in seconds
- * @param int $timeoutMicroseconds Read timeout in microseconds
- *
- * @return string|null
- */
- public abstract function getLine(int $timeoutSeconds, int $timeoutMicroseconds): ?string;
- /**
- * Get all lines that has been returned by getLine() method.
- *
- * @return string[]
- */
- public abstract function getAllLines(): array;
- }
- class LogStreamSource extends LogSource
- {
- /**
- * @var resource
- */
- private $stream;
- /**
- * @var array
- */
- private array $lines = [];
- public function __construct($stream)
- {
- $this->stream = $stream;
- }
- /**
- * Get single line from the stream.
- *
- * @param int $timeoutSeconds Read timeout in seconds
- * @param int $timeoutMicroseconds Read timeout in microseconds
- *
- * @return string|null
- */
- public function getLine(int $timeoutSeconds, int $timeoutMicroseconds): ?string
- {
- if (feof($this->stream)) {
- return null;
- }
- $read = [$this->stream];
- $write = null;
- $except = null;
- if (stream_select($read, $write, $except, $timeoutSeconds, $timeoutMicroseconds)) {
- $line = fgets($this->stream);
- $this->lines[] = $line;
- return $line;
- } else {
- return null;
- }
- }
- /**
- * Get all stream read lines.
- *
- * @return string[]
- */
- public function getAllLines(): array
- {
- return $this->lines;
- }
- }
- class LogFileSource extends LogSource
- {
- /**
- * @var string
- */
- private string $filePath;
- /**
- * @var int
- */
- private int $position;
- /**
- * @var array
- */
- private array $lines = [];
- public function __construct(string $filePath)
- {
- $this->filePath = $filePath;
- $this->position = 0;
- }
- /**
- * Get single line from the file.
- *
- * @param int $timeoutSeconds Read timeout in seconds
- * @param int $timeoutMicroseconds Read timeout in microseconds
- *
- * @return string|null
- */
- public function getLine(int $timeoutSeconds, int $timeoutMicroseconds): ?string
- {
- $endTime = microtime(true) + $timeoutSeconds + ($timeoutMicroseconds / 1_000_000);
- while ($this->position >= count($this->lines)) {
- if (is_file($this->filePath)) {
- $lines = file($this->filePath);
- if ($lines === false) {
- return null;
- }
- $this->lines = $lines;
- if ($this->position < count($lines)) {
- break;
- }
- }
- usleep(50_000);
- if (microtime(true) > $endTime) {
- return null;
- }
- }
- return $this->lines[$this->position++];
- }
- /**
- * Get all returned lines from the file.
- *
- * @return string[]
- */
- public function getAllLines(): array
- {
- return array_slice($this->lines, 0, $this->position);
- }
- }
|