diff --git a/.gitignore b/.gitignore index fa36fe5..5d0d27a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ vendor composer.lock .idea +*.iml +phpunit.xml +*.cache +*.bak \ No newline at end of file diff --git a/README.md b/README.md index 8138932..9fb38a4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -# Stackify Monolog v2 Handler +# Stackify Monolog v3 Handler Monolog handler for sending log messages and exceptions to Stackify. -Monolog >= 2.0.0 is supported. +Monolog >= 3.0.0 is supported. -> For Monolog v1, use the [1.x branch](https://github.com/stackify/stackify-log-monolog/tree/1.x) +For Monolog V1 + +> Use the [1.x branch](https://github.com/stackify/stackify-log-monolog/tree/1.x) + +For Monolog V2 + +> Use the [2.x branch](https://github.com/stackify/stackify-log-monolog/tree/2.x) * **Errors and Logs Overview:** http://support.stackify.com/errors-and-logs-overview/ * **Sign Up for a Trial:** http://www.stackify.com/sign-up/ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a94a6af..4be7f02 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ pool: vmImage: ubuntu-latest variables: - phpVersion: 7.2 + phpVersion: 8.1 steps: - script: | diff --git a/composer.json b/composer.json index 5e314f2..e19f00c 100644 --- a/composer.json +++ b/composer.json @@ -6,13 +6,22 @@ "type": "library", "license": "Apache-2.0", "require": { - "php": ">=7.0", - "stackify/logger": "~1.4", - "monolog/monolog": "~2.0" + "php": ">=8.1", + "stackify/logger": "~1.6", + "monolog/monolog": "~3.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1" }, "autoload": { "psr-4": { "Stackify\\Log\\Monolog\\": "src/Stackify/Log/Monolog" + }, + "autoload-dev": { + "psr-4": {"Stackify\\Log\\Monolog\\Tests": "tests/"} } }, "minimum-stability": "dev", diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..64292b9 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,8 @@ + + + + + ./tests + + + diff --git a/src/Stackify/Log/Monolog/Handler.php b/src/Stackify/Log/Monolog/Handler.php index cdc1fcc..5cd779d 100644 --- a/src/Stackify/Log/Monolog/Handler.php +++ b/src/Stackify/Log/Monolog/Handler.php @@ -8,6 +8,8 @@ use Monolog\Logger; use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Level; +use Monolog\LogRecord; class Handler extends AbstractProcessingHandler { @@ -16,7 +18,12 @@ class Handler extends AbstractProcessingHandler * * @var \Stackify\Log\Transport\TransportInterface */ - private $_transport; + private TransportInterface $_transport; + + /** + * Include channel inside message as hashtag + */ + private bool $includeChannel; /** * Stackify monolog handler @@ -29,14 +36,15 @@ class Handler extends AbstractProcessingHandler * @param boolean $bubble */ public function __construct( - $appName, - $environmentName = null, + string $appName, + string $environmentName = null, TransportInterface $transport = null, - $logServerVariables = false, - $config = null, - $level = Logger::DEBUG, - $bubble = true + bool $logServerVariables = false, + array $config = null, + int|string|Level $level = Level::Debug, + bool $bubble = true ) { + parent::__construct($level, $bubble); if ($config) { @@ -45,7 +53,7 @@ public function __construct( AgentConfig::getInstance()->extract($config); } - $messageBuilder = new MessageBuilder('Stackify Monolog v.2.0', $appName, $environmentName, $logServerVariables); + $messageBuilder = new MessageBuilder('Stackify Monolog v.3.0', $appName, $environmentName, $logServerVariables); if (null === $transport) { $transport = new AgentSocketTransport(); @@ -53,18 +61,23 @@ public function __construct( $transport->setMessageBuilder($messageBuilder); $this->_transport = $transport; + $this->includeChannel = false; + + if ($config && $config['includeChannel']) { + $this->includeChannel = true; + } } /** * {@inheritdoc} * - * @param array $record + * @param LogRecord $record * * @return void */ - public function write(array $record): void + public function write(LogRecord $record): void { - $this->_transport->addEntry(new LogEntry($record)); + $this->_transport->addEntry(new LogEntry($record, $this->includeChannel)); } /** @@ -72,7 +85,7 @@ public function write(array $record): void * * @return void */ - public function flush() + public function flush(): void { $this->_transport->finish(); } @@ -93,7 +106,7 @@ public function close(): void * * @return void */ - public function reset() + public function reset(): void { $this->flush(); parent::reset(); diff --git a/src/Stackify/Log/Monolog/LogEntry.php b/src/Stackify/Log/Monolog/LogEntry.php index 6041349..4b5fef9 100644 --- a/src/Stackify/Log/Monolog/LogEntry.php +++ b/src/Stackify/Log/Monolog/LogEntry.php @@ -6,6 +6,7 @@ use Stackify\Log\Entities\NativeError; use Monolog\Logger as MonologLogger; +use Monolog\LogRecord as MonologLogRecord; final class LogEntry implements LogEntryInterface { @@ -14,10 +15,13 @@ final class LogEntry implements LogEntryInterface private $exception; private $context; private $nativeError; + private $includeChannel; + private $channel; - public function __construct(array $record) + public function __construct(MonologLogRecord $record, bool $includeChannel = false) { $this->record = $record; + $context = $record['context']; // find exception and remove from context foreach ($context as $key => $value) { @@ -44,6 +48,12 @@ public function __construct(array $record) if (!empty($context)) { $this->context = $context; } + + $this->includeChannel = $includeChannel; + $this->channel = null; + if ($record && $record['channel']) { + $this->channel = $record['channel']; + } } public function getContext() @@ -63,6 +73,10 @@ public function getLevel() public function getMessage() { + if ($this->includeChannel && $this->channel) { + return $this->record['message']." #{$this->channel}"; + } + return $this->record['message']; } diff --git a/tests/HandlerTest.php b/tests/HandlerTest.php new file mode 100644 index 0000000..377446e --- /dev/null +++ b/tests/HandlerTest.php @@ -0,0 +1,139 @@ + [$level], + Level::cases() + ); + } + + /** + * @dataProvider logLevelProvider + */ + public function testHandlesAllLevels(Level $level) + { + $message = 'Hello, world! ' . $level->value; + $context = ['foo' => 'bar', 'level' => $level->value]; + + $transport = $this->createDummyTransport(); + + $handler = $this->createDummyHandler($transport, $level); + $record = $this->getRecord($level, $message, context: $context); + $handler->handle($record); + + $logEntries = $transport->getEntries(); + + $firstLogEntry = $logEntries[0]; + + $this->assertEquals(1, count($logEntries)); + $this->assertEquals($firstLogEntry->getLevel(), strtoupper($record->level->name)); + $this->assertEquals($firstLogEntry->getMessage(), $record->message); + + // Reset every call + $transport->reset(); + } + + public function testLogRecordChannel() + { + $level = Level::Info; + $message = 'Hello, world! ' . $level->value; + $context = ['foo' => 'bar', 'level' => $level->value]; + + $transport = $this->createDummyTransport(); + + $includeChannel = true; + $channel = "test"; + $handler = $this->createDummyHandler($transport, $level, $includeChannel); + $record = $this->getRecord($level, $message, context: $context, channel: $channel); + $handler->handle($record); + + $logEntries = $transport->getEntries(); + + $firstLogEntry = $logEntries[0]; + $this->assertEquals(1, count($logEntries)); + $this->assertEquals($firstLogEntry->getLevel(), strtoupper($record->level->name)); + $this->assertEquals($firstLogEntry->getMessage(), $record->message . " #{$channel}"); + + // Reset every call + $transport->reset(); + } + + private function createDummyHandler($transport, Level $level = null, $includeChannel = false) { + return $this->createHandler($this->appName, $this->environmentName, $transport, $level, $includeChannel); + } + + private function createHandler($appName, $environmentName, $transport, Level $level = null, bool $includeChannel = false): Handler + { + $config = []; + if ($includeChannel) { + $config['includeChannel'] = true; + } + + if (null === $level) { + $handler = new Handler($appName, $environmentName, $transport, false, $config, $level); + } else { + $handler = new Handler($appName, $environmentName, $transport, false, $config); + } + + return $handler; + } + + private function createDummyTransport() + { + return new SpyTransport(); + } +} + + +class SpyTransport implements TransportInterface { + protected BuilderInterface $messageBuilder; + protected array $logEntries; + protected bool $hasFinished; + + public function __construct() { + $this->logEntries = []; + $this->hasFinished = false; + } + + public function setMessageBuilder(BuilderInterface $messageBuilder) { + $this->messageBuilder = $messageBuilder; + } + + public function addEntry(LogEntryInterface $logEntry): int { + $this->logEntries[] = $logEntry; + return count($this->logEntries) - 1; + } + + public function finish() { + $this->hasFinished = true; + } + + public function hasFinish() { + return $this->hasFinished; + } + + public function getEntries() { + return $this->logEntries; + } + + public function reset() { + $this->logEntries = []; + $this->hasFinished = false; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..f091c14 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,3 @@ +