From 0cacd89313d7a8e3b11f9d636e78f6580cf0fd3b Mon Sep 17 00:00:00 2001 From: rldhont Date: Tue, 24 Jun 2025 17:00:54 +0200 Subject: [PATCH 1/4] Start Psr-3 Logger Require psr/log:2.* because psr/log 3.* is in conflict with symfony/console v5.4.47 ``` Your requirements could not be resolved to an installable set of packages. Problem 1 - symfony/console v5.4.47 conflicts with psr/log 3.0.2. - symfony/console v5.4.47 conflicts with psr/log 3.0.1. - symfony/console v5.4.47 conflicts with psr/log 3.0.0. - jelix/jelix dev-jelix-1.8.x requires symfony/console ^5.4.26 -> satisfiable by symfony/console[v5.4.47]. - jelix/jelix is locked to version dev-jelix-1.8.x and an update of this package was not requested. - Root composer.json requires psr/log ^3.0 -> satisfiable by psr/log[3.0.0, 3.0.1, 3.0.2]. ``` symfony/console could not be updated to version v7 because jelix-1.8.x requires symfony/console ^5.4.26 --- lizmap/composer.json | 3 ++- lizmap/modules/lizmap/lib/Logger/Logger.php | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 lizmap/modules/lizmap/lib/Logger/Logger.php diff --git a/lizmap/composer.json b/lizmap/composer.json index eaf9dd494a..77708b995d 100644 --- a/lizmap/composer.json +++ b/lizmap/composer.json @@ -15,7 +15,8 @@ "violet/streaming-json-encoder": "^1.1.5", "guzzlehttp/guzzle": "^7.7.0", "halaxa/json-machine": "^1.1", - "kevinrob/guzzle-cache-middleware": "^6.0" + "kevinrob/guzzle-cache-middleware": "^6.0", + "psr/log": "2.*" }, "minimum-stability": "stable", "config": { diff --git a/lizmap/modules/lizmap/lib/Logger/Logger.php b/lizmap/modules/lizmap/lib/Logger/Logger.php new file mode 100644 index 0000000000..a75ecf5f97 --- /dev/null +++ b/lizmap/modules/lizmap/lib/Logger/Logger.php @@ -0,0 +1,21 @@ + Date: Tue, 24 Jun 2025 17:32:48 +0200 Subject: [PATCH 2/4] Update Logger --- lizmap/app/system/mainconfig.ini.php | 10 ++ lizmap/modules/lizmap/lib/Logger/Logger.php | 139 +++++++++++++++++++- tests/units/classes/Log/LoggerTest.php | 137 +++++++++++++++++++ 3 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 tests/units/classes/Log/LoggerTest.php diff --git a/lizmap/app/system/mainconfig.ini.php b/lizmap/app/system/mainconfig.ini.php index ef99e4673d..c80beefa90 100644 --- a/lizmap/app/system/mainconfig.ini.php +++ b/lizmap/app/system/mainconfig.ini.php @@ -191,9 +191,14 @@ [logger] _all= default=file +emergency=file +alert=file +critical=file error=file warning=file notice=file +info=file +debug=file ;deprecated=syslog strict=file ;sql=syslog @@ -203,9 +208,14 @@ [fileLogger] default=messages.log +emergency=errors.log +alert=errors.log +critical=errors.log error=errors.log warning=errors.log notice=errors.log +info=errors.log +debug=errors.log strict=errors.log ;metric=time.log auth=messages.log diff --git a/lizmap/modules/lizmap/lib/Logger/Logger.php b/lizmap/modules/lizmap/lib/Logger/Logger.php index a75ecf5f97..12f2855b32 100644 --- a/lizmap/modules/lizmap/lib/Logger/Logger.php +++ b/lizmap/modules/lizmap/lib/Logger/Logger.php @@ -4,9 +4,109 @@ use Psr\Log\AbstractLogger; use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +/** + * Logger is an implementation of the PSR-3 Logger Interface. + * + * This logger wraps \jLog with a log level. + * + * @author 3liz + * + * @see https://www.php-fig.org/psr/psr-3/ + * @see https://jelix.org/documentation/jelix/1.8/api/jLog + */ class Logger extends AbstractLogger { + /** + * Levels ordered from most important to least important + * as defined in RFC 5424. + * + * @var string[] + * + * @see LogLevel + */ + public const LogLevels = array( + LogLevel::EMERGENCY, + LogLevel::ALERT, + LogLevel::CRITICAL, + LogLevel::ERROR, + LogLevel::WARNING, + LogLevel::NOTICE, + LogLevel::INFO, + LogLevel::DEBUG, + ); + + /** + * The default log level: error. + * + * @var string + */ + public const DefaultLevel = LogLevel::ERROR; + + /** + * The minimum logging level at which this logger will be used. + * + * @var string + */ + protected $level; + + /** + * Initialise the logger with a log level. + * + * @param string $level The minimum logging level at which this logger will be used + * + * @throws InvalidArgumentException + * + * @see LogLevel + * @see setLevel() + * @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel + */ + public function __construct(string $level = LogLevel::ERROR) + { + if (!in_array($level, self::LogLevels)) { + throw new InvalidArgumentException('Invalid log level'); + } + $this->level = $level; + } + + /** + * Get log level. + */ + public function getLevel(): string + { + return $this->level; + } + + /** + * Set log level. + * + * @param string $level + * + * @throws InvalidArgumentException + */ + public function setLevel($level): void + { + if (!in_array($level, self::LogLevels)) { + throw new InvalidArgumentException('Invalid log level'); + } + $this->level = $level; + } + + /** + * Check if the given level is high enough to be logged. + * + * This is used to avoid unnecessary logging. + * For example, if the logger is set to LogLevel::ERROR, then LogLevel::INFO will not be logged. + * But LogLevel::ERROR will be logged. + * if the level is not in the list of LogLevels, it will be considered as not high enough. + */ + public function isLevelHighEnough(string $level): bool + { + return in_array($level, self::LogLevels) + && array_search($level, self::LogLevels) <= array_search($this->level, self::LogLevels); + } + /** * Logs with an arbitrary level. * @@ -14,8 +114,43 @@ class Logger extends AbstractLogger * * @throws InvalidArgumentException */ - public function log($level, string|\Stringable $message, array $context = array()) + public function log($level, string|\Stringable $message, array $context = array()): void { - \jLog::log($message, $level); + if (!in_array($level, self::LogLevels)) { + throw new InvalidArgumentException('Invalid log level'); + } + + // If the level is not high enough, return early. + if ($this->isLevelHighEnough($level)) { + return; + } + + \jLog::log( + $this->interpolate((string) $message, $context), + $level + ); + } + + /** + * Interpolates context values into message placeholders according to PSR-3 rules. + * + * This method replaces placeholders in the message with actual values + * from the context array. + * + * @param string $message Message with placeholders + * @param array $context Values to replace placeholders + * + * @return string Interpolated message + * + * @see https://www.php-fig.org/psr/psr-3/#12-message + */ + private function interpolate(string $message, array $context = array()): string + { + $replace = array(); + foreach ($context as $key => $val) { + $replace['{'.$key.'}'] = $val; + } + + return strtr($message, $replace); } } diff --git a/tests/units/classes/Log/LoggerTest.php b/tests/units/classes/Log/LoggerTest.php new file mode 100644 index 0000000000..07c3e90ee1 --- /dev/null +++ b/tests/units/classes/Log/LoggerTest.php @@ -0,0 +1,137 @@ +assertInstanceOf(Logger::class, $logger); + $this->assertEquals(Logger::DefaultLevel, $logger->getLevel()); + $this->assertEquals(LogLevel::ERROR, $logger->getLevel()); + + $this->assertTrue($logger->isLevelHighEnough('error')); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::EMERGENCY)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::CRITICAL)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::ALERT)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::ERROR)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::WARNING)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::NOTICE)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::INFO)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::DEBUG)); + + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[0])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[1])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[2])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[3])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[4])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[5])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[6])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[7])); + $this->assertFalse($logger->isLevelHighEnough('default')); + } + + public function testConstruct(): void + { + $logger = new Logger(LogLevel::EMERGENCY); + $this->assertInstanceOf(Logger::class, $logger); + $this->assertEquals(LogLevel::EMERGENCY, $logger->getLevel()); + $this->assertNotEquals(Logger::DefaultLevel, $logger->getLevel()); + + $this->assertTrue($logger->isLevelHighEnough('emergency')); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::EMERGENCY)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::CRITICAL)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::ALERT)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::ERROR)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::WARNING)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::NOTICE)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::INFO)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::DEBUG)); + + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[0])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[1])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[2])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[3])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[4])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[5])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[6])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[7])); + $this->assertFalse($logger->isLevelHighEnough('default')); + + $logger = new Logger(LogLevel::DEBUG); + $this->assertInstanceOf(Logger::class, $logger); + $this->assertEquals(LogLevel::DEBUG, $logger->getLevel()); + $this->assertNotEquals(Logger::DefaultLevel, $logger->getLevel()); + + $this->assertTrue($logger->isLevelHighEnough('debug')); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::EMERGENCY)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::CRITICAL)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::ALERT)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::ERROR)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::WARNING)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::NOTICE)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::INFO)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::DEBUG)); + + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[0])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[1])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[2])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[3])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[4])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[5])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[6])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[7])); + $this->assertFalse($logger->isLevelHighEnough('default')); + + $logger = new Logger('warning'); + $this->assertInstanceOf(Logger::class, $logger); + $this->assertEquals(LogLevel::WARNING, $logger->getLevel()); + $this->assertNotEquals(Logger::DefaultLevel, $logger->getLevel()); + + $this->assertTrue($logger->isLevelHighEnough('error')); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::EMERGENCY)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::CRITICAL)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::ALERT)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::ERROR)); + $this->assertTrue($logger->isLevelHighEnough(LogLevel::WARNING)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::NOTICE)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::INFO)); + $this->assertFalse($logger->isLevelHighEnough(LogLevel::DEBUG)); + + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[0])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[1])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[2])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[3])); + $this->assertTrue($logger->isLevelHighEnough(Logger::LogLevels[4])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[5])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[6])); + $this->assertFalse($logger->isLevelHighEnough(Logger::LogLevels[7])); + $this->assertFalse($logger->isLevelHighEnough('default')); + } + + public function testInvalidArgument(): void + { + $logger = new Logger(); + $this->assertInstanceOf(Logger::class, $logger); + $this->expectException(InvalidArgumentException::class); + $logger->log('invalid', 'message'); + + $this->expectException(InvalidArgumentException::class); + $logger->isLevelHighEnough('invalid'); + + $this->expectException(InvalidArgumentException::class); + $logger->setLevel('invalid'); + + $this->expectException(InvalidArgumentException::class); + $invalidLogger = new Logger('invalid'); + } +} From 828c28ee9f041a59b79495045db15093ab200d40 Mon Sep 17 00:00:00 2001 From: rldhont Date: Wed, 25 Jun 2025 18:54:36 +0200 Subject: [PATCH 3/4] Using the new Logger --- .../modules/lizmap/classes/lizmap.class.php | 20 ++++++++++++++++++- .../modules/lizmap/lib/App/JelixContext.php | 15 ++++++++++++-- ...QgisFormValueRelationDynamicDatasource.php | 5 ++++- .../lizmap/lib/Project/ProjectConfig.php | 2 +- .../lizmap/lib/Project/QgisProject.php | 7 +++++-- .../modules/lizmap/lib/Request/OGCRequest.php | 6 +++--- lizmap/modules/lizmap/lib/Request/Proxy.php | 13 +++++++----- .../lib/Request/RequestCacheStorage.php | 4 ++-- .../modules/lizmap/lib/Request/WMSRequest.php | 16 +++++++-------- .../lizmap/lib/Request/WMTSRequest.php | 2 +- 10 files changed, 64 insertions(+), 26 deletions(-) diff --git a/lizmap/modules/lizmap/classes/lizmap.class.php b/lizmap/modules/lizmap/classes/lizmap.class.php index cccdf14426..a71846e106 100644 --- a/lizmap/modules/lizmap/classes/lizmap.class.php +++ b/lizmap/modules/lizmap/classes/lizmap.class.php @@ -16,6 +16,7 @@ use Lizmap\Logger as Log; use Lizmap\Logger\Config as LogConfig; use Lizmap\Logger\Item as LogItem; +use Lizmap\Logger\Logger; use Lizmap\Project\Project; /** @@ -54,6 +55,11 @@ class lizmap */ protected static $appContext; + /** + * @var null|Logger The logger instance + */ + protected static $logger; + /** * this is a static class, so private constructor. */ @@ -87,6 +93,18 @@ public static function getAppContext() return self::$appContext; } + /** + * @return Logger The logger instance + */ + public static function getLogger() + { + if (!self::$logger) { + self::$logger = new Logger(); + } + + return self::$logger; + } + public static function saveServices() { $ini = new IniModifier(jApp::varConfigPath('lizmapConfig.ini.php')); @@ -495,6 +513,6 @@ public static function logMetric($label, $service, $payload = array()) $logMessage = new Log\MetricsLogMessage($log, 'metric'); - jLog::log($logMessage); + self::getLogger()->info($logMessage); } } diff --git a/lizmap/modules/lizmap/lib/App/JelixContext.php b/lizmap/modules/lizmap/lib/App/JelixContext.php index b3c225f721..1832a2b373 100644 --- a/lizmap/modules/lizmap/lib/App/JelixContext.php +++ b/lizmap/modules/lizmap/lib/App/JelixContext.php @@ -14,6 +14,7 @@ namespace Lizmap\App; use Jelix\IniFile\IniModifier; +use Lizmap\Logger\Logger; class JelixContext implements AppContextInterface { @@ -210,7 +211,11 @@ public function flushCache($profile = '') */ public function logMessage($message, $cat = 'default') { - \jLog::log($message, $cat); + if (in_array($cat, Logger::LogLevels)) { + \lizmap::getLogger()->log($cat, $message); + } else { + \jLog::log($message, $cat); + } } /** @@ -221,7 +226,13 @@ public function logMessage($message, $cat = 'default') */ public function logException($exception, $cat = 'default') { - \jLog::logEx($exception, $cat); + if (in_array($cat, Logger::LogLevels)) { + if (\lizmap::getLogger()->isLevelHighEnough($cat)) { + \jLog::logEx($exception, $cat); + } + } else { + \jLog::logEx($exception, $cat); + } } /** diff --git a/lizmap/modules/lizmap/lib/Form/QgisFormValueRelationDynamicDatasource.php b/lizmap/modules/lizmap/lib/Form/QgisFormValueRelationDynamicDatasource.php index 24dd3e9480..1a8b23c9f0 100644 --- a/lizmap/modules/lizmap/lib/Form/QgisFormValueRelationDynamicDatasource.php +++ b/lizmap/modules/lizmap/lib/Form/QgisFormValueRelationDynamicDatasource.php @@ -62,7 +62,10 @@ public function getData($form) if ($wkt && \lizmapWkt::check($wkt)) { $geom = \lizmapWkt::parse($wkt); if ($geom === null) { - \jLog::log('Parsing WKT failed! '.$wkt, 'error'); + \lizmap::getLogger()->error( + 'Parsing WKT failed! {wkt}', + array('wkt' => $wkt) + ); } } } else { diff --git a/lizmap/modules/lizmap/lib/Project/ProjectConfig.php b/lizmap/modules/lizmap/lib/Project/ProjectConfig.php index 57ff5008ba..c2fdd0fb0b 100644 --- a/lizmap/modules/lizmap/lib/Project/ProjectConfig.php +++ b/lizmap/modules/lizmap/lib/Project/ProjectConfig.php @@ -649,7 +649,7 @@ private function parseDatavizTreeNode($node, $level, $debug) $active = ($n == 0) ? 'active' : ''; if ($debug) { - \jLog::log("Node {$subNode->name} - n = {$n} ET active = {$active}"); + \lizmap::getLogger()->debug("Node {$subNode->name} - n = {$n} ET active = {$active}"); } $item = $prefix.'