diff --git a/dependency_injection/services.php b/dependency_injection/services.php
index feb0727550f..7408857b162 100644
--- a/dependency_injection/services.php
+++ b/dependency_injection/services.php
@@ -36,7 +36,6 @@
use Glpi\DependencyInjection\PublicService;
use Glpi\Error\ErrorHandler;
-use Glpi\Log\LegacyGlobalLogger;
return static function (ContainerConfigurator $container): void {
$projectDir = dirname(__DIR__);
@@ -66,5 +65,5 @@
* Override Symfony's logger.
* @see \Symfony\Component\HttpKernel\DependencyInjection\LoggerPass
*/
- $services->set('logger', LegacyGlobalLogger::class);
+ $services->set('logger')->synthetic();
};
diff --git a/phpunit/functional/Glpi/Exception/LogLineFormatterTest.php b/phpunit/functional/Glpi/Exception/LogLineFormatterTest.php
index 9e2dea6b9f5..a3afb538ed1 100644
--- a/phpunit/functional/Glpi/Exception/LogLineFormatterTest.php
+++ b/phpunit/functional/Glpi/Exception/LogLineFormatterTest.php
@@ -34,7 +34,7 @@
namespace tests\units\Glpi\Exception;
-use Glpi\Error\LogLineFormatter;
+use Glpi\Log\LogLineFormatter;
use PHPUnit\Framework\TestCase;
class LogLineFormatterTest extends TestCase
diff --git a/src/CommonGLPI.php b/src/CommonGLPI.php
index 32d860ba8d9..b810450be56 100644
--- a/src/CommonGLPI.php
+++ b/src/CommonGLPI.php
@@ -1197,7 +1197,6 @@ public function getHeaderName(): string
*/
public function display($options = [])
{
- trigger_error('Error triggerred', E_USER_WARNING);
// Item might already be loaded, skip load and rights checks
$item_loaded = $options['loaded'] ?? false;
unset($options['loaded']);
diff --git a/src/GLPI.php b/src/GLPI.php
index 56ab391064b..68f48ad171d 100644
--- a/src/GLPI.php
+++ b/src/GLPI.php
@@ -33,7 +33,7 @@
* ---------------------------------------------------------------------
*/
-use Glpi\Error\LogLineFormatter;
+use Glpi\Log\LogLineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LogLevel;
@@ -66,50 +66,4 @@ class GLPI
*/
public const ENV_DEVELOPMENT = 'development';
- /**
- * Init logger
- *
- * @return void
- */
- public function initLogger()
- {
- $errorHandler = new \Glpi\Error\ErrorHandler();
- $errorHandler::register($errorHandler);
-
- /**
- * @var \Psr\Log\LoggerInterface $PHPLOGGER
- */
- global $PHPLOGGER;
-
- if (defined('GLPI_LOG_LVL')) {
- $log_level = GLPI_LOG_LVL;
- } else {
- switch (GLPI_ENVIRONMENT_TYPE) {
- case self::ENV_DEVELOPMENT:
- // All error/messages are logs, including deprecations.
- $log_level = LogLevel::DEBUG;
- break;
- case self::ENV_TESTING:
- // Silent deprecation and info, as they should have no functional impact.
- // Keep notices as they have may indicate that code is not correctly handling a specific case.
- $log_level = LogLevel::NOTICE;
- break;
- case self::ENV_STAGING:
- case self::ENV_PRODUCTION:
- default:
- // Keep only warning/error messages.
- $log_level = LogLevel::WARNING;
- break;
- }
- }
-
- $PHPLOGGER = new Logger('glpiphplog');
- $handler = new StreamHandler(
- GLPI_LOG_DIR . "/php-errors.log",
- $log_level
- );
- $handler->setFormatter(new LogLineFormatter());
-
- $PHPLOGGER->pushHandler($handler);
- }
}
diff --git a/src/Glpi/Application/SystemConfigurator.php b/src/Glpi/Application/SystemConfigurator.php
index d3c2641e1ba..cb78665dd77 100644
--- a/src/Glpi/Application/SystemConfigurator.php
+++ b/src/Glpi/Application/SystemConfigurator.php
@@ -35,16 +35,25 @@
namespace Glpi\Application;
+use Glpi\Error\ErrorHandler;
+use Glpi\Log\GlpiLogHandler;
+use Monolog\Logger;
+use Psr\Log\LoggerInterface;
+
final class SystemConfigurator
{
+ private LoggerInterface $logger;
+
public function __construct(private string $root_dir, private ?string $env)
{
+ $this->computeConstants();
+ $this->setSessionConfiguration();
+ $this->initLogger();
}
- public function __invoke(): void
+ public function getLogger(): LoggerInterface
{
- $this->computeConstants();
- $this->setSessionConfiguration();
+ return $this->logger;
}
private function computeConstants(): void
@@ -245,4 +254,21 @@ private function setSessionConfiguration(): void
// for every GLPI instance, enven if they are served by the same server (mostly for dev envs).
session_name('glpi_' . \hash('sha512', $this->root_dir . ($_SERVER['HTTP_HOST'] ?? '') . ($_SERVER['SERVER_PORT'] ?? '')));
}
+
+ private function initLogger(): void
+ {
+ /**
+ * @var LoggerInterface $PHPLOGGER
+ */
+ global $PHPLOGGER;
+
+ $PHPLOGGER = new Logger('glpi');
+ $PHPLOGGER->pushHandler(new GlpiLogHandler());
+
+ $this->logger = $PHPLOGGER;
+
+ $errorHandler = new ErrorHandler($PHPLOGGER);
+ $errorHandler::register($errorHandler);
+
+ }
}
diff --git a/src/Glpi/Controller/CentralController.php b/src/Glpi/Controller/CentralController.php
index c64149db06b..647bffbf9c4 100644
--- a/src/Glpi/Controller/CentralController.php
+++ b/src/Glpi/Controller/CentralController.php
@@ -69,6 +69,8 @@ public function __invoke(Request $request): Response
return new RedirectResponse($url);
}
+ throw new \RuntimeException('Noop');
+
return $this->render('pages/central/index.html.twig', [
'central' => new Central(),
]);
diff --git a/src/Glpi/Controller/ErrorController.php b/src/Glpi/Controller/ErrorController.php
index 3aae8103f44..6d68fa4c736 100644
--- a/src/Glpi/Controller/ErrorController.php
+++ b/src/Glpi/Controller/ErrorController.php
@@ -36,7 +36,6 @@
use Config;
use DBConnection;
-use Glpi\Application\ErrorUtils;
use Html;
use Session;
use Symfony\Component\ErrorHandler\Error\OutOfMemoryError;
@@ -57,12 +56,17 @@ public function __invoke(Request $request, ?\Throwable $exception = null): Respo
return new Response('', 500);
}
- $this->logException($exception, $request);
+ $this->logHttpException($exception, $request);
return $this->getErrorResponse($exception, $request);
}
- private function logException(\Throwable $exception, Request $request): void
+ /**
+ * @TODO: create a specific handler in our logger so that HTTP errors are logged differently according to this method ⬇
+ *
+ * @see \Glpi\Log\GlpiLogHandler::canHandle
+ */
+ private function logHttpException(\Throwable $exception, Request $request): void
{
if (
$exception instanceof HttpExceptionInterface
@@ -78,29 +82,23 @@ private function logException(\Throwable $exception, Request $request): void
$user_id = Session::getLoginUserID() ?: 'Anonymous';
- switch ($exception::class) {
- case AccessDeniedHttpException::class:
- $message = sprintf(
- 'User ID: `%s` tried to access or perform an action on `%s` with insufficient rights.',
- $user_id,
- $requested_uri
- );
- break;
- case NotFoundHttpException::class:
- $message = sprintf(
- 'User ID: `%s` tried to access a non-existent item on `%s`.',
- $user_id,
- $requested_uri
- );
- break;
- default:
- $message = sprintf(
- 'User ID: `%s` tried to execute an invalid request on `%s`.',
- $user_id,
- $requested_uri
- );
- break;
- }
+ $message = match ($exception::class) {
+ AccessDeniedHttpException::class => sprintf(
+ 'User ID: `%s` tried to access or perform an action on `%s` with insufficient rights.',
+ $user_id,
+ $requested_uri
+ ),
+ NotFoundHttpException::class => sprintf(
+ 'User ID: `%s` tried to access a non-existent item on `%s`.',
+ $user_id,
+ $requested_uri
+ ),
+ default => sprintf(
+ 'User ID: `%s` tried to execute an invalid request on `%s`.',
+ $user_id,
+ $requested_uri
+ ),
+ };
if (($exception_message = $exception->getMessage()) !== '') {
$message .= sprintf('Additional information: %s', $exception_message);
@@ -119,10 +117,6 @@ private function logException(\Throwable $exception, Request $request): void
}
Toolbox::logInFile('access-errors', $message);
- } else {
- // Other errors are logged in the `php-errors` log
- ErrorUtils::logException($exception);
- ErrorUtils::outputExceptionMessage($exception);
}
}
diff --git a/src/Glpi/Error/ErrorDisplayHandler/HtmlErrorDisplayHandler.php b/src/Glpi/Error/ErrorDisplayHandler/HtmlErrorDisplayHandler.php
index 0a9412f542f..08c9983e8ed 100644
--- a/src/Glpi/Error/ErrorDisplayHandler/HtmlErrorDisplayHandler.php
+++ b/src/Glpi/Error/ErrorDisplayHandler/HtmlErrorDisplayHandler.php
@@ -47,20 +47,30 @@ public static function setCurrentRequest(Request $request): void
public function canOutput(string $log_level, string $env): bool
{
- return self::$currentRequest !== null;
+ if (!self::$currentRequest) {
+ return false;
+ }
+ $is_dev_env = $env === \GLPI::ENV_DEVELOPMENT;
+ $is_debug_mode = isset($_SESSION['glpi_use_mode']) && $_SESSION['glpi_use_mode'] == \Session::DEBUG_MODE;
+
+ if (
+ !$is_dev_env // error messages are always displayed in development environment
+ && !$is_debug_mode // error messages are always displayed in debug mode
+ ) {
+ return false;
+ }
+
+ /**
+ * @see Request::initializeFormats
+ */
+ $type = self::$currentRequest->getPreferredFormat();
+
+ return $type === 'html';
}
public function displayErrorMessage(string $error_type, string $message, string $log_level, mixed $env): void
{
- $req = self::$currentRequest;
-
- $types = $req->getAcceptableContentTypes();
-
- if (in_array('text/html', $types)) {
- echo '
'
- . '' . \htmlescape($error_type) . ': ' . \htmlescape($message) . '
';
- } else {
- \trigger_error('Cannot display error in HTTP context for requests that do not accept HTML as a response.', \E_USER_WARNING);
- }
+ echo ''
+ . '' . \htmlescape($error_type) . ': ' . \htmlescape($message) . '
';
}
}
diff --git a/src/Glpi/Error/ErrorDisplayHandler/EchoDisplayHandler.php b/src/Glpi/Error/ErrorDisplayHandler/LegacyCliDisplayHandler.php
similarity index 93%
rename from src/Glpi/Error/ErrorDisplayHandler/EchoDisplayHandler.php
rename to src/Glpi/Error/ErrorDisplayHandler/LegacyCliDisplayHandler.php
index 1819dc1de35..c2644036c4a 100644
--- a/src/Glpi/Error/ErrorDisplayHandler/EchoDisplayHandler.php
+++ b/src/Glpi/Error/ErrorDisplayHandler/LegacyCliDisplayHandler.php
@@ -34,11 +34,11 @@
namespace Glpi\Error\ErrorDisplayHandler;
-final class EchoDisplayHandler implements ErrorDisplayHandler
+final class LegacyCliDisplayHandler implements ErrorDisplayHandler
{
public function canOutput(string $log_level, string $env): bool
{
- return true;
+ return \isCommandLine();
}
public function displayErrorMessage(string $error_type, string $message, string $log_level, mixed $env): void
diff --git a/src/Glpi/Error/ErrorHandler.php b/src/Glpi/Error/ErrorHandler.php
index b36ad4932f3..ed0f415dc28 100644
--- a/src/Glpi/Error/ErrorHandler.php
+++ b/src/Glpi/Error/ErrorHandler.php
@@ -36,13 +36,12 @@
use GLPI;
use Glpi\Error\ErrorDisplayHandler\ConsoleErrorDisplayHandler;
-use Glpi\Error\ErrorDisplayHandler\EchoDisplayHandler;
+use Glpi\Error\ErrorDisplayHandler\LegacyCliDisplayHandler;
use Glpi\Error\ErrorDisplayHandler\HtmlErrorDisplayHandler;
use Glpi\Error\ErrorDisplayHandler\ErrorDisplayHandler;
use Monolog\Logger;
+use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
-use Symfony\Component\Console\Output\ConsoleOutput;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\ErrorHandler\BufferingLogger;
use Symfony\Component\ErrorHandler\ErrorHandler as BaseErrorHandler;
@@ -91,21 +90,21 @@ final class ErrorHandler extends BaseErrorHandler
*/
private static bool $enable_output = true;
- private static Logger $currentLogger;
+ private static LoggerInterface $currentLogger;
private string $env;
- public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false, string $env = \GLPI_ENVIRONMENT_TYPE)
+ public function __construct(LoggerInterface $logger)
{
- parent::__construct($bootstrappingLogger, $debug);
+ parent::__construct();
- $this->env = $env;
+ $this->env = \GLPI_ENVIRONMENT_TYPE;
$this->scopeAt(self::FATAL_ERRORS, true);
$this->screamAt(self::FATAL_ERRORS, true);
$this->traceAt(self::FATAL_ERRORS, true);
$this->throwAt(self::FATAL_ERRORS, true);
- $this->configureLogger();
+ self::$currentLogger = $logger;
$this->configureErrorDisplay();
}
@@ -119,12 +118,8 @@ public static function disableOutput(): void
self::$enable_output = false;
}
- public static function getCurrentLogger(): Logger
+ public static function getCurrentLogger(): LoggerInterface
{
- if (!self::$currentLogger) {
- throw new \RuntimeException('Logger was fetched from an unbuilt ErrorHandler, probably because the call was made before Kernel was created. Please check your GLPI codebase is up-to-date and that all your PHP files are executed within a valid Kernel.');
- }
-
return self::$currentLogger;
}
@@ -143,7 +138,7 @@ private static function getOutputHandlers(): array
return $handlers = [
new ConsoleErrorDisplayHandler(),
new HtmlErrorDisplayHandler(),
- new EchoDisplayHandler(),
+ new LegacyCliDisplayHandler(),
];
}
@@ -153,31 +148,12 @@ public static function displayErrorMessage(string $error_type, string $message,
return;
}
- $is_dev_env = $env === \GLPI::ENV_DEVELOPMENT;
- $is_debug_mode = isset($_SESSION['glpi_use_mode']) && $_SESSION['glpi_use_mode'] == \Session::DEBUG_MODE;
- $is_console_context = \isCommandLine();
-
- if (
- !$is_dev_env // error messages are always displayed in development environment
- && !$is_debug_mode // error messages are always displayed in debug mode
- && !$is_console_context // error messages are always forwarded to the console output handler, that handles itself the verbosity level
- ) {
- return;
- }
-
- $output_handled = false;
-
foreach (self::getOutputHandlers() as $handler) {
if ($handler->canOutput($log_level, $env)) {
- $output_handled = true;
$handler->displayErrorMessage($error_type, $message, $log_level, $env);
break; // Only one display per handler
}
}
-
- if (!$output_handled) {
- throw new \RuntimeException('No error display handler was able to output the error message.');
- }
}
public function handleError(int $type, string $message, string $file, int $line): bool
@@ -196,31 +172,6 @@ public function handleError(int $type, string $message, string $file, int $line)
return $handled;
}
- private function configureLogger(): void
- {
- if (\defined('GLPI_LOG_LVL')) {
- $minimum_log_level = GLPI_LOG_LVL;
- } else {
- $minimum_log_level = match (\GLPI_ENVIRONMENT_TYPE) {
- \GLPI::ENV_DEVELOPMENT => LogLevel::DEBUG,
- \GLPI::ENV_TESTING => LogLevel::NOTICE,
- default => LogLevel::WARNING,
- };
- }
-
- $logger = new Logger('glpierrorlog');
- $handler = new \Monolog\Handler\StreamHandler(
- GLPI_LOG_DIR . "/glpi-errors.log",
- $minimum_log_level
- );
- $handler->setFormatter(new LogLineFormatter());
-
- $logger->pushHandler($handler);
- $this->setDefaultLogger($logger);
-
- self::$currentLogger = $logger;
- }
-
private function configureErrorDisplay(): void
{
// Adjust reporting level to the environment, to ensure that all the errors supposed to be logged are
diff --git a/src/Glpi/Kernel/Kernel.php b/src/Glpi/Kernel/Kernel.php
index aae98d67cd5..2b659bc1a51 100644
--- a/src/Glpi/Kernel/Kernel.php
+++ b/src/Glpi/Kernel/Kernel.php
@@ -37,6 +37,7 @@
use GLPI;
use Glpi\Application\SystemConfigurator;
use Glpi\Http\Listener\PluginsRouterListener;
+use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Bundle\TwigBundle\TwigBundle;
@@ -53,16 +54,15 @@ final class Kernel extends BaseKernel
{
use MicroKernelTrait;
+ private LoggerInterface $logger;
+
public function __construct(?string $env = null)
{
// Initialize system configuration.
// It must be done after the autoload inclusion that requires some constants to be defined (e.g. GLPI_VERSION).
// It must be done before the Kernel boot as some of the define constants must be defined during the boot sequence.
- (new SystemConfigurator($this->getProjectDir(), $env))();
-
- // TODO: refactor the GLPI class.
- $glpi = (new GLPI());
- $glpi->initLogger();
+ $configurator = new SystemConfigurator($this->getProjectDir(), $env);
+ $this->logger = $configurator->getLogger();
$env = GLPI_ENVIRONMENT_TYPE;
parent::__construct(
@@ -113,6 +113,9 @@ public function boot(): void
parent::boot();
+ // Override Symfony's logger
+ $this->container->set('logger', $this->logger);
+
if ($dispatch_postboot) {
$this->container->get('event_dispatcher')->dispatch(new PostBootEvent());
}
diff --git a/src/Glpi/Log/GlpiLogHandler.php b/src/Glpi/Log/GlpiLogHandler.php
new file mode 100644
index 00000000000..0318776e41a
--- /dev/null
+++ b/src/Glpi/Log/GlpiLogHandler.php
@@ -0,0 +1,107 @@
+.
+ *
+ * ---------------------------------------------------------------------
+ */
+
+namespace Glpi\Log;
+
+use Monolog\Handler\StreamHandler;
+use Monolog\Level;
+use Monolog\LogRecord;
+use Psr\Log\LogLevel;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
+
+class GlpiLogHandler extends StreamHandler
+{
+ public function __construct()
+ {
+ if (\defined('GLPI_LOG_LVL')) {
+ $log_level = GLPI_LOG_LVL;
+ } else {
+ $log_level = match (GLPI_ENVIRONMENT_TYPE) {
+ \GLPI::ENV_DEVELOPMENT => LogLevel::DEBUG,
+ \GLPI::ENV_TESTING => LogLevel::NOTICE,
+ default => LogLevel::WARNING,
+ };
+ }
+
+ parent::__construct(GLPI_LOG_DIR . '/php-errors.log', $log_level);
+
+ $this->setFormatter(new LogLineFormatter());
+ }
+
+ public function isHandling(LogRecord $record): bool
+ {
+ if (!$this->canHandle($record)) {
+ return false;
+ }
+
+ return parent::isHandling($record);
+ }
+
+ public function handle(LogRecord $record): bool
+ {
+ if (!$this->canHandle($record)) {
+ return false;
+ }
+
+ return parent::handle($record);
+ }
+
+ /**
+ * The goal here is mostly to disable logging Symfony's Kernel events.
+ * It avoid flooding the debug logs with "Notified event {...}" messages.
+ */
+ public function canHandle(LogRecord $record): bool
+ {
+ if (isset($record->context['event']) && $record->level === Level::Debug) {
+ return false;
+ }
+
+ /**
+ * @see \Glpi\Controller\ErrorController::logHttpException
+ */
+ if (isset($record->context['exception'])) {
+ /** @var \Throwable $exception */
+ $exception = $record->context['exception'];
+ if (
+ $exception instanceof HttpExceptionInterface
+ && $exception->getStatusCode() >= 400
+ && $exception->getStatusCode() < 500
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/Glpi/Log/LegacyGlobalLogger.php b/src/Glpi/Log/LegacyGlobalLogger.php
deleted file mode 100644
index 903e56ac020..00000000000
--- a/src/Glpi/Log/LegacyGlobalLogger.php
+++ /dev/null
@@ -1,106 +0,0 @@
-.
- *
- * ---------------------------------------------------------------------
- */
-
-namespace Glpi\Log;
-
-use Glpi\Exception\Http\HttpExceptionInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-
-class LegacyGlobalLogger implements LoggerInterface
-{
- private function getLogger(): LoggerInterface
- {
- /**
- * @var null|\Psr\Log\LoggerInterface $PHPLOGGER
- */
- global $PHPLOGGER;
-
- return $PHPLOGGER ?? new NullLogger();
- }
-
- public function emergency(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->emergency($message, $context);
- }
-
- public function alert(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->alert($message, $context);
- }
-
- public function critical(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->critical($message, $context);
- }
-
- public function error(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->error($message, $context);
- }
-
- public function warning(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->warning($message, $context);
- }
-
- public function notice(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->notice($message, $context);
- }
-
- public function info(\Stringable|string $message, array $context = []): void
- {
- $this->getLogger()->info($message, $context);
- }
-
- public function debug(\Stringable|string $message, array $context = []): void
- {
- // Debug is disabled for now because Symfony generates a lot of debug logs that GLPI doesn't need yet.
- }
-
- public function log($level, \Stringable|string $message, array $context = []): void
- {
- if (isset($context['exception']) && $context['exception'] instanceof HttpExceptionInterface) {
- // Bypass \Symfony\Component\HttpKernel\EventListener\ErrorListener's logging system.
- // Symfony is force-logging all exceptions, even the deliberately thrown HTTP exceptions.
- // In GLPI, 4xx exception are not usually an "issue", and are logged in the "access" channel.
- // However, Symfony logs them in the "error" channel.
- // To keep most of Symfony's behavior in non-HTTP exceptions, we just remove the HttpExceptionInterface from it.
- return;
- }
-
- $this->getLogger()->log($level, $message, $context);
- }
-}
diff --git a/src/Glpi/Error/LogLineFormatter.php b/src/Glpi/Log/LogLineFormatter.php
similarity index 97%
rename from src/Glpi/Error/LogLineFormatter.php
rename to src/Glpi/Log/LogLineFormatter.php
index 5f694785202..f0976c18151 100644
--- a/src/Glpi/Error/LogLineFormatter.php
+++ b/src/Glpi/Log/LogLineFormatter.php
@@ -32,8 +32,9 @@
* ---------------------------------------------------------------------
*/
-namespace Glpi\Error;
+namespace Glpi\Log;
+use Glpi\Error\StackTraceFormatter;
use Monolog\Formatter\LineFormatter;
final class LogLineFormatter extends LineFormatter