From 01ec5a2509a11c9ed26e60fcf632364248e78c2a Mon Sep 17 00:00:00 2001 From: Clebson Moura Date: Tue, 26 Aug 2025 01:14:26 -0300 Subject: [PATCH 1/4] feat: adds initial infrastructure for a logging system within the framework --- composer.json | 3 ++ infra/Support/Log.php | 91 +++++++++++++++++++++++++++++++++++++++++ infra/helper.php | 39 ++++++++++++++++++ public/index.php | 3 +- storage/logs/.gitignore | 2 + 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 infra/Support/Log.php create mode 100644 infra/helper.php create mode 100644 storage/logs/.gitignore diff --git a/composer.json b/composer.json index 9e0c641..d1a06bd 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,9 @@ "name": "clebson/pure-php-oop", "type": "project", "autoload": { + "files": [ + "infra/helper.php" + ], "psr-4": { "App\\": "app/", "Infra\\": "infra/", diff --git a/infra/Support/Log.php b/infra/Support/Log.php new file mode 100644 index 0000000..aac716d --- /dev/null +++ b/infra/Support/Log.php @@ -0,0 +1,91 @@ +filePath = $logsDirectory."/app-{$today}.log"; + + /** @todo delete old log files */ + $fileHandle = fopen($this->filePath, 'a'); + + if (! $fileHandle) { + throw new RuntimeException("Unable to open log file: $this->filePath"); + } + + $this->fileHandle = $fileHandle; + } + + public static function getInstance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + public function info(string $message): void + { + fwrite($this->fileHandle, $this->buildMessage($message, self::LEVEL_INFO)); + } + + public function debug(string $message): void + { + fwrite($this->fileHandle, $this->buildMessage($message, self::LEVEL_DEBUG)); + } + + public function warn(string $message): void + { + fwrite($this->fileHandle, $this->buildMessage($message, self::LEVEL_WARN)); + } + + public function error(string $message): void + { + fwrite($this->fileHandle, $this->buildMessage($message, self::LEVEL_ERROR)); + } + + private function buildMessage(string $message, string $level): string + { + /** @todo php timezone is set to UTC, find a way to make it configurable */ + $timestamp = '['.date('Y-m-d H:i:s', time())."] $level: "; + + return $timestamp.$message.PHP_EOL; + } + + public function __destruct() + { + if ($this->fileHandle !== null) { + fclose($this->fileHandle); + } + } +} diff --git a/infra/helper.php b/infra/helper.php new file mode 100644 index 0000000..69c0d48 --- /dev/null +++ b/infra/helper.php @@ -0,0 +1,39 @@ +info($message); +} + +function debug(string $message): void +{ + $logger = Log::getInstance(); + + $logger->debug($message); +} +function error(string $message): void +{ + $logger = Log::getInstance(); + + $logger->error($message); +} +function warn(string $message): void +{ + $logger = Log::getInstance(); + + $logger->warn($message); +} diff --git a/public/index.php b/public/index.php index b81edb3..56dce9c 100644 --- a/public/index.php +++ b/public/index.php @@ -32,8 +32,7 @@ } catch (Throwable $e) { // Case the application crash // Log the actual error for the developer - // @todo Create proper Application logger - error_log($e->getMessage()); + error((string) $e); // Show a generic error to the user http_response_code(500); diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 263789d62f5c240d70d310d2797de8a0b28d539d Mon Sep 17 00:00:00 2001 From: Clebson Moura Date: Tue, 26 Aug 2025 01:30:40 -0300 Subject: [PATCH 2/4] chore: add missing strict types --- app/Dtos/PostRequestDto.php | 2 ++ infra/Interfaces/RequestDtoInterface.php | 2 ++ infra/helper.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/Dtos/PostRequestDto.php b/app/Dtos/PostRequestDto.php index 38a07bd..2c12e51 100644 --- a/app/Dtos/PostRequestDto.php +++ b/app/Dtos/PostRequestDto.php @@ -1,5 +1,7 @@ Date: Tue, 26 Aug 2025 01:32:10 -0300 Subject: [PATCH 3/4] test: add tests for log feature --- tests/Feature/infra/Support/LogTest.php | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/Feature/infra/Support/LogTest.php diff --git a/tests/Feature/infra/Support/LogTest.php b/tests/Feature/infra/Support/LogTest.php new file mode 100644 index 0000000..74fddd2 --- /dev/null +++ b/tests/Feature/infra/Support/LogTest.php @@ -0,0 +1,53 @@ +toBeTrue(); + }); + + it('put debug message in log file', function () { + $message = 'test debug message'; + + debug($message); + + $today = date('Ymd'); + $logFilePath = storage_path()."/logs/app-$today.log"; + $logFile = file_get_contents($logFilePath); + + expect(str_contains($logFile, $message))->toBeTrue(); + }); + + it('put warn message in log file', function () { + $message = 'test warn message'; + + warn($message); + + $today = date('Ymd'); + $logFilePath = storage_path()."/logs/app-$today.log"; + $logFile = file_get_contents($logFilePath); + + expect(str_contains($logFile, $message))->toBeTrue(); + }); + + it('put error message in log file', function () { + $message = 'test error message'; + + error($message); + + $today = date('Ymd'); + $logFilePath = storage_path()."/logs/app-$today.log"; + $logFile = file_get_contents($logFilePath); + + expect(str_contains($logFile, $message))->toBeTrue(); + }); +}); From 6280c529c5aa8eaf38c91b1c5076f36bec019615 Mon Sep 17 00:00:00 2001 From: Clebson Moura Date: Tue, 26 Aug 2025 01:33:20 -0300 Subject: [PATCH 4/4] test: ignore a few lines from code coverage until we can simulate the file system --- infra/Support/Log.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/infra/Support/Log.php b/infra/Support/Log.php index aac716d..0ca2238 100644 --- a/infra/Support/Log.php +++ b/infra/Support/Log.php @@ -1,5 +1,7 @@ filePath, 'a'); if (! $fileHandle) { + // @codeCoverageIgnoreStart throw new RuntimeException("Unable to open log file: $this->filePath"); + // @codeCoverageIgnoreEnd } $this->fileHandle = $fileHandle; @@ -84,8 +86,10 @@ private function buildMessage(string $message, string $level): string public function __destruct() { + // @codeCoverageIgnoreStart if ($this->fileHandle !== null) { fclose($this->fileHandle); } + // @codeCoverageIgnoreEnd } }