Merge pull request #14682 from Art4/refactor-logger

Create LoggerManager
This commit is contained in:
Hypolite Petovan 2025-01-13 13:31:35 -05:00 committed by GitHub
commit 47946b1cb7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 402 additions and 64 deletions

View file

@ -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

View file

@ -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();
}
/**

View file

@ -0,0 +1,59 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capability\ICanCreateInstances;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Bridge for the legacy Logger factory.
*
* This class can be removed after the following classes are replaced or
* refactored implementing the `\Friendica\Core\Logger\Factory\LoggerFactory`:
*
* - Friendica\Core\Logger\Factory\StreamLogger
* - Friendica\Core\Logger\Factory\SyslogLogger
* - monolog addon: Friendica\Addon\monolog\src\Factory\Monolog
*
* @see \Friendica\Core\Logger\Factory\StreamLogger
* @see \Friendica\Core\Logger\Factory\SyslogLogger
*/
final class LegacyLoggerFactory implements LoggerFactory
{
private ICanCreateInstances $instanceCreator;
private IManageConfigValues $config;
private Profiler $profiler;
public function __construct(ICanCreateInstances $instanceCreator, IManageConfigValues $config, Profiler $profiler)
{
$this->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);
}
}

View file

@ -0,0 +1,28 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger\Factory;
use Psr\Log\LoggerInterface;
/**
* Interface for a logger factory
*/
interface LoggerFactory
{
/**
* 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;
}

View file

@ -0,0 +1,101 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Factory\LoggerFactory;
use Friendica\Core\Logger\Type\ProfilerLogger;
use Friendica\Core\Logger\Type\WorkerLogger;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
/**
* Manager for the core logging instances
*/
final class LoggerManager
{
/**
* Workaround: $logger must be static
* because Dice always creates a new LoggerManager object
*
* @var LoggerInterface|null
*/
private static $logger = null;
/**
* Workaround: $logChannel must be static
* because Dice always creates a new LoggerManager object
*/
private static string $logChannel = LogChannel::DEFAULT;
private IManageConfigValues $config;
private LoggerFactory $factory;
private bool $debug;
private string $logLevel;
private bool $profiling;
public function __construct(IManageConfigValues $config, LoggerFactory $factory)
{
$this->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;
}
}

View file

@ -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);
}

View file

@ -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
//

View file

@ -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' => [

View file

@ -0,0 +1,36 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capability\ICanCreateInstances;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Factory\LegacyLoggerFactory;
use Friendica\Util\Profiler;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class LegacyLoggerFactoryTest extends TestCase
{
public function testCreateLoggerReturnsPsrLogger(): void
{
$factory = new LegacyLoggerFactory(
$this->createStub(ICanCreateInstances::class),
$this->createStub(IManageConfigValues::class),
$this->createStub(Profiler::class),
);
$this->assertInstanceOf(
LoggerInterface::class,
$factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT)
);
}
}

View file

@ -0,0 +1,129 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Logger;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Factory\LoggerFactory;
use Friendica\Core\Logger\LoggerManager;
use Friendica\Core\Logger\Type\ProfilerLogger;
use Friendica\Core\Logger\Type\WorkerLogger;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
class LoggerManagerTest extends TestCase
{
/**
* Clean the private static properties
*
* @see LoggerManager::$logger
* @see LoggerManager::$logChannel
*/
protected function tearDown(): void
{
$reflectionProperty = new \ReflectionProperty(LoggerManager::class, 'logger');
$reflectionProperty->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());
}
}