diff --git a/src/App.php b/src/App.php index c1ac8b2d43..73f2913b1a 100644 --- a/src/App.php +++ b/src/App.php @@ -20,6 +20,7 @@ use Friendica\Content\Nav; use Friendica\Core\Addon\Capability\ICanLoadAddons; use Friendica\Core\Config\Factory\Config; use Friendica\Core\Container; +use Friendica\Core\Logger\LoggerManager; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Database\Definition\DbaDefinition; @@ -139,7 +140,7 @@ class App $this->setupContainerForAddons(); - $this->setupContainerForLogger(LogChannel::APP); + $this->setupLogChannel(LogChannel::APP); $this->setupLegacyServiceLocator(); @@ -181,7 +182,7 @@ class App { $this->setupContainerForAddons(); - $this->setupContainerForLogger($this->determineLogChannel($argv)); + $this->setupLogChannel($this->determineLogChannel($argv)); $this->setupLegacyServiceLocator(); @@ -196,7 +197,7 @@ class App { $this->setupContainerForAddons(); - $this->setupContainerForLogger(LogChannel::AUTH_JABBERED); + $this->setupLogChannel(LogChannel::AUTH_JABBERED); $this->setupLegacyServiceLocator(); @@ -229,7 +230,7 @@ class App private function determineLogChannel(array $argv): string { - $command = strtolower($argv[1]) ?? ''; + $command = strtolower($argv[1] ?? ''); if ($command === 'daemon' || $command === 'jetstream') { return LogChannel::DAEMON; @@ -244,11 +245,11 @@ class App return LogChannel::CONSOLE; } - private function setupContainerForLogger(string $logChannel): void + private function setupLogChannel(string $logChannel): void { - $this->container->addRule(LoggerInterface::class, [ - 'constructParams' => [$logChannel], - ]); + /** @var LoggerManager */ + $loggerManager = $this->container->create(LoggerManager::class); + $loggerManager->changeLogChannel($logChannel); } private function setupLegacyServiceLocator(): void diff --git a/src/Core/Logger.php b/src/Core/Logger.php index c15271e841..a87be78633 100644 --- a/src/Core/Logger.php +++ b/src/Core/Logger.php @@ -13,57 +13,17 @@ use Psr\Log\LoggerInterface; /** * Logger functions + * + * @deprecated 2025.02 Use constructor injection or `DI::logger()` instead */ class Logger { - /** - * LoggerInterface The default Logger type - * - * @var string - */ - const TYPE_LOGGER = LoggerInterface::class; - /** - * WorkerLogger A specific worker logger type, which can be enabled - * - * @var string - */ - const TYPE_WORKER = WorkerLogger::class; - /** - * @var string $type LoggerInterface The current logger type - */ - private static $type = self::TYPE_LOGGER; - /** * @return LoggerInterface|WorkerLogger */ private static function getInstance() { - if (self::$type === self::TYPE_LOGGER) { - return DI::logger(); - } else { - return DI::workerLogger(); - } - } - - /** - * Enable additional logging for worker usage - * - * @param string $functionName The worker function, which got called - * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public static function enableWorker(string $functionName) - { - self::$type = self::TYPE_WORKER; - DI::workerLogger()->setFunctionName($functionName); - } - - /** - * Disable additional logging for worker usage - */ - public static function disableWorker() - { - self::$type = self::TYPE_LOGGER; + return DI::logger(); } /** diff --git a/src/Core/Logger/Factory/LegacyLoggerFactory.php b/src/Core/Logger/Factory/LegacyLoggerFactory.php new file mode 100644 index 0000000000..5c91d11771 --- /dev/null +++ b/src/Core/Logger/Factory/LegacyLoggerFactory.php @@ -0,0 +1,59 @@ +instanceCreator = $instanceCreator; + $this->config = $config; + $this->profiler = $profiler; + } + + /** + * Creates and returns a PSR-3 Logger instance. + * + * Calling this method multiple times with the same parameters SHOULD return the same object. + * + * @param \Psr\Log\LogLevel::* $logLevel The log level + * @param \Friendica\Core\Logger\Capability\LogChannel::* $logChannel The log channel + */ + public function createLogger(string $logLevel, string $logChannel): LoggerInterface + { + $factory = new Logger($logChannel); + + return $factory->create($this->instanceCreator, $this->config, $this->profiler); + } +} diff --git a/src/Core/Logger/Factory/LoggerFactory.php b/src/Core/Logger/Factory/LoggerFactory.php new file mode 100644 index 0000000000..0802540d5d --- /dev/null +++ b/src/Core/Logger/Factory/LoggerFactory.php @@ -0,0 +1,28 @@ +config = $config; + $this->factory = $factory; + + $this->debug = (bool) $config->get('system', 'debugging') ?? false; + $this->logLevel = (string) $config->get('system', 'loglevel') ?? LogLevel::NOTICE; + $this->profiling = (bool) $config->get('system', 'profiling') ?? false; + } + + public function changeLogChannel(string $logChannel): void + { + self::$logChannel = $logChannel; + self::$logger = null; + } + + /** + * (Creates and) Returns the logger instance + */ + public function getLogger(): LoggerInterface + { + if (self::$logger === null) { + self::$logger = $this->createLogger(); + } + + return self::$logger; + } + + private function createLogger(): LoggerInterface + { + // Always create NullLogger if debug is disabled + if ($this->debug === false) { + $logger = new NullLogger(); + } else { + $logger = $this->factory->createLogger($this->logLevel, self::$logChannel); + } + + if ($this->profiling === true) { + $profiler = new Profiler($this->config); + + $logger = new ProfilerLogger($logger, $profiler); + } + + // Decorate Logger as WorkerLogger for BC + if (self::$logChannel === LogChannel::WORKER) { + $logger = new WorkerLogger($logger); + } + + return $logger; + } +} diff --git a/src/Core/Worker.php b/src/Core/Worker.php index 568fa2873f..c84de94f63 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -8,6 +8,8 @@ namespace Friendica\Core; use Friendica\Core\Cache\Enum\Duration; +use Friendica\Core\Logger\Capability\LogChannel; +use Friendica\Core\Logger\Type\WorkerLogger; use Friendica\Core\Worker\Entity\Process; use Friendica\Database\DBA; use Friendica\DI; @@ -537,9 +539,15 @@ class Worker self::coolDown(); - Logger::enableWorker($funcname); + DI::loggerManager()->changeLogChannel(LogChannel::WORKER); - Logger::info('Process start.', ['priority' => $queue['priority'], 'id' => $queue['id']]); + $logger = DI::logger(); + + if ($logger instanceof WorkerLogger) { + $logger->setFunctionName($funcname); + } + + DI::logger()->info('Process start.', ['priority' => $queue['priority'], 'id' => $queue['id']]); $stamp = (float)microtime(true); @@ -559,19 +567,19 @@ class Worker try { call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv); } catch (\Throwable $e) { - Logger::error('Uncaught exception in worker method execution', ['class' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTraceAsString(), 'previous' => $e->getPrevious()]); + DI::logger()->error('Uncaught exception in worker method execution', ['class' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTraceAsString(), 'previous' => $e->getPrevious()]); Worker::defer(); } } else { try { $funcname($argv, count($argv)); } catch (\Throwable $e) { - Logger::error('Uncaught exception in worker execution', ['message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTraceAsString(), 'previous' => $e->getPrevious()]); + DI::logger()->error('Uncaught exception in worker execution', ['message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTraceAsString(), 'previous' => $e->getPrevious()]); Worker::defer(); } } - Logger::disableWorker(); + DI::loggerManager()->changeLogChannel(LogChannel::DEFAULT); $appHelper->setQueue([]); @@ -591,7 +599,7 @@ class Worker $rest = round(max(0, $up_duration - (self::$db_duration + self::$lock_duration)), 2); $exec = round($duration, 2); - Logger::info('Performance:', ['function' => $funcname, 'state' => self::$state, 'count' => $dbcount, 'stat' => $dbstat, 'write' => $dbwrite, 'lock' => $dblock, 'total' => $dbtotal, 'rest' => $rest, 'exec' => $exec]); + DI::logger()->info('Performance:', ['function' => $funcname, 'state' => self::$state, 'count' => $dbcount, 'stat' => $dbstat, 'write' => $dbwrite, 'lock' => $dblock, 'total' => $dbtotal, 'rest' => $rest, 'exec' => $exec]); self::coolDown(); @@ -603,16 +611,16 @@ class Worker self::$lock_duration = 0; if ($duration > 3600) { - Logger::info('Longer than 1 hour.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); + DI::logger()->info('Longer than 1 hour.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); } elseif ($duration > 600) { - Logger::info('Longer than 10 minutes.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); + DI::logger()->info('Longer than 10 minutes.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); } elseif ($duration > 300) { - Logger::info('Longer than 5 minutes.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); + DI::logger()->info('Longer than 5 minutes.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); } elseif ($duration > 120) { - Logger::info('Longer than 2 minutes.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); + DI::logger()->info('Longer than 2 minutes.', ['priority' => $queue['priority'], 'id' => $queue['id'], 'duration' => round($duration / 60, 3)]); } - Logger::info('Process done.', ['function' => $funcname, 'priority' => $queue['priority'], 'retrial' => $queue['retrial'], 'id' => $queue['id'], 'duration' => round($duration, 3)]); + DI::logger()->info('Process done.', ['function' => $funcname, 'priority' => $queue['priority'], 'retrial' => $queue['retrial'], 'id' => $queue['id'], 'duration' => round($duration, 3)]); DI::profiler()->saveLog(DI::logger(), 'ID ' . $queue['id'] . ': ' . $funcname); } diff --git a/src/DI.php b/src/DI.php index 72be1f3aaa..7d1114ac5c 100644 --- a/src/DI.php +++ b/src/DI.php @@ -9,6 +9,7 @@ namespace Friendica; use Dice\Dice; use Friendica\Core\Logger\Capability\ICheckLoggerSettings; +use Friendica\Core\Logger\LoggerManager; use Friendica\Core\Logger\Util\LoggerSettingsCheck; use Friendica\Core\Session\Capability\IHandleSessions; use Friendica\Core\Session\Capability\IHandleUserSessions; @@ -324,6 +325,8 @@ abstract class DI } /** + * @deprecated 2025.02 Use `DI::loggerManager()` and `DI::logger()` instead + * * @return \Friendica\Core\Logger\Type\WorkerLogger */ public static function workerLogger() @@ -331,6 +334,11 @@ abstract class DI return self::$dice->create(Core\Logger\Type\WorkerLogger::class); } + public static function loggerManager(): LoggerManager + { + return self::$dice->create(LoggerManager::class); + } + // // "Factory" namespace instances // diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 5d1edfea22..f1c2f4e52e 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -157,11 +157,19 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo ], ], \Psr\Log\LoggerInterface::class => [ - 'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class, + 'instanceOf' => \Friendica\Core\Logger\LoggerManager::class, 'call' => [ - ['create', [], Dice::CHAIN_CALL], + ['getLogger', [], Dice::CHAIN_CALL], ], ], + \Friendica\Core\Logger\LoggerManager::class => [ + 'substitutions' => [ + \Friendica\Core\Logger\Factory\LoggerFactory::class => \Friendica\Core\Logger\Factory\LegacyLoggerFactory::class, + ], + ], + \Friendica\Core\Logger\Factory\LoggerFactory::class => [ + 'instanceOf' => \Friendica\Core\Logger\Factory\LegacyLoggerFactory::class, + ], \Friendica\Core\Logger\Type\SyslogLogger::class => [ 'instanceOf' => \Friendica\Core\Logger\Factory\SyslogLogger::class, 'call' => [ diff --git a/tests/Unit/Core/Logger/Factory/LegacyLoggerFactoryTest.php b/tests/Unit/Core/Logger/Factory/LegacyLoggerFactoryTest.php new file mode 100644 index 0000000000..9ef920c71f --- /dev/null +++ b/tests/Unit/Core/Logger/Factory/LegacyLoggerFactoryTest.php @@ -0,0 +1,36 @@ +createStub(ICanCreateInstances::class), + $this->createStub(IManageConfigValues::class), + $this->createStub(Profiler::class), + ); + + $this->assertInstanceOf( + LoggerInterface::class, + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) + ); + } +} diff --git a/tests/Unit/Core/Logger/LoggerManagerTest.php b/tests/Unit/Core/Logger/LoggerManagerTest.php new file mode 100644 index 0000000000..02f9c0b2e2 --- /dev/null +++ b/tests/Unit/Core/Logger/LoggerManagerTest.php @@ -0,0 +1,129 @@ +setAccessible(true); + $reflectionProperty->setValue(null, null); + + $reflectionProperty = new \ReflectionProperty(LoggerManager::class, 'logChannel'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue(null, LogChannel::DEFAULT); + } + + public function testGetLoggerReturnsPsrLogger(): void + { + $factory = new LoggerManager( + $this->createStub(IManageConfigValues::class), + $this->createStub(LoggerFactory::class) + ); + + $this->assertInstanceOf(LoggerInterface::class, $factory->getLogger()); + } + + public function testGetLoggerReturnsSameObject(): void + { + $factory = new LoggerManager( + $this->createStub(IManageConfigValues::class), + $this->createStub(LoggerFactory::class) + ); + + $this->assertSame($factory->getLogger(), $factory->getLogger()); + } + + public function testGetLoggerWithDebugDisabledReturnsNullLogger(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'debugging', null, false], + ]); + + $factory = new LoggerManager( + $config, + $this->createStub(LoggerFactory::class) + ); + + $this->assertInstanceOf(NullLogger::class, $factory->getLogger()); + } + + public function testGetLoggerWithProfilerEnabledReturnsProfilerLogger(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'debugging', null, false], + ['system', 'profiling', null, true], + ]); + + $factory = new LoggerManager( + $config, + $this->createStub(LoggerFactory::class) + ); + + $this->assertInstanceOf(ProfilerLogger::class, $factory->getLogger()); + } + + public function testChangeLogChannelReturnsDifferentLogger(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'debugging', null, false], + ['system', 'profiling', null, true], + ]); + + $factory = new LoggerManager( + $config, + $this->createStub(LoggerFactory::class) + ); + + $logger1 = $factory->getLogger(); + + $factory->changeLogChannel(LogChannel::CONSOLE); + + $this->assertNotSame($logger1, $factory->getLogger()); + } + + public function testChangeLogChannelToWorkerReturnsWorkerLogger(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'debugging', null, false], + ['system', 'profiling', null, true], + ]); + + $factory = new LoggerManager( + $config, + $this->createStub(LoggerFactory::class) + ); + + $factory->changeLogChannel(LogChannel::WORKER); + + $this->assertInstanceOf(WorkerLogger::class, $factory->getLogger()); + } +}