Skip to content

Commit

Permalink
introduces new Console Logger that interact with the profilable circu…
Browse files Browse the repository at this point in the history
…it breaker
  • Loading branch information
llaville committed Sep 3, 2024
1 parent 5b9ca70 commit 2230e6e
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
62 changes: 62 additions & 0 deletions src/Console/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types=1);
/**
* This file is part of the BoxManifest package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Bartlett\BoxManifest\Console;

use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;

use function trim;

/**
* @author Laurent Laville
* @since Release 4.0.0
*/
class Logger extends ConsoleLogger
{
public const STATUS_STARTED = 'started';
public const STATUS_RUNNING = 'running';
public const STATUS_STOPPED = 'stopped';

/**
* @param array<string, int> $verbosityLevelMap
* @param array<string, string> $formatLevelMap
*/
public function __construct(
protected DebugFormatterHelper $helper,
OutputInterface $output,
array $verbosityLevelMap = [],
array $formatLevelMap = []
) {
parent::__construct($output, $verbosityLevelMap, $formatLevelMap);
}

/**
* @param array{
* status?: string,
* id?: int,
* error?: boolean,
* prefix?: string,
* errorPrefix?: string
* } $context
*/
public function log($level, $message, array $context = []): void
{
if (isset($context['status']) && isset($context['id'])) {
$id = $context['id'];
$error = $context['error'] ?? false;
$message = match ($context['status']) {
self::STATUS_STARTED => $this->helper->start($id, $message, $context['prefix'] ?? 'RUN'),
self::STATUS_RUNNING => $this->helper->progress($id, $message, $error, $context['prefix'] ?? 'OUT', $context['errorPrefix'] ?? 'ERR'),
self::STATUS_STOPPED => trim($this->helper->stop($id, $message, !$error, $context['prefix'] ?? 'RES')),
default => $message, // uses raw message if status is unknown
};
}
parent::log($level, $message, $context);
}
}
82 changes: 82 additions & 0 deletions src/Pipeline/InterruptibleTimedProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php declare(strict_types=1);
/**
* This file is part of the BoxManifest package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Bartlett\BoxManifest\Pipeline;

use Bartlett\BoxManifest\Console\Logger;

use League\Pipeline\ProcessorInterface;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

use Symfony\Component\Stopwatch\Stopwatch;

use RuntimeException;
use Throwable;
use function sprintf;

/**
* Starts a timer before entering the stage, and logs the expired time afterward.
* Implements also a circuit breaker feature.
*
* @author Laurent Laville
* @since Release 4.0.0
*/
final readonly class InterruptibleTimedProcessor implements ProcessorInterface
{
public function __construct(private LoggerInterface $logger)
{
}

/**
* @throws Throwable
*/
public function process($payload, callable ...$stages)
{
$stopwatch = new Stopwatch();

foreach ($stages as $stage) {
$name = $stage::class;
$stopwatch->start($name);
$pid = $payload['pid'] = uniqid();

$this->logger->debug(
sprintf('Starting stage "%s"', $name),
['status' => Logger::STATUS_STARTED, 'id' => $pid]
);

try {
$payload = $stage($payload);
$event = $stopwatch->stop($name);
$level = LogLevel::NOTICE;
$message = sprintf('Completed stage "%s" in %d ms', $name, $event->getDuration());
$isSuccessful = true;
} catch (RuntimeException $exception) {
// Runtime errors that do not require immediate action but should typically be logged and monitored
$level = LogLevel::ERROR;
$isSuccessful = false;
} catch (Throwable $exception) {
// Critical conditions
$level = LogLevel::CRITICAL;
$isSuccessful = false;
} finally {
if (!$isSuccessful) {
$message = sprintf('The stage "%s" has failed : %s', $name, $exception->getMessage());
}
$this->logger->log($level, $message, ['status' => Logger::STATUS_STOPPED, 'id' => $pid, 'error' => !$isSuccessful]);

if (!$isSuccessful) {
// circuit breaker or critical conditions lead to abort the workflow
throw $exception;
}
}
}

return $payload;
}
}

0 comments on commit 2230e6e

Please sign in to comment.