-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
introduces new Console Logger that interact with the profilable circu…
…it breaker
- Loading branch information
Showing
2 changed files
with
144 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |