Generalize application for any LoggerInterface

2.x 2.2.0
Julien Rosset 2 years ago
parent 01a7d7ffc4
commit de43de4ca0

@ -19,7 +19,8 @@
"symfony/console": "^5.4", "symfony/console": "^5.4",
"jrosset/betterphptoken": "^1.0", "jrosset/betterphptoken": "^1.0",
"jrosset/collections": "^2.3", "jrosset/collections": "^2.3",
"jrosset/extendedmonolog": "^1.1" "jrosset/extendedmonolog": "^1.1",
"psr/log": "^2.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

@ -2,21 +2,14 @@
namespace jrosset\CliProgram; namespace jrosset\CliProgram;
use jrosset\CliProgram\Monolog\ConsoleOutputWithMonolog; use jrosset\CliProgram\Monolog\TMonologApplication;
use jrosset\ExtendedMonolog\ExceptionLogger;
use jrosset\ExtendedMonolog\LogDirectoryHandler; 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 * An application with a {@see LogDirectoryHandler Monolog log directory} for each command
*/ */
class ApplicationWithCommandMonolog extends ApplicationWithCommandOutputInterface { class ApplicationWithCommandMonolog extends ApplicationWithCommandOutputInterface {
/** use TMonologApplication;
* @var string The main log directory for Monolog: on subdirectory by command
*/
private string $logMainDirectory;
/** /**
* Initialization * Initialization
@ -29,44 +22,4 @@ class ApplicationWithCommandMonolog extends ApplicationWithCommandOutputInterfac
parent::__construct($name, $version); parent::__construct($name, $version);
$this->setLogMainDirectory($logMainDirectory); $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; namespace jrosset\CliProgram;
use jrosset\CliProgram\Output\TCommandOutputApplication;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/** /**
* An application with a by-command {@see OutputInterface} * An application with a by-command {@see OutputInterface}
*/ */
class ApplicationWithCommandOutputInterface extends Application { class ApplicationWithCommandOutputInterface extends Application {
/** use 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));
}
} }

@ -2,29 +2,38 @@
namespace jrosset\CliProgram\Monolog; namespace jrosset\CliProgram\Monolog;
use jrosset\CliProgram\Output\OutputWithLogger;
use Monolog\Logger;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
/** /**
* 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 { class ConsoleOutputWithMonolog extends OutputWithLogger {
use TOutputInterfaceWithMonolog;
/**
* Option pour ne pas écrire un message dans Monolog
*/
public const OPTION_SKIP_MONOLOG = 4096;
/** /**
* @param LoggerInterface $logger The Monolog Logger * @inheritDoc
* @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) { protected function getLoggerLevelFromVerbosity (int $verbosity): int {
parent::__construct($verbosity, $decorated, $formatter); if ($verbosity <= OutputInterface::VERBOSITY_QUIET) {
$this->setLogger($logger); return Logger::ERROR;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_NORMAL) {
return Logger::NOTICE;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_VERBOSE) {
return Logger::INFO;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_VERY_VERBOSE) {
return Logger::INFO;
}
elseif ($verbosity <= OutputInterface::VERBOSITY_DEBUG) {
return Logger::DEBUG;
}
else {
return Logger::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,130 @@
<?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
*
* @noinspection PhpMissingParamTypeInspection
*/
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}
*
* @noinspection PhpMissingParamTypeInspection
*/
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 ($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
* @noinspection PhpReturnDocTypeMismatchInspection
*/
protected function getLoggerLevelFromVerbosity (int $verbosity) {
return $verbosity;
}
/**
* @inheritDoc
*/
public function write ($messages, bool $newline = false, int $options = 0) {
$this->writeToLogger($messages, $newline, $options);
return parent::write($messages, $newline, $options);
}
/**
* @inheritDoc
*/
public function writeLn ($messages, int $options = 0) {
$this->writeToLogger($messages, true, $options);
return parent::writeLn($messages, $options);
}
}

@ -0,0 +1,112 @@
<?php
namespace jrosset\CliProgram\Output;
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
*/
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 ($messages, bool $newline = false, int $options = 0) {
return $this->getOutput()->write($messages, $newline, $options);
}
/**
* @inheritDoc
*/
public function writeln ($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));
}
}

@ -4,7 +4,7 @@ namespace jrosset\Tests\Commands;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use jrosset\CliProgram\Monolog\ConsoleOutputWithMonolog; use jrosset\CliProgram\Output\OutputWithLogger;
use jrosset\CliProgram\Validation\CommandWithValidation; use jrosset\CliProgram\Validation\CommandWithValidation;
use jrosset\CliProgram\Validation\Validators\DateValidator; use jrosset\CliProgram\Validation\Validators\DateValidator;
use jrosset\CliProgram\Validation\Validators\IntegerValidator; use jrosset\CliProgram\Validation\Validators\IntegerValidator;
@ -69,7 +69,7 @@ class Hello extends CommandWithValidation {
for ($curr = 0; $curr < $repeat; $curr++) { for ($curr = 0; $curr < $repeat; $curr++) {
$output->writeln($text); $output->writeln($text);
} }
$output->writeln('FIN', ConsoleOutputWithMonolog::OPTION_SKIP_MONOLOG); $output->writeln('FIN', OutputWithLogger::OPTION_SKIP_LOGGER);
return Command::SUCCESS; return Command::SUCCESS;
} }
} }
Loading…
Cancel
Save