php-8.0.30-src/sapi/fpm/tests/logreader.inc

373 lines
9.2 KiB
PHP

<?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 Read timeout in seconds
* @param int $timeoutMicroseconds Read timeout in microseconds
* @param bool $throwOnTimeout Whether to throw an exception on timeout
*
* @return null|string
* @throws \Exception
*/
public function getLine(
int $timeoutSeconds = 3,
int $timeoutMicroseconds = 0,
bool $throwOnTimeout = false
): ?string {
$line = $this->getSource()->getLine(
$timeoutSeconds,
$timeoutMicroseconds,
$throwOnTimeout
);
$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";
}
}
}
class LogTimoutException extends \Exception
{
}
abstract class LogSource
{
/**
* Get single line from the source.
*
* @param int $timeoutSeconds Read timeout in seconds
* @param int $timeoutMicroseconds Read timeout in microseconds
* @param bool $throwOnTimeout Whether to throw an exception on timeout
*
* @return string|null
* @throws LogTimoutException
*/
public abstract function getLine(
int $timeoutSeconds,
int $timeoutMicroseconds,
bool $throwOnTimeout = false
): ?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
* @param bool $throwOnTimeout Whether to throw an exception on timeout
*
* @return string|null
* @throws LogTimoutException
*/
public function getLine(
int $timeoutSeconds,
int $timeoutMicroseconds,
bool $throwOnTimeout = false
): ?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 {
if ($throwOnTimeout) {
throw new LogTimoutException('Timout exceeded when reading line');
}
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
* @param bool $throwOnTimeout Whether to throw an exception on timeout
*
* @return string|null
* @throws LogTimoutException
*/
public function getLine(
int $timeoutSeconds,
int $timeoutMicroseconds,
bool $throwOnTimeout = false
): ?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) {
if ($throwOnTimeout) {
throw new LogTimoutException('Timout exceeded when reading line');
}
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);
}
}