From 68c9164eaadf23015f60af6165848e48c72f4a60 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 8 Sep 2023 18:30:05 +0900 Subject: [PATCH] New ErrorMessage class for frontend return error messages --- .../CoreLibsLoggingErrorMessagesTest.php | 284 ++++++++++++++++++ www/admin/class_test.error_msg.php | 52 ++++ www/admin/class_test.file.php | 2 +- www/admin/class_test.php | 1 + www/lib/CoreLibs/Logging/ErrorMessage.php | 167 ++++++++++ www/lib/CoreLibs/Logging/Logger/Level.php | 15 + .../CoreLibs/Logging/Logger/MessageLevel.php | 51 ++++ www/vendor/composer/autoload_classmap.php | 2 + www/vendor/composer/autoload_static.php | 2 + 9 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php create mode 100644 www/admin/class_test.error_msg.php create mode 100644 www/lib/CoreLibs/Logging/ErrorMessage.php create mode 100644 www/lib/CoreLibs/Logging/Logger/MessageLevel.php diff --git a/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php b/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php new file mode 100644 index 00000000..72012e48 --- /dev/null +++ b/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php @@ -0,0 +1,284 @@ + [ + 'level' => 'ok', + 'str' => 'OK', + 'expected' => 'ok', + ], + 'info' => [ + 'level' => 'info', + 'str' => 'INFO', + 'expected' => 'info', + ], + 'warn' => [ + 'level' => 'warn', + 'str' => 'WARN', + 'expected' => 'warn' + ], + 'warning' => [ + 'level' => 'warning', + 'str' => 'WARN', + 'expected' => 'warn' + ], + 'error' => [ + 'level' => 'error', + 'str' => 'ERROR', + 'expected' => 'error' + ], + 'abort' => [ + 'level' => 'abort', + 'str' => 'ABORT', + 'expected' => 'abort' + ], + 'crash' => [ + 'level' => 'crash', + 'str' => 'CRASH', + 'expected' => 'crash' + ], + 'wrong level' => [ + 'level' => 'wrong', + 'str' => 'WRONG', + 'expected' => 'unknown' + ] + ]; + } + + /** + * Undocumented function + * + * @dataProvider providerErrorMessageLevel + * @testdox error message level: $level will be $expected [$_dataName] + * + * @param string $level + * @param string $str + * @param string $expected + * @return void + */ + public function testErrorMessageLevelOk(string $level, string $str, string $expected): void + { + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testErrorMessages', + 'log_folder' => self::LOG_FOLDER, + 'log_level' => Level::Debug, + ]); + $em = new \CoreLibs\Logging\ErrorMessage($log); + $em->setErrorMsgLevel( + $level, + $str + ); + $this->assertEquals( + [ + 'level' => $expected, + 'str' => $str, + 'id' => '', + 'target' => '', + 'highlight' => [], + ], + $em->getLastErrorMsg() + ); + } + + /** + * Undocumented function + * + * @testdox Test of all methods for n messages [$_dataName] + * + * @return void + */ + public function testErrorMessageOk(): void + { + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testErrorMessages', + 'log_folder' => self::LOG_FOLDER, + 'log_level' => Level::Debug + ]); + $em = new \CoreLibs\Logging\ErrorMessage($log); + $em->setErrorMsg( + '100', + 'info', + 'INFO MESSAGE' + ); + + $this->assertEquals( + [ + 'id' => '100', + 'level' => 'info', + 'str' => 'INFO MESSAGE', + 'target' => '', + 'highlight' => [], + ], + $em->getLastErrorMsg() + ); + $this->assertEquals( + ['100'], + $em->getErrorIds() + ); + $this->assertEquals( + [ + [ + 'id' => '100', + 'level' => 'info', + 'str' => 'INFO MESSAGE', + 'target' => '', + 'highlight' => [], + ] + ], + $em->getErrorMsg() + ); + + $em->setErrorMsg( + '200', + 'error', + 'ERROR MESSAGE' + ); + $this->assertEquals( + [ + 'id' => '200', + 'level' => 'error', + 'str' => 'ERROR MESSAGE', + 'target' => '', + 'highlight' => [], + ], + $em->getLastErrorMsg() + ); + $this->assertEquals( + ['100', '200'], + $em->getErrorIds() + ); + $this->assertEquals( + [ + [ + 'id' => '100', + 'level' => 'info', + 'str' => 'INFO MESSAGE', + 'target' => '', + 'highlight' => [], + ], + [ + 'id' => '200', + 'level' => 'error', + 'str' => 'ERROR MESSAGE', + 'target' => '', + 'highlight' => [], + ] + ], + $em->getErrorMsg() + ); + } + + public function providerErrorMessageLog(): array + { + return [ + 'crash' => [ + 'id' => '300', + 'level' => 'crash', + 'str' => 'CRASH MESSAGE', + 'message' => null, + 'expected' => ' CRASH MESSAGE', + ], + 'crash, message' => [ + 'id' => '300', + 'level' => 'crash', + 'str' => 'CRASH MESSAGE', + 'message' => 'OTHER CRASH MESSAGE', + 'expected' => ' OTHER CRASH MESSAGE', + ], + 'abort' => [ + 'id' => '200', + 'level' => 'abort', + 'str' => 'ABORT MESSAGE', + 'message' => null, + 'expected' => ' ABORT MESSAGE', + ], + 'abort, message' => [ + 'id' => '200', + 'level' => 'abort', + 'str' => 'ABORT MESSAGE', + 'message' => 'OTHER ABORT MESSAGE', + 'expected' => ' OTHER ABORT MESSAGE', + ], + 'unknown' => [ + 'id' => '400', + 'level' => 'wrong level', + 'str' => 'WRONG LEVEL MESSAGE', + 'message' => null, + 'expected' => ' WRONG LEVEL MESSAGE', + ], + 'unknown, message' => [ + 'id' => '400', + 'level' => 'wrong level', + 'str' => 'WRONG LEVEL MESSAGE', + 'message' => 'OTHER WRONG LEVEL MESSAGE', + 'expected' => ' OTHER WRONG LEVEL MESSAGE', + ], + ]; + } + + /** + * Undocumented function + * + * @dataProvider providerErrorMessageLog + * @testdox Test Log writing [$_dataName] + * + * @return void + */ + public function testErrorMessageLog(string $id, string $level, string $str, ?string $message, string $expected) + { + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testErrorMessages', + 'log_folder' => self::LOG_FOLDER, + 'log_level' => Level::Debug, + 'log_per_run' => true + ]); + $em = new \CoreLibs\Logging\ErrorMessage($log); + $em->setErrorMsg( + $id, + $level, + $str, + message: $message + ); + $file_content = file_get_contents( + $log->getLogFolder() . $log->getLogFile() + ) ?: ''; + $this->assertStringContainsString( + $expected, + $file_content + ); + } +} + +// __END__ diff --git a/www/admin/class_test.error_msg.php b/www/admin/class_test.error_msg.php new file mode 100644 index 00000000..b1731206 --- /dev/null +++ b/www/admin/class_test.error_msg.php @@ -0,0 +1,52 @@ + BASE . LOG, + 'log_file_id' => $LOG_FILE_ID, + 'log_per_date' => true, +]); + +$PAGE_NAME = 'TEST CLASS: ERROR MSG'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +print '

' . $PAGE_NAME . '

'; + +$em = new \CoreLibs\Logging\ErrorMessage($log); + +print "FN: " . ml::fromName('Affe')->name . "
"; +print "NU: " . ml::fromValue(100)->name . "
"; +print "NU: " . ml::fromValue(1000)->name . "
"; + +$em->setErrorMsg('123', 'error', 'msg this is bad'); +$em->setErrorMsg('1000', 'info', 'This is good'); +$em->setErrorMsg('9999', 'abort', 'BAD: This is critical (abort)'); +$em->setErrorMsg('10-1000', 'wrong', 'Wrong level: This is emergency'); +print "ErrorsLast:
" . $log->prAr($em->getLastErrorMsg()) . "
"; +print "ErrorsIds:
" . $log->prAr($em->getErrorIds()) . "
"; +print "Errors:
" . $log->prAr($em->getErrorMsg()) . "
"; + +print ""; + +// __END__ diff --git a/www/admin/class_test.file.php b/www/admin/class_test.file.php index 23ff1422..f2c26c17 100644 --- a/www/admin/class_test.file.php +++ b/www/admin/class_test.file.php @@ -15,7 +15,7 @@ define('USE_DATABASE', false); // sample config require 'config.php'; // define log file id -$LOG_FILE_ID = 'classTest-datetime'; +$LOG_FILE_ID = 'classTest-file'; ob_end_flush(); use CoreLibs\Check\File; diff --git a/www/admin/class_test.php b/www/admin/class_test.php index f7645079..c8f528f3 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -115,6 +115,7 @@ $test_files = [ 'class_test.config.link.php' => 'Class Test: CONFIG LINK', 'class_test.config.direct.php' => 'Class Test: CONFIG DIRECT', 'class_test.class-calls.php' => 'Class Test: CLASS CALLS', + 'class_test.error_msg.php' => 'Class Test: ERROR MSG', 'subfolder/class_test.config.direct.php' => 'Class Test: CONFIG DIRECT SUB', ]; diff --git a/www/lib/CoreLibs/Logging/ErrorMessage.php b/www/lib/CoreLibs/Logging/ErrorMessage.php new file mode 100644 index 00000000..a7255d37 --- /dev/null +++ b/www/lib/CoreLibs/Logging/ErrorMessage.php @@ -0,0 +1,167 @@ + */ + private array $error_str = []; + /** @var \CoreLibs\Logging\Logging $log */ + public \CoreLibs\Logging\Logging $log; + + public function __construct( + \CoreLibs\Logging\Logging $log + ) { + $this->log = $log; + } + + /** + * pushes new error message into the error_str array + * error_id: internal Error ID (should be unique) + * level: error level, can only be ok, info, warn, error, abort, crash + * ok and info are positive response: success + * warn: success, but there might be some things that are not 100% ok + * error: input error or error in executing request + * abort: an internal error happened as mandatory information that normally is + * there is missing, or the ACL level that should normally match does not + * will be logged to "critical" + * crash: system failure or critical system problems (db connection failure) + * will be logged as "alert" + * not set: unkown, will be logged as "emergency" + * target/highlight: id target name for frontend where to attach this message + * highlight is a list of other target points to highlight + * + * @param string $error_id Any internal error ID for this error + * @param string $level Error level in ok/info/warn/error + * @param string $str Error message (out) + * @param string $target alternate attachment point for this error message + * @param array $highlight Any additional error data as error OR + * highlight points for field highlights + * @param string|null $message If abort/crash, non localized $str + * @param array $context Additionl info for abort/crash messages + */ + public function setErrorMsg( + string $error_id, + string $level, + string $str, + string $target = '', + array $highlight = [], + ?string $message = null, + array $context = [], + ): void { + $original_level = $level; + $level = MessageLevel::fromName($level)->name; + // if not string set, write message string if set, else level/error id + if (empty($str)) { + $str = $message ?? 'L:' . $level . '|E:' . $error_id; + } + $this->error_str[] = [ + 'id' => $error_id, + 'level' => $level, + 'str' => $str, + 'target' => $target, + 'highlight' => $highlight, + ]; + // write to log for abort/crash + switch ($level) { + case 'abort': + $this->log->critical($message ?? $str, array_merge([ + 'id' => $error_id, + 'level' => $original_level, + ], $context)); + break; + case 'crash': + $this->log->alert($message ?? $str, array_merge([ + 'id' => $error_id, + 'level' => $original_level, + ], $context)); + break; + case 'unknown': + $this->log->emergency($message ?? $str, array_merge([ + 'id' => $error_id, + 'level' => $original_level, + ], $context)); + break; + } + } + + /** + * pushes new error message into the error_str array + * Note, the parameter order is different and does not need an error id + * This is for backend alerts + * + * @param string $level error level (ok/warn/info/error) + * @param string $str error string + * @param string|null $error_id optional error id for precise error lookup + * @param string $target Alternate id name for output target on frontend + * @param array $highlight Any additional error data as error OR + * highlight points for field highlights + * @param string|null $message If abort/crash, non localized $str + * @param array $context Additionl info for abort/crash messages + */ + public function setErrorMsgLevel( + string $level, + string $str, + ?string $error_id = null, + string $target = '', + array $highlight = [], + ?string $message = null, + array $context = [], + ): void { + $this->setErrorMsg($error_id ?? '', $level, $str, $target, $highlight, $message, $context); + } + + // ********************************************************************* + // GETTERS + // ********************************************************************* + + /** + * Returns the current set error content from setErrorMsg method + * + * @return array Error messages array + */ + public function getErrorMsg(): array + { + return $this->error_str; + } + + /** + * Current set error ids + * + * @return array + */ + public function getErrorIds(): array + { + return array_column($this->error_str, 'id'); + } + + /** + * Gets the LAST entry in the array list. + * If nothing found returns empty array set + * + * @return array{id:string,level:string,str:string,target:string,highlight:string[]} Error block + */ + public function getLastErrorMsg(): array + { + return $this->error_str[array_key_last($this->error_str)] ?? [ + 'level' => '', + 'str' => '', + 'id' => '', + 'target' => '', + 'highlight' => [], + ]; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Logging/Logger/Level.php b/www/lib/CoreLibs/Logging/Logger/Level.php index 355dce43..ba497f70 100644 --- a/www/lib/CoreLibs/Logging/Logger/Level.php +++ b/www/lib/CoreLibs/Logging/Logger/Level.php @@ -113,17 +113,32 @@ enum Level: int /** * Returns true if the passed $level is higher or equal to $this + * + * @param Level $level + * @return bool */ public function includes(Level $level): bool { return $this->value <= $level->value; } + /** + * If level is higher than set one + * + * @param Level $level + * @return bool + */ public function isHigherThan(Level $level): bool { return $this->value > $level->value; } + /** + * if level is lower than set one + * + * @param Level $level + * @return bool + */ public function isLowerThan(Level $level): bool { return $this->value < $level->value; diff --git a/www/lib/CoreLibs/Logging/Logger/MessageLevel.php b/www/lib/CoreLibs/Logging/Logger/MessageLevel.php new file mode 100644 index 00000000..40f11704 --- /dev/null +++ b/www/lib/CoreLibs/Logging/Logger/MessageLevel.php @@ -0,0 +1,51 @@ + self::ok, + 'info' => self::info, + 'warn', 'warning' => self::warn, + 'error' => self::error, + 'abort' => self::abort, + 'crash' => self::crash, + default => self::unknown, + }; + } + + /** + * @param int $value + * @return static + */ + public static function fromValue(int $value): self + { + return self::tryFrom($value) ?? self::unknown; + } +} + +// __END__ diff --git a/www/vendor/composer/autoload_classmap.php b/www/vendor/composer/autoload_classmap.php index 936b320c..ebec90f5 100644 --- a/www/vendor/composer/autoload_classmap.php +++ b/www/vendor/composer/autoload_classmap.php @@ -57,8 +57,10 @@ return array( 'CoreLibs\\Language\\Core\\StringReader' => $baseDir . '/lib/CoreLibs/Language/Core/StringReader.php', 'CoreLibs\\Language\\GetLocale' => $baseDir . '/lib/CoreLibs/Language/GetLocale.php', 'CoreLibs\\Language\\L10n' => $baseDir . '/lib/CoreLibs/Language/L10n.php', + 'CoreLibs\\Logging\\ErrorMessage' => $baseDir . '/lib/CoreLibs/Logging/ErrorMessage.php', 'CoreLibs\\Logging\\Logger\\Flag' => $baseDir . '/lib/CoreLibs/Logging/Logger/Flag.php', 'CoreLibs\\Logging\\Logger\\Level' => $baseDir . '/lib/CoreLibs/Logging/Logger/Level.php', + 'CoreLibs\\Logging\\Logger\\MessageLevel' => $baseDir . '/lib/CoreLibs/Logging/Logger/MessageLevel.php', 'CoreLibs\\Logging\\Logging' => $baseDir . '/lib/CoreLibs/Logging/Logging.php', 'CoreLibs\\Output\\Form\\Elements' => $baseDir . '/lib/CoreLibs/Output/Form/Elements.php', 'CoreLibs\\Output\\Form\\Generate' => $baseDir . '/lib/CoreLibs/Output/Form/Generate.php', diff --git a/www/vendor/composer/autoload_static.php b/www/vendor/composer/autoload_static.php index b78b744a..1d1bc678 100644 --- a/www/vendor/composer/autoload_static.php +++ b/www/vendor/composer/autoload_static.php @@ -108,8 +108,10 @@ class ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9 'CoreLibs\\Language\\Core\\StringReader' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/Core/StringReader.php', 'CoreLibs\\Language\\GetLocale' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/GetLocale.php', 'CoreLibs\\Language\\L10n' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/L10n.php', + 'CoreLibs\\Logging\\ErrorMessage' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/ErrorMessage.php', 'CoreLibs\\Logging\\Logger\\Flag' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logger/Flag.php', 'CoreLibs\\Logging\\Logger\\Level' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logger/Level.php', + 'CoreLibs\\Logging\\Logger\\MessageLevel' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logger/MessageLevel.php', 'CoreLibs\\Logging\\Logging' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logging.php', 'CoreLibs\\Output\\Form\\Elements' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/Elements.php', 'CoreLibs\\Output\\Form\\Generate' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/Generate.php',