Generalize application for any LoggerInterface

master 3.2.0
Julien Rosset 2 years ago
parent 2981e01945
commit 73c12fc82f

@ -16,10 +16,11 @@
"minimum-stability": "stable",
"require": {
"php": "^8.1",
"symfony/console": "^6.1",
"jrosset/betterphptoken": "^1.0",
"jrosset/collections": "^3.0",
"jrosset/extendedmonolog": "^2.0"
"jrosset/extendedmonolog": "^2.0",
"psr/log": "^2.0",
"symfony/console": "^6.1"
},
"autoload": {
"psr-4": {

@ -2,21 +2,14 @@
namespace jrosset\CliProgram;
use jrosset\CliProgram\Monolog\ConsoleOutputWithMonolog;
use jrosset\ExtendedMonolog\ExceptionLogger;
use jrosset\CliProgram\Monolog\TMonologApplication;
use jrosset\ExtendedMonolog\LogDirectoryHandler;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* An application with a {@see LogDirectoryHandler Monolog log directory} for each command
*/
class ApplicationWithCommandMonolog extends ApplicationWithCommandOutputInterface {
/**
* @var string The main log directory for Monolog: on subdirectory by command
*/
private string $logMainDirectory;
use TMonologApplication;
/**
* Initialization
@ -29,44 +22,4 @@ class ApplicationWithCommandMonolog extends ApplicationWithCommandOutputInterfac
parent::__construct($name, $version);
$this->setLogMainDirectory($logMainDirectory);
}
/**
* @inheritDoc
*/
protected function getOutputInterfaceForCommand (Command $command, InputInterface $input, OutputInterface $output): OutputInterface {
return new ConsoleOutputWithMonolog(
new ExceptionLogger(
$command->getName(),
[
new LogDirectoryHandler(
$this->getLogMainDirectory() . DIRECTORY_SEPARATOR
. str_replace(':', DIRECTORY_SEPARATOR, $command->getName())
),
]
),
$output->getVerbosity(),
$output->isDecorated(),
$output->getFormatter()
);
}
/**
* The main log directory for Monolog: on subdirectory by command
*
* @return string The main log directory for Monolog: on subdirectory by command
*/
public function getLogMainDirectory (): string {
return $this->logMainDirectory;
}
/**
* Set the main log directory for Monolog: on subdirectory by command
*
* @param string $logMainDirectory The main log directory for Monolog: on subdirectory by command
*
* @return $this
*/
public function setLogMainDirectory (string $logMainDirectory): self {
$this->logMainDirectory = $logMainDirectory;
return $this;
}
}

@ -2,38 +2,13 @@
namespace jrosset\CliProgram;
use jrosset\CliProgram\Output\TCommandOutputApplication;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/**
* An application with a by-command {@see OutputInterface}
*/
class ApplicationWithCommandOutputInterface extends Application {
/**
* Get the {@see OutputInterface} for a command
*
* @param Command $command The command
* @param InputInterface $input The input
* @param OutputInterface $output The existing output
*
* @return OutputInterface The output for the command
*
* @throws Throwable On error
*
* @noinspection PhpUnusedParameterInspection
*/
protected function getOutputInterfaceForCommand (Command $command, InputInterface $input, OutputInterface $output): OutputInterface {
return $output;
}
/**
* @inheritDoc
* @throws Throwable
*/
protected function doRunCommand (Command $command, InputInterface $input, OutputInterface $output): int {
return parent::doRunCommand($command, $input, $this->getOutputInterfaceForCommand($command, $input, $output));
}
use TCommandOutputApplication;
}

@ -2,29 +2,39 @@
namespace jrosset\CliProgram\Monolog;
use jrosset\CliProgram\Output\OutputWithLogger;
use Monolog\Level;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* A {@see ConsoleOutput} with a {@see LoggerInterface Monolog Logger}
* A generic output interface with a {@see LoggerInterface Monolog Logger}
*
* @implements OutputWithLogger<Logger>
*/
class ConsoleOutputWithMonolog extends ConsoleOutput {
use TOutputInterfaceWithMonolog;
class ConsoleOutputWithMonolog extends OutputWithLogger {
/**
* Option pour ne pas écrire un message dans Monolog
* @inheritDoc
*/
public const OPTION_SKIP_MONOLOG = 4096;
/**
* @param LoggerInterface $logger The Monolog Logger
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct (LoggerInterface $logger, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) {
parent::__construct($verbosity, $decorated, $formatter);
$this->setLogger($logger);
protected function getLoggerLevelFromVerbosity (int $verbosity): Level {
if ($verbosity <= OutputInterface::VERBOSITY_QUIET) {
return Level::Error;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_NORMAL) {
return Level::Notice;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_VERBOSE) {
return Level::Info;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_VERY_VERBOSE) {
return Level::Info;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_DEBUG) {
return Level::Debug;
}
else {
return Level::Notice;
}
}
}

@ -0,0 +1,57 @@
<?php
namespace jrosset\CliProgram\Monolog;
use jrosset\ExtendedMonolog\ExceptionLogger;
use jrosset\ExtendedMonolog\LogDirectoryHandler;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Implementation for an application with a {@see ConsoleOutputWithMonolog} and a {@see LogDirectoryHandler Monolog log directory} for each command
*/
trait TMonologApplication {
/**
* @var string The main log directory for Monolog: on subdirectory by command
*/
private string $logMainDirectory;
/**
* @inheritDoc
*/
protected function getOutputInterfaceForCommand (Command $command, InputInterface $input, OutputInterface $output): OutputInterface {
return new ConsoleOutputWithMonolog(
$output,
new ExceptionLogger(
$command->getName(),
[
new LogDirectoryHandler(
$this->getLogMainDirectory() . DIRECTORY_SEPARATOR
. str_replace(':', DIRECTORY_SEPARATOR, $command->getName())
),
]
)
);
}
/**
* The main log directory for Monolog: on subdirectory by command
*
* @return string The main log directory for Monolog: on subdirectory by command
*/
public function getLogMainDirectory (): string {
return $this->logMainDirectory;
}
/**
* Set the main log directory for Monolog: on subdirectory by command
*
* @param string $logMainDirectory The main log directory for Monolog: on subdirectory by command
*
* @return $this
*/
public function setLogMainDirectory (string $logMainDirectory): self {
$this->logMainDirectory = $logMainDirectory;
return $this;
}
}

@ -1,76 +0,0 @@
<?php
namespace jrosset\CliProgram\Monolog;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Provide a {@see LoggerInterface Monolog Logger} for {@see OutputInterface}
*/
trait TOutputInterfaceWithMonolog {
/**
* @var LoggerInterface|null The logger
*/
private ?LoggerInterface $logger;
/**
* The logger
*
* @return LoggerInterface|null The logger
*/
public function getLogger (): ?LoggerInterface {
return $this->logger;
}
/**
* Set the logger
*
* @param LoggerInterface|null $logger The logger
*
* @return $this
*/
public function setLogger (?LoggerInterface $logger): self {
$this->logger = $logger;
return $this;
}
/**
* @inheritDoc
*/
public function write($messages, bool $newline = false, int $options = 0): void {
if (!is_iterable($messages)) {
$messages = [$messages];
}
if ($this->logger !== null && (($options & ConsoleOutputWithMonolog::OPTION_SKIP_MONOLOG) !== ConsoleOutputWithMonolog::OPTION_SKIP_MONOLOG)) {
$verbosities = OutputInterface::VERBOSITY_QUIET | OutputInterface::VERBOSITY_NORMAL | OutputInterface::VERBOSITY_VERBOSE | OutputInterface::VERBOSITY_VERY_VERBOSE | OutputInterface::VERBOSITY_DEBUG;
$verbosity = $verbosities & $options ?: OutputInterface::VERBOSITY_NORMAL;
if ($verbosity <= OutputInterface::VERBOSITY_QUIET) {
$loggerLevel = Logger::ERROR;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_NORMAL) {
$loggerLevel = Logger::NOTICE;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_VERBOSE) {
$loggerLevel = Logger::INFO;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_VERY_VERBOSE) {
$loggerLevel = Logger::INFO;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_DEBUG) {
$loggerLevel = Logger::DEBUG;
}
else {
$loggerLevel = Logger::NOTICE;
}
foreach ($messages as $message) {
$this->logger->log($loggerLevel, strip_tags($message));
}
}
parent::write($messages, $newline, $options);
}
}

@ -0,0 +1,125 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace jrosset\CliProgram\Output;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* A generic output interface with a {@see LoggerInterface PSR logger} connected
*
* @template TLogger of LoggerInterface
*/
class OutputWithLogger extends OutputWrapper {
/**
* Option to not write messages to logger
*
* @see static::write()
* @see static::writeln()
*/
public const OPTION_SKIP_LOGGER = 4096;
/**
* @var TLogger|null The logger
*/
private $logger;
/**
* Initialization
*
* @param OutputInterface $output The real output interface
* @param TLogger|null $logger The logger
*/
public function __construct (OutputInterface $output, $logger = null) {
parent::__construct($output);
$this->setLogger($logger);
}
/**
* The logger
*
* @return TLogger|null The logger
*/
public function getLogger () {
return $this->logger;
}
/**
* Set the logger
*
* @param TLogger|null $logger The logger
*
* @return $this
*
* @throws InvalidArgumentException If the logger is not null or an instance for {@see LoggerInterface}
*/
public function setLogger ($logger): self {
if ($logger !== null && !$logger instanceof LoggerInterface) {
throw new InvalidArgumentException('The logger must be null or a ' . LoggerInterface::class . ' instance');
}
$this->logger = $logger;
return $this;
}
/**
* Write into the logger
*
* @param iterable|string $messages The messages to write
* @param bool $newline Newline at the end ?
* @param int $options A bitmask of options
*
* @return void
*
* @noinspection PhpUnusedParameterInspection
*/
protected function writeToLogger (iterable|string $messages, bool $newline = false, int $options = 0): void {
if ($this->logger === null || ($options & static::OPTION_SKIP_LOGGER) === static::OPTION_SKIP_LOGGER) {
return;
}
//region Calcul log level
$verbosities = OutputInterface::VERBOSITY_QUIET
| OutputInterface::VERBOSITY_NORMAL
| OutputInterface::VERBOSITY_VERBOSE
| OutputInterface::VERBOSITY_VERY_VERBOSE
| OutputInterface::VERBOSITY_DEBUG;
$loggerLevel = $this->getLoggerLevelFromVerbosity(
$verbosities & $options ? : OutputInterface::VERBOSITY_NORMAL
);
//endregion
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
$this->getLogger()->log($loggerLevel, strip_tags($message));
}
}
/**
* OGet the logger level from verbosity
*
* @param int $verbosity The verbosity
*
* @return mixed The logger level
*/
protected function getLoggerLevelFromVerbosity (int $verbosity): mixed {
return $verbosity;
}
/**
* @inheritDoc
*/
public function write (iterable|string $messages, bool $newline = false, int $options = 0) {
$this->writeToLogger($messages, $newline, $options);
return parent::write($messages, $newline, $options);
}
/**
* @inheritDoc
*/
public function writeLn (iterable|string $messages, int $options = 0) {
$this->writeToLogger($messages, true, $options);
return parent::writeLn($messages, $options);
}
}

@ -0,0 +1,114 @@
<?php
namespace jrosset\CliProgram\Output;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* A wrapper for an OutputInterface
*/
abstract class OutputWrapper implements OutputInterface {
/**
* @var OutputInterface The real output interface
*/
private OutputInterface $output;
/**
* Initialization
*
* @param OutputInterface $output The real output interface
* @param LoggerInterface|null $logger The logger
*/
public function __construct (OutputInterface $output) {
$this->output = $output;
}
/**
* The real output interface
*
* @return OutputInterface The real output interface
*/
public function getOutput (): OutputInterface {
return $this->output;
}
/**
* @inheritDoc
*/
public function write (iterable|string $messages, bool $newline = false, int $options = 0) {
return $this->getOutput()->write($messages, $newline, $options);
}
/**
* @inheritDoc
*/
public function writeln (iterable|string $messages, int $options = 0) {
return $this->getOutput()->writeln($messages, $options);
}
/**
* @inheritDoc
*/
public function setVerbosity (int $level) {
return $this->getOutput()->setVerbosity($level);
}
/**
* @inheritDoc
*/
public function getVerbosity (): int {
return $this->getOutput()->getVerbosity();
}
/**
* @inheritDoc
*/
public function isQuiet (): bool {
return $this->getOutput()->isQuiet();
}
/**
* @inheritDoc
*/
public function isVerbose (): bool {
return $this->getOutput()->isVerbose();
}
/**
* @inheritDoc
*/
public function isVeryVerbose (): bool {
return $this->getOutput()->isVeryVerbose();
}
/**
* @inheritDoc
*/
public function isDebug (): bool {
return $this->getOutput()->isDebug();
}
/**
* @inheritDoc
*/
public function setDecorated (bool $decorated) {
return $this->getOutput()->setDecorated($decorated);
}
/**
* @inheritDoc
*/
public function isDecorated (): bool {
return $this->getOutput()->isDecorated();
}
/**
* @inheritDoc
* @noinspection PhpInappropriateInheritDocUsageInspection
*/
public function setFormatter (OutputFormatterInterface $formatter) {
return $this->getOutput()->setFormatter($formatter);
}
/**
* @inheritDoc
*/
public function getFormatter (): OutputFormatterInterface {
return $this->getOutput()->getFormatter();
}
}

@ -0,0 +1,38 @@
<?php
namespace jrosset\CliProgram\Output;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/**
* Implementation for an application with a by-command {@see OutputInterface}
*/
trait TCommandOutputApplication {
/**
* Get the {@see OutputInterface} for a command
*
* @param Command $command The command
* @param InputInterface $input The input
* @param OutputInterface $output The existing output
*
* @return OutputInterface The output for the command
*
* @throws Throwable On error
*
* @noinspection PhpUnusedParameterInspection
*/
protected function getOutputInterfaceForCommand (Command $command, InputInterface $input, OutputInterface $output): OutputInterface {
return $output;
}
/**
* @inheritDoc
* @throws Throwable
*/
protected function doRunCommand (Command $command, InputInterface $input, OutputInterface $output): int {
return parent::doRunCommand($command, $input, $this->getOutputInterfaceForCommand($command, $input, $output));
}
}

@ -32,7 +32,7 @@ class Hello extends CommandWithValidation {
/**
* @inheritDoc
*/
protected function configure () {
protected function configure (): void {
parent::configure();
$this->addArgument(
@ -79,7 +79,7 @@ class Hello extends CommandWithValidation {
for ($curr = 0; $curr < $repeat; $curr++) {
$output->writeln($text);
}
$output->writeln('FIN', ConsoleOutputWithMonolog::OPTION_SKIP_MONOLOG);
$output->writeln('FIN', ConsoleOutputWithMonolog::OPTION_SKIP_LOGGER);
return Command::SUCCESS;
}
}
Loading…
Cancel
Save