[ self::LEVEL_NORMAL, config::DEVEL => self::LEVEL_MINOR, ], self::KEY_TECH => [ self::LEVEL_NORMAL, config::DEVEL => self::LEVEL_MINOR, ], self::KEY_EXCEPTION => [ self::LEVEL_NEVER, config::DEVEL => self::LEVEL_MINOR, ], ]; const LOG_LEVELS = [ self::KEY_USER => [ self::LEVEL_NORMAL, config::DEVEL => self::LEVEL_MINOR, ], self::KEY_TECH => [ self::LEVEL_NORMAL, config::DEVEL => self::LEVEL_MINOR, ], self::KEY_EXCEPTION => [ self::LEVEL_NORMAL, config::DEVEL => self::LEVEL_MINOR, ], ]; const TYPE_LEVELS = [ IMessenger::TYPE_INFO, config::DEVEL => IMessenger::TYPE_DEBUG, ]; const DATE_FORMAT = 'Y-m-d\TH:i:s.u'; function __construct(?array $params=null) { self::set_parametrable_params_defaults($params, [ "print_levels" => static::PRINT_LEVELS, "log_levels" => static::LOG_LEVELS, "type_levels" => static::TYPE_LEVELS, "date_format" => static::DATE_FORMAT, ]); parent::__construct($params); } const PARAMETRABLE_PARAMS_SCHEMA = [ "print_levels" => ["array", null, "niveaux par défaut pour chaque catégorie de message"], "log_levels" => ["array", null, "niveaux par défaut pour chaque catégorie de message"], "type_levels" => ["array", null, "niveaux par défaut des types de message"], "log_output" => [null, null, "destination des messages de logs"], "display_log" => ["bool", false, "faut-il afficher les logs?"], "add_date" => ["?bool", null, "faut-il dater les messages?"], "date_format" => ["string", null, "format de la date"] ]; /** @var array */ protected $ppPrintLevels; function pp_setPrintLevels(array $levels): void { A::merge_nn($this->ppPrintLevels, $levels); } /** @var array */ protected $ppLogLevels; function pp_setLogLevels(array $levels): void { A::merge_nn($this->ppLogLevels, $levels); } /** @var array */ protected $ppTypeLevels; /** @var IWriter */ protected $ppLogOutput; /** @var bool */ protected $ppDisplayLog; /** @var bool */ protected $ppAddDate; /** @var ?string */ protected $ppDateFormat; /** @var IWriter */ protected $logOutput; protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void { if (self::was_parametrable_param_modified($modifiedKeys, "log_output")) { $this->logOutput = $this->ppLogOutput !== null? writer::with($this->ppLogOutput): null; if (!self::was_parametrable_param_modified($modifiedKeys, "add_date")) { $this->ppAddDate = $this->logOutput !== null; } } } function setLevels(?array $printLevels, ?array $logLevels=null, $typeLevels=null): IMessenger { $this->setParametrableParams(SL::filter_z([ "print_levels" => $printLevels, "log_levels" => $logLevels, "type_levels" => $typeLevels, ])); return $this; } protected function _getLevel(string $key, array $levels): int { $level = A::get($levels, $key, self::LEVEL_NORMAL); if (is_int($level)) return $level; $levels = A::with($level); $level = A::get($levels, config::get_profile()); if ($level === null) $level = A::get($levels, 0); if ($level === null) return self::LEVEL_NORMAL; return $level; } function getPrintLevel(string $key): int { return $this->_getLevel($key, $this->ppPrintLevels); } function getLogLevel(string $key): int { return $this->_getLevel($key, $this->ppLogLevels); } function getTypeLevel(): int { $levels = $this->ppTypeLevels; $level = A::get($levels, config::get_profile()); if ($level === null) $level = A::get($levels, 0); if ($level === null) return IMessenger::TYPE_INFO; return $level; } function isLogMessage(?string $msg, int $level, string $msgKey): bool { return $msg !== null && $level >= $this->getLogLevel($msgKey); } function isPrintMessage(?string $msg, int $level, string $msgKey): bool { return $msg !== null && $level >= $this->getPrintLevel($msgKey); } protected function date() { return date_create()->format($this->ppDateFormat); } protected function getString($text): string { return c::string(c::nq($text)); } protected function filterTags(string $text): string { return preg_replace('/<[^>]*>/', "", $text); } protected function filterColors(string $text): string { return $text; } protected function wnl(IWriter $writer, ?bool $color, string $sep, ...$values): void { $values = c::flatten($values); foreach ($values as &$value) { $value = $this->getString($value); if (!$value) $value = false; }; unset($value); $text = $this->filterTags($writer->toString($sep, $values)); if ($color === null) $color = $writer->isatty(); if (!$color) $text = $this->filterColors($text); $writer->wnl($text); } protected function fixDest(?int &$type): void { if ($type === null) $type = 0; if (($type & self::DEST_MASK) === 0) $type += self::DEST_ALL; } protected function shouldLog(?int $type): bool { if ($this->logOutput === null) return false; $this->fixDest($type); return ($type & self::DEST_LOG) != 0; } protected function shouldPrint(?int $type): bool { $this->fixDest($type); return ($type & self::DEST_DISPLAY) != 0 || $this->ppDisplayLog; } ############################################################################# # Sections protected function logStartSection($title): void { $datetime = $this->ppAddDate? $this->date()." ": "\n"; $title = "$datetime>>>> $title <<<<"; $this->wnl($this->logOutput, false, "", $title); } protected function logEndSection(): void { } protected abstract function printStartSection($title, ?int $msgType, ?int $msgLevel): void; protected abstract function printEndSection(): void; /** @var bool */ private $inSection; function isInSection(): bool { return $this->inSection; } function startSection($title, ?int $msgType=null, ?int $msgLevel=null): IMessenger { if ($this->inSection) $this->endSection(); $allowLog = $msgLevel === null || $msgLevel >= $this->getLogLevel(self::KEY_USER); $allowPrint = $msgLevel === null || $msgLevel >= $this->getPrintLevel(self::KEY_USER); if ($allowLog || $allowPrint) { if ($allowLog && $this->shouldLog($msgType)) $this->logStartSection($title); if ($allowPrint && $this->shouldPrint($msgType)) $this->printStartSection($title, $msgType, $msgLevel); $this->inSection = true; } return $this; } function endSection(): IMessenger { if ($this->inSection) { $this->printEndSection(); $this->inSection = false; } return $this; } ############################################################################# # Groupes protected function getGroupCount(?array $group): int { if ($group === null ) return 0; $count = $group["count"]; return $count !== null? $count: 2; } protected function getGroupIndent(?array $group, ?int $indent=null, int $maxCount=1): string { if ($group === null) return ""; if ($indent === null) { $indent = $group["indent"]; $count = $this->getGroupCount($group); if ($count <= $maxCount) $indent--; } return str_repeat(" ", $indent); } protected function getGroupPrefix(?array $group, int $maxCount=1): string { if ($group === null) return ""; $count = $this->getGroupCount($group); if ($count > $maxCount) return ""; return $group["prefix"]." :"; } protected function logStartGroup(array $group): void { if ($this->getGroupCount($group) > 1) { $groupIndent = $this->getGroupIndent($group, $group["indent"] - 1); if ($this->ppAddDate) $groupIndent = self::date()." $groupIndent"; $this->wnl($this->logOutput, false, " ", $groupIndent.">", $group["prefix"]); } } protected function logEndGroup(array $group): void { } protected abstract function printStartGroup(array $group, ?array $groups): void; protected abstract function printEndGroup(array $group): void; private $groups; public function isInGroup(): bool { return $this->groups !== null; } function startGroup($prefix, int $count=null, ?int $msgType=null, ?int $msgLevel=null): IMessenger { $allowLog = $msgLevel === null || $msgLevel >= $this->getLogLevel(self::KEY_USER); $allowPrint = $msgLevel === null || $msgLevel >= $this->getPrintLevel(self::KEY_USER); if ($allowLog || $allowPrint) { $indent = 1; if ($this->groups !== null) { foreach ($this->groups as $group) { if ($group !== null) $indent++; } } $group = [ "indent" => $indent, "prefix" => $prefix, "count" => $count, "type" => $msgType, "level" => $msgLevel, ]; if ($allowLog && $this->shouldLog($msgType)) $this->logStartGroup($group); if ($allowPrint && $this->shouldPrint($msgType)) $this->printStartGroup($group, $this->groups); } else { $group = false; } $this->groups[] = $group; return $this; } function endGroup(): IMessenger { $group = array_pop($this->groups); if ($group) { $type = $group["type"]; if ($this->shouldLog($type)) $this->logEndGroup($group); if ($this->shouldPrint($type)) $this->printEndGroup($group); } if (!$this->groups) $this->groups = null; return $this; } function end(bool $all=false): IMessenger { if ($all) { while ($this->groups) $this->endGroup(); $this->endSection(); } elseif ($this->groups) $this->endGroup(); else $this->endSection(); return $this; } ############################################################################# # Messages const TYPE_PREFIXES = [ # les clés doivent être ordonnées de la plus grande à la plus petite # la 2ème valeur indique s'il faut garder le préfixe s'il y a un result self::LEVEL_CRITICAL => [ self::TYPE_ERROR => ["CRITICAL!", true], self::TYPE_WARNING => ["ATTENTION!", true], self::TYPE_DEBUG => ["IMPORTANT!", true], ], self::LEVEL_MAJOR => [ self::TYPE_ERROR => ["ERROR:", true], self::TYPE_WARNING => ["WARN:", true], self::TYPE_INFO => ["INFO:", false], self::TYPE_DEBUG => ["DEBUG:", true], ], self::LEVEL_NORMAL => [ self::TYPE_ERROR => ["E", true], self::TYPE_WARNING => ["W", true], self::TYPE_INFO => ["", false], self::TYPE_DEBUG => ["D", true], ], self::LEVEL_MINOR => [ self::TYPE_ERROR => ["e", true], self::TYPE_WARNING => ["w", true], self::TYPE_INFO => ["i", false], self::TYPE_DEBUG => ["d", true], ], ]; const RESULT_PREFIXES = [ self::RESULT_FAILURE => "(FAILURE)", self::RESULT_SUCCESS => "(SUCCESS)", self::RESULT_NEUTRAL => "*", self::RESULT_NONE => null, ]; protected function getResultPrefix(int $result, bool $color): ?string { return self::RESULT_PREFIXES[$result]; } protected function getTypePrefixSuffix(int $type, int $level, bool $color, bool $haveResultPrefix): array { $typePrefixSuffix = false; foreach (self::TYPE_PREFIXES as $prefixLevel => $prefixes) { if ($level >= $prefixLevel) { foreach ($prefixes as $prefixType => $prefixValue) { if ($type >= $prefixType) { $typePrefixSuffix = $prefixValue; break; } } } if ($typePrefixSuffix !== false) break; } if ($typePrefixSuffix === false) return [null, null]; elseif ($haveResultPrefix && !$typePrefixSuffix[1]) return [null, null]; $prefix = $typePrefixSuffix[0]; $suffix = ""; return [$prefix, $suffix]; } /** @var Metadata */ private static $message_md; protected static function message_md(): Metadata { return md_utils::ensure_md(self::$message_md , array_merge(self::MESSAGE_SCHEMA, self::MESSAGE_OPTIONS_SCHEMA)); } protected function logMsg( ?array $groups, bool $logUser, $userMsg, bool $logTech, $techMsg, bool $logException, $exceptionMsg, int $type, int $level ): void { $group = A::last($groups); if ($group === false) { # groupe neutralisé return; } $groupIndent = $this->getGroupIndent($group); $groupPrefix = $this->getGroupPrefix($group); if ($this->ppAddDate) $groupIndent = self::date()." $groupIndent"; $color = false; $result = $type & self::RESULT_MASK; $type = $type & self::TYPE_MASK; $resultPrefix = $this->getResultPrefix($result, $color); [$typePrefix, $typeSuffix] = $this->getTypePrefixSuffix($type, $level, $color, $resultPrefix !== null); $prefix = $groupIndent.$typePrefix.$resultPrefix; $logOutput = $this->logOutput; if ($logUser) $this->wnl($logOutput, $color, " ", $prefix, $groupPrefix, $userMsg, $typeSuffix); if ($logTech) $this->wnl($logOutput, $color, " ", $prefix, "TECH:", $techMsg, $typeSuffix); if ($logException) { [$summary, $traceback] = $exceptionMsg; $this->wnl($logOutput, $color, " ", $prefix, "TRACEBACK:", $summary, $typeSuffix); $this->wnl($logOutput, false, "", $traceback); } } protected abstract function getUserMsg($msg): array; protected abstract function getTechMsg($msg): array; protected abstract function getExceptionMsg($exception, $user, $tech, bool $haveTechMsgOrSummary): array; protected abstract function printMsg( ?array $groups , bool $printUser, $userMsg , bool $printTech, $techMsg , bool $printException, $exceptionMsg , int $type, int $level, array $options ): void; protected function processMsgOptions(array $options): void { } function ensureMessage(&$message): void { static::message_md()->ensureSchema($message); $user =& $message[self::KEY_USER]; $tech =& $message[self::KEY_TECH]; $exception =& $message[self::KEY_EXCEPTION]; if ($exception === null) { if ($tech instanceof Throwable || $tech instanceof ExceptionShadow) $exception = $tech; elseif ($user instanceof Throwable || $user instanceof ExceptionShadow) $exception = $user; } if ($tech === null) { if ($user instanceof Throwable || $user instanceof ExceptionShadow) $tech = $user; } } function addMessage($message, int $type, int $level): IMessenger { $allowType = ($type & self::TYPE_MASK) >= $this->getTypeLevel(); if (!$allowType) return $this; $this->fixDest($type); $this->ensureMessage($message); $user = $message[self::KEY_USER]; $logUser = $level >= $this->getLogLevel(self::KEY_USER); $printUser = $level >= $this->getPrintLevel(self::KEY_USER); $userMsg = false; if ($user !== null) { if ($user instanceof UserException) $msg = $user->getUserMessage(); elseif ($user instanceof Throwable || $user instanceof ExceptionShadow) $msg = $user->getMessage(); else $msg = $user; if (!$msg) $printUser = false; else $userMsg = $this->getUserMsg($msg); } $logUser &= $userMsg !== false; $printUser &= $userMsg !== false; $tech = $message[self::KEY_TECH]; $logTech = $level >= $this->getLogLevel(self::KEY_TECH); $printTech = $level >= $this->getPrintLevel(self::KEY_TECH); $techMsg = false; $techSummary = false; $exception = $message[self::KEY_EXCEPTION]; $logException = $exception !== null && $level >= $this->getLogLevel(self::KEY_EXCEPTION);; $printException = $exception !== null && $level >= $this->getPrintLevel(self::KEY_EXCEPTION);; $exceptionMsg = false; if ($tech !== null) { if ($tech instanceof UserException) { $msg = $tech->getTechMessage(); } elseif (($tech instanceof Throwable || $tech instanceof ExceptionShadow) && !$printException) { $techSummary = true; $msg = UserException::get_summary($tech); } else { $msg = $tech; } if (!$msg) $printTech = false; else $techMsg = $this->getTechMsg($msg); } $logTech &= $techMsg !== false; $printTech &= $techMsg !== false; if ($exception !== null) { $exceptionMsg = $this->getExceptionMsg($exception, $user, $tech, $techMsg || $techSummary); } $logException &= $exceptionMsg !== false; $printException &= $exceptionMsg !== false; $options = $message; if ($this->shouldLog($type)) { $this->logMsg( $this->groups, $logUser, $userMsg, $logTech, $techMsg, $logException, $exceptionMsg, $type, $level); } if ($this->shouldPrint($type)) { $this->printMsg( $this->groups, $printUser, $userMsg, $printTech, $techMsg, $printException, $exceptionMsg, $type, $level, $options); } $this->processMsgOptions($options); return $this; } function aresult($result, ?array $args=null, $message=null, ?int $type=null): IMessenger { if ($message !== null) static::message_md()->ensureSchema($message); if (is_callable($result)) { if ($args === null) $args = []; try { $result = func::call($result, ...$args); if ($result === null) { # cas des fonctions void $result = true; } } catch (Exception $e) { $result = $e; } } $type = ($type?: 0) & self::DEST_MASK; $level = self::LEVEL_MAJOR; if ($result instanceof Exception) { $type += self::TYPE_INFO + self::RESULT_FAILURE; $message[self::KEY_USER] = $result; } elseif (is_string($result)) { $type += self::TYPE_INFO + self::RESULT_SUCCESS; $message[self::KEY_USER] = $result; } elseif ($result === null) { $type += self::TYPE_INFO + self::RESULT_NEUTRAL; A::replace_z($message, self::KEY_USER, "en cours"); } elseif ($result) { $type += self::TYPE_INFO + self::RESULT_SUCCESS; A::replace_z($message, self::KEY_USER, "succès"); } else { $type += self::TYPE_INFO + self::RESULT_FAILURE; A::replace_z($message, self::KEY_USER, "échec"); } $this->addMessage($message, $type, $level); if ($this->isInGroup()) $this->endGroup(); return $this; } function astep($message=null, ?int $type=null): IMessenger { $this->aresult(null, null, $message, $type); return $this; } function asuccess($message=null, ?int $type=null): IMessenger { $this->aresult(true, null, $message, $type); return $this; } function afailure($message=null, ?Throwable $e=null, ?int $type=null): IMessenger { static::message_md()->ensureSchema($message); if ($message === null) { $message = $e; } elseif ($message[self::KEY_USER] === null) { $message[self::KEY_USER] = $e; } else { A::replace_z($message, self::KEY_EXCEPTION, $e); } $this->aresult(false, null, $message, $type); return $this; } function action($message, $result=null, ?array $args=null, ?int $type=null, ?int $level=null): IMessenger { $this->startGroup($message, 1, $type, $level); if ($result !== null) $this->aresult($result, $args, null, $type); return $this; } }