diff --git a/system/Log/Handlers/ChromeLoggerHandler.php b/system/Log/Handlers/ChromeLoggerHandler.php index 3d03e79f104e..83bfb9878865 100644 --- a/system/Log/Handlers/ChromeLoggerHandler.php +++ b/system/Log/Handlers/ChromeLoggerHandler.php @@ -90,8 +90,8 @@ public function __construct(array $config = []) * will stop. Any handlers that have not run, yet, will not * be run. * - * @param string $level - * @param string $message + * @param string $level + * @param array|int|object|string $message */ public function handle($level, $message): bool { @@ -128,7 +128,7 @@ public function handle($level, $message): bool /** * Converts the object to display nicely in the Chrome Logger UI. * - * @param mixed $object + * @param array|int|object|string $object * * @return array */ diff --git a/system/Log/Handlers/ErrorlogHandler.php b/system/Log/Handlers/ErrorlogHandler.php index a3c9dfabb6e8..9ec34d9e7ce9 100644 --- a/system/Log/Handlers/ErrorlogHandler.php +++ b/system/Log/Handlers/ErrorlogHandler.php @@ -62,11 +62,15 @@ public function __construct(array $config = []) * will stop. Any handlers that have not run, yet, will not * be run. * - * @param string $level - * @param string $message + * @param string $level + * @param array|int|object|string $message */ public function handle($level, $message): bool { + if (! is_string($message)) { + $message = print_r($message, true); + } + $message = strtoupper($level) . ' --> ' . $message . "\n"; return $this->errorLog($message, $this->messageType); diff --git a/system/Log/Handlers/FileHandler.php b/system/Log/Handlers/FileHandler.php index e80a6121c676..0c31477737f1 100644 --- a/system/Log/Handlers/FileHandler.php +++ b/system/Log/Handlers/FileHandler.php @@ -61,8 +61,8 @@ public function __construct(array $config = []) * will stop. Any handlers that have not run, yet, will not * be run. * - * @param string $level - * @param string $message + * @param string $level + * @param array|int|object|string $message * * @throws Exception */ diff --git a/system/Log/Handlers/HandlerInterface.php b/system/Log/Handlers/HandlerInterface.php index 7b809a320d65..ba580319e0c0 100644 --- a/system/Log/Handlers/HandlerInterface.php +++ b/system/Log/Handlers/HandlerInterface.php @@ -22,8 +22,8 @@ interface HandlerInterface * will stop. Any handlers that have not run, yet, will not * be run. * - * @param string $level - * @param string $message + * @param string $level + * @param array|int|object|string $message */ public function handle($level, $message): bool; diff --git a/system/Log/Logger.php b/system/Log/Logger.php index 515a065647ee..55efeaa4ea23 100644 --- a/system/Log/Logger.php +++ b/system/Log/Logger.php @@ -15,6 +15,7 @@ use CodeIgniter\Log\Handlers\HandlerInterface; use Psr\Log\LoggerInterface; use RuntimeException; +use Stringable; use Throwable; /** @@ -152,7 +153,7 @@ public function __construct($config, bool $debug = CI_DEBUG) /** * System is unusable. * - * @param string $message + * @param string|Stringable $message */ public function emergency($message, array $context = []): bool { @@ -165,7 +166,7 @@ public function emergency($message, array $context = []): bool * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message + * @param string|Stringable $message */ public function alert($message, array $context = []): bool { @@ -177,7 +178,7 @@ public function alert($message, array $context = []): bool * * Example: Application component unavailable, unexpected exception. * - * @param string $message + * @param string|Stringable $message */ public function critical($message, array $context = []): bool { @@ -188,7 +189,7 @@ public function critical($message, array $context = []): bool * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message + * @param string|Stringable $message */ public function error($message, array $context = []): bool { @@ -201,7 +202,7 @@ public function error($message, array $context = []): bool * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message + * @param string|Stringable $message */ public function warning($message, array $context = []): bool { @@ -211,7 +212,7 @@ public function warning($message, array $context = []): bool /** * Normal but significant events. * - * @param string $message + * @param string|Stringable $message */ public function notice($message, array $context = []): bool { @@ -223,7 +224,7 @@ public function notice($message, array $context = []): bool * * Example: User logs in, SQL logs. * - * @param string $message + * @param string|Stringable $message */ public function info($message, array $context = []): bool { @@ -233,7 +234,7 @@ public function info($message, array $context = []): bool /** * Detailed debug information. * - * @param string $message + * @param string|Stringable $message */ public function debug($message, array $context = []): bool { @@ -243,8 +244,8 @@ public function debug($message, array $context = []): bool /** * Logs with an arbitrary level. * - * @param mixed $level - * @param string $message + * @param string $level + * @param string|Stringable $message */ public function log($level, $message, array $context = []): bool { @@ -312,9 +313,9 @@ public function log($level, $message, array $context = []): bool * {file} * {line} * - * @param mixed $message + * @param array|int|object|string $message * - * @return mixed + * @return array|int|object|string */ protected function interpolate($message, array $context = []) { diff --git a/system/Test/Mock/MockLogger.php b/system/Test/Mock/MockLogger.php index 2ee2500b0679..22701e9ea8b8 100644 --- a/system/Test/Mock/MockLogger.php +++ b/system/Test/Mock/MockLogger.php @@ -95,6 +95,22 @@ class MockLogger 'notice', 'warning', ], + /* + * The default filename extension for log files. + * An extension of 'php' allows for protecting the log files via basic + * scripting, when they are to be stored under a publicly accessible directory. + * + * Note: Leaving it blank will default to 'log'. + */ + 'fileExtension' => '', + + /* + * The file system permissions to be applied on newly created log files. + * + * IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal + * integer notation (i.e. 0700, 0644, etc.) + */ + 'filePermissions' => 0644, // Logging Directory Path 'path' => '', diff --git a/tests/system/Log/Handlers/ErrorlogHandlerTest.php b/tests/system/Log/Handlers/ErrorlogHandlerTest.php index f184f5a2db57..8910b21da21b 100644 --- a/tests/system/Log/Handlers/ErrorlogHandlerTest.php +++ b/tests/system/Log/Handlers/ErrorlogHandlerTest.php @@ -14,6 +14,7 @@ use CodeIgniter\Log\Exceptions\LogException; use CodeIgniter\Test\CIUnitTestCase; use PHPUnit\Framework\MockObject\MockObject; +use stdClass; /** * @internal @@ -34,6 +35,39 @@ public function testErrorLoggingWithErrorLog(): void $this->assertTrue($logger->handle('error', 'Test message.')); } + public function testErrorLoggingWithArray(): void + { + $logger = $this->getMockedHandler(['handles' => ['critical', 'error']]); + $logger->method('errorLog')->willReturn(true); + $logger->expects($this->once())->method('errorLog')->with("ERROR --> Array\n( + [firstName] => John + [lastName] => Doe\n)\n\n", 0); + $this->assertTrue($logger->handle('error', ['firstName' => 'John', 'lastName' => 'Doe'])); + } + + public function testErrorLoggingWithInteger(): void + { + $logger = $this->getMockedHandler(['handles' => ['critical', 'error']]); + $logger->method('errorLog')->willReturn(true); + $logger->expects($this->once())->method('errorLog')->with("ERROR --> 123456\n", 0); + $this->assertTrue($logger->handle('error', 123456)); + } + + public function testErrorLoggingWithObject(): void + { + $logger = $this->getMockedHandler(['handles' => ['critical', 'error']]); + + $obj = new stdClass(); + $obj->firstName = 'John'; + $obj->lastName = 'Doe'; + + $logger->method('errorLog')->willReturn(true); + $logger->expects($this->once())->method('errorLog')->with("ERROR --> stdClass Object\n( + [firstName] => John + [lastName] => Doe\n)\n\n", 0); + $this->assertTrue($logger->handle('error', $obj)); + } + /** * @return ErrorlogHandler&MockObject */ diff --git a/tests/system/Log/Handlers/FileHandlerTest.php b/tests/system/Log/Handlers/FileHandlerTest.php index 050511a63465..6f7889c51be2 100644 --- a/tests/system/Log/Handlers/FileHandlerTest.php +++ b/tests/system/Log/Handlers/FileHandlerTest.php @@ -16,6 +16,7 @@ use CodeIgniter\Test\Mock\MockLogger as LoggerConfig; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamDirectory; +use stdClass; use Tests\Support\Log\Handlers\TestHandler; /** @@ -77,6 +78,100 @@ public function testHandleCreateFile() $this->assertStringContainsString($expectedResult, $line); } + public function testHandleCreateFilePHP() + { + $config = new LoggerConfig(); + $config->handlers[TestHandler::class]['path'] = $this->start; + $config->handlers[TestHandler::class]['fileExtension'] = 'php'; + $logger = new MockFileLogger($config->handlers[TestHandler::class]); + + $logger->setDateFormat('Y-m-d'); + $date = date('Y-m-d'); + $expected = 'log-' . $date . '.php'; + $logger->handle('info', 'This is a test log'); + + $fp = fopen($config->handlers[TestHandler::class]['path'] . $expected, 'rb'); + $result = stream_get_contents($fp); + fclose($fp); + + // did the log file get created? + $expectedResult = "\n\nINFO - " . $date . " --> This is a test log\n"; + $this->assertSame($expectedResult, $result); + } + + public function testHandleWithInteger() + { + $config = new LoggerConfig(); + $config->handlers[TestHandler::class]['path'] = $this->start; + $logger = new MockFileLogger($config->handlers[TestHandler::class]); + + $logger->setDateFormat('Y-m-d'); + $date = date('Y-m-d'); + $expected = 'log-' . $date . '.log'; + $logger->handle('info', 123456); + + $fp = fopen($config->handlers[TestHandler::class]['path'] . $expected, 'rb'); + $result = stream_get_contents($fp); + fclose($fp); + + // did the log file get created? + $expectedResult = 'INFO - ' . $date . " --> 123456\n"; + $this->assertSame($expectedResult, $result); + } + + public function testHandleWithArray() + { + $config = new LoggerConfig(); + $config->handlers[TestHandler::class]['path'] = $this->start; + $logger = new MockFileLogger($config->handlers[TestHandler::class]); + + $logger->setDateFormat('Y-m-d'); + $date = date('Y-m-d'); + $arrData = [ + 'firstName' => 'John', + 'lastName' => 'Doe', + ]; + $expected = 'log-' . $date . '.log'; + $logger->handle('info', print_r($arrData, true)); + + $fp = fopen($config->handlers[TestHandler::class]['path'] . $expected, 'rb'); + $result = stream_get_contents($fp); + fclose($fp); + + // did the log file get created? + $expectedResult = 'INFO - ' . $date . " --> Array\n( + [firstName] => John + [lastName] => Doe\n)\n\n"; + $this->assertSame($expectedResult, $result); + } + + public function testHandleWithObject() + { + $config = new LoggerConfig(); + $config->handlers[TestHandler::class]['path'] = $this->start; + $logger = new MockFileLogger($config->handlers[TestHandler::class]); + + $logger->setDateFormat('Y-m-d'); + $date = date('Y-m-d'); + + $obj = new stdClass(); + $obj->firstName = 'John'; + $obj->lastName = 'Doe'; + + $expected = 'log-' . $date . '.log'; + $logger->handle('info', print_r($obj, true)); + + $fp = fopen($config->handlers[TestHandler::class]['path'] . $expected, 'rb'); + $result = stream_get_contents($fp); + fclose($fp); + + // did the log file get created? + $expectedResult = 'INFO - ' . $date . " --> stdClass Object\n( + [firstName] => John + [lastName] => Doe\n)\n\n"; + $this->assertSame($expectedResult, $result); + } + public function testHandleDateTimeCorrectly() { $config = new LoggerConfig();