Introduce dynamic hook loading

- Dynamically load addon files
- Dynamically load hooks
- Rewrite Logger-logic to use new hook logic (Monolog is working again)
This commit is contained in:
Philipp 2023-07-02 23:56:56 +02:00
parent ff092833ae
commit 14b76e48f0
No known key found for this signature in database
GPG key ID: 24A7501396EB5432
39 changed files with 1163 additions and 469 deletions

View file

@ -58,6 +58,7 @@ if (php_sapi_name() !== 'cli') {
use Dice\Dice;
use Friendica\App\Mode;
use Friendica\Core\Logger\Capabilities\LogChannel;
use Friendica\Security\ExAuth;
use Psr\Log\LoggerInterface;
@ -78,7 +79,10 @@ chdir($directory);
require dirname(__DIR__) . '/vendor/autoload.php';
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php');
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['auth_ejabberd']]);
/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */
$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class);
$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies'));
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => [LogChannel::AUTH_JABBERED]]);
\Friendica\DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));

View file

@ -26,13 +26,17 @@ if (php_sapi_name() !== 'cli') {
}
use Dice\Dice;
use Friendica\Core\Logger\Capabilities\LogChannel;
use Friendica\DI;
use Psr\Log\LoggerInterface;
require dirname(__DIR__) . '/vendor/autoload.php';
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php');
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['console']]);
/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */
$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class);
$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies'));
$dice = $dice->addRule(LoggerInterface::class, ['constructParams' => [LogChannel::CONSOLE]]);
/// @fixme Necessary until Hooks inside the Logger can get loaded without the DI-class
DI::init($dice);

View file

@ -38,7 +38,6 @@ use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface;
// Get options
$shortopts = 'f';
@ -60,7 +59,10 @@ if (!file_exists('index.php') && (sizeof($_SERVER['argv']) != 0)) {
require dirname(__DIR__) . '/vendor/autoload.php';
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php');
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['daemon']]);
/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */
$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class);
$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies'));
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => [Logger\Capabilities\LogChannel::DAEMON]]);
DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));

View file

@ -29,6 +29,7 @@ if (php_sapi_name() !== 'cli') {
use Dice\Dice;
use Friendica\App;
use Friendica\App\Mode;
use Friendica\Core\Logger\Capabilities\LogChannel;
use Friendica\Core\Update;
use Friendica\Core\Worker;
use Friendica\DI;
@ -54,7 +55,10 @@ if (!file_exists("index.php") && (sizeof($_SERVER["argv"]) != 0)) {
require dirname(__DIR__) . '/vendor/autoload.php';
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php');
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['worker']]);
/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */
$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class);
$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies'));
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => [LogChannel::WORKER]]);
DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));

View file

@ -30,6 +30,9 @@ if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
require __DIR__ . '/vendor/autoload.php';
$dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.php');
/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */
$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class);
$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies'));
$dice = $dice->addRule(Friendica\App\Mode::class, ['call' => [['determineRunMode', [false, $_SERVER], Dice::CHAIN_CALL]]]);
\Friendica\DI::init($dice);

View file

@ -0,0 +1,37 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Addon\Capabilities;
/**
* Interface for loading Addons specific content
*/
interface ICanLoadAddons
{
/**
* Returns a merged config array of all active addons for a given config-name
*
* @param string $configName The config-name (config-file at the static directory, like 'hooks' => '{addon}/static/hooks.config.php)
*
* @return array the merged array
*/
public function getActiveAddonConfig(string $configName): array;
}

View file

@ -19,11 +19,17 @@
*
*/
namespace Friendica\Core\Hooks\Capabilities;
namespace Friendica\Core\Addon\Exception;
use Throwable;
/**
* All classes, implementing this interface are valid Strategies for Hook calls
* Exception in case one or more config files of the addons are invalid
*/
interface IAmAStrategy
class AddonInvalidConfigFileException extends \RuntimeException
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, 500, $previous);
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Addon\Model;
use Friendica\Core\Addon\Capabilities\ICanLoadAddons;
use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Util\Strings;
class AddonLoader implements ICanLoadAddons
{
const STATIC_PATH = 'static';
/** @var string */
protected $basePath;
/** @var IManageConfigValues */
protected $config;
public function __construct(string $basePath, IManageConfigValues $config)
{
$this->basePath = $basePath;
$this->config = $config;
}
/** {@inheritDoc} */
public function getActiveAddonConfig(string $configName): array
{
$addons = array_keys(array_filter($this->config->get('addons') ?? []));
$returnConfig = [];
foreach ($addons as $addon) {
$addonName = Strings::sanitizeFilePathItem(trim($addon));
$configFile = $this->basePath . '/addon/' . $addonName . '/' . static::STATIC_PATH . '/' . $configName . '.config.php';
if (!file_exists($configFile)) {
// Addon unmodified, skipping
continue;
}
$config = include $configFile;
if (!is_array($config)) {
throw new AddonInvalidConfigFileException('Error loading config file ' . $configFile);
}
$returnConfig = array_merge_recursive($returnConfig, $config);
}
return $returnConfig;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Hooks\Capabilities;
interface HookType
{
/**
* Defines the key for the list of strategy-hooks.
*
* @see https://refactoring.guru/design-patterns/strategy
*/
const STRATEGY = 'strategy';
/**
* Defines the key for the list of decorator-hooks.
*
* @see https://refactoring.guru/design-patterns/decorator
*/
const DECORATOR = 'decorator';
const EVENT = 'event';
}

View file

@ -0,0 +1,59 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Hooks\Capabilities;
use Friendica\Core\Hooks\Exceptions\HookInstanceException;
/**
* creates special instance and decorator treatments for given classes
*/
interface ICanCreateInstances
{
/**
* Returns a new instance of a given class for the corresponding name
*
* The instance will be build based on the registered strategy and the (unique) name
*
* In case, there are registered decorators for this class as well, all decorators of the list will be wrapped
* around the instance before returning it
*
* @param string $class The fully-qualified name of the given class or interface which will get returned
* @param string $name An arbitrary identifier to find a concrete instance strategy.
* @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime
*
* @return object The concrete instance of the type "$class"
*/
public function createWithName(string $class, string $name, array $arguments = []): object;
/**
* Returns a new instance of a given class
*
* In case, there are registered decorators for this class as well, all decorators of the list will be wrapped
* around the instance before returning it
*
* @param string $class The fully-qualified name of the given class or interface which will get returned
* @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime
*
* @return object The concrete instance of the type "$class"
*/
public function create(string $class, array $arguments = []): object;
}

View file

@ -21,61 +21,43 @@
namespace Friendica\Core\Hooks\Capabilities;
use Friendica\Core\Hooks\Exceptions\HookInstanceException;
use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException;
/**
* Managing special instance and decorator treatments for classes
* Register strategies and decorator/treatment handling for given classes
*/
interface ICanManageInstances
interface ICanRegisterInstances
{
/**
* Register a class(strategy) for a given interface with a unique name.
*
* @see https://refactoring.guru/design-patterns/strategy
*
* @param string $interface The interface, which the given class implements
* @param string $name An arbitrary identifier for the given class, which will be used for factories, dependency injections etc.
* @param string $class The fully-qualified given class name
* @param ?array $arguments Additional arguments, which can be passed to the constructor
* @param string $interface The interface, which the given class implements
* @param string $class The fully-qualified given class name
* A placeholder for dependencies is possible as well
* @param ?string $name An arbitrary identifier for the given class, which will be used for factories, dependency injections etc.
*
* @return $this This interface for chain-calls
*
* @throws HookRegisterArgumentException in case the given class for the interface isn't valid or already set
*/
public function registerStrategy(string $interface, string $name, string $class, array $arguments = null): self;
public function registerStrategy(string $interface, string $class, ?string $name = null): self;
/**
* Register a new decorator for a given class or interface
* @see https://refactoring.guru/design-patterns/decorator
*
* @see https://refactoring.guru/design-patterns/decorator
*
* @note Decorator attach new behaviors to classes without changing them or without letting them know about it.
*
* @param string $class The fully-qualified class or interface name, which gets decorated by a class
* @param string $decoratorClass The fully-qualified name of the class which mimics the given class or interface and adds new functionality
* @param array $arguments Additional arguments, which can be passed to the constructor of "decoratorClass"
* A placeholder for dependencies is possible as well
*
* @return $this This interface for chain-calls
*
* @throws HookRegisterArgumentException in case the given class for the class or interface isn't valid
*/
public function registerDecorator(string $class, string $decoratorClass, array $arguments = []): self;
/**
* Returns a new instance of a given class for the corresponding name
*
* The instance will be build based on the registered strategy and the (unique) name
*
* In case, there are registered decorators for this class as well, all decorators of the list will be wrapped
* around the instance before returning it
*
* @param string $class The fully-qualified name of the given class or interface which will get returned
* @param string $name An arbitrary identifier to find a concrete instance strategy.
* @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime
*
* @return object The concrete instance of the type "$class"
*
* @throws HookInstanceException In case the class cannot get created
*/
public function getInstance(string $class, string $name, array $arguments = []): object;
public function registerDecorator(string $class, string $decoratorClass): self;
}

View file

@ -19,13 +19,11 @@
*
*/
namespace Friendica\Core\Logger\Exception;
namespace Friendica\Core\Hooks\Exceptions;
use Throwable;
class LoggerInvalidException extends \RuntimeException
class HookConfigException extends \RuntimeException
{
public function __construct($message = "", Throwable $previous = null)
public function __construct($message = "", \Throwable $previous = null)
{
parent::__construct($message, 500, $previous);
}

View file

@ -0,0 +1,111 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Hooks\Model;
use Dice\Dice;
use Friendica\Core\Hooks\Capabilities\ICanCreateInstances;
use Friendica\Core\Hooks\Capabilities\ICanRegisterInstances;
use Friendica\Core\Hooks\Exceptions\HookInstanceException;
use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException;
use Friendica\Core\Hooks\Util\HookFileManager;
/**
* This class represents an instance register, which uses Dice for creation
*
* @see Dice
*/
class DiceInstanceManager implements ICanCreateInstances, ICanRegisterInstances
{
protected $instance = [];
protected $decorator = [];
/** @var Dice */
protected $dice;
public function __construct(Dice $dice, HookFileManager $hookFileManager)
{
$this->dice = $dice;
$hookFileManager->setupHooks($this);
}
/** {@inheritDoc} */
public function registerStrategy(string $interface, string $class, ?string $name = null): ICanRegisterInstances
{
if (!empty($this->instance[$interface][$name])) {
throw new HookRegisterArgumentException(sprintf('A class with the name %s is already set for the interface %s', $name, $interface));
}
$this->instance[$interface][$name] = $class;
return $this;
}
/** {@inheritDoc} */
public function registerDecorator(string $class, string $decoratorClass): ICanRegisterInstances
{
$this->decorator[$class][] = $decoratorClass;
return $this;
}
/** {@inheritDoc} */
public function create(string $class, array $arguments = []): object
{
$instanceClassName = $class;
$instanceRule = $this->dice->getRule($instanceClassName) ?? [];
$instanceRule = array_replace_recursive($instanceRule, [
'constructParams' => $arguments,
]);
$this->dice = $this->dice->addRule($instanceClassName, $instanceRule);
foreach ($this->decorator[$class] ?? [] as $decorator) {
$dependencyRule = $this->dice->getRule($decorator);
for ($i = 0; $i < count($dependencyRule['call'] ?? []); $i++) {
$dependencyRule['call'][$i][1] = [[Dice::INSTANCE => $instanceClassName]];
}
$dependencyRule['constructParams'] = $arguments;
$dependencyRule['substitutions'] = [
$class => $instanceClassName,
];
$this->dice = $this->dice->addRule($decorator, $dependencyRule);
$instanceClassName = $decorator;
}
return $this->dice->create($instanceClassName);
}
/** {@inheritDoc} */
public function createWithName(string $class, string $name, array $arguments = []): object
{
if (empty($this->instance[$class][$name])) {
throw new HookInstanceException(sprintf('The class with the name %s isn\'t registered for the class or interface %s', $name, $class));
}
$instanceClassName = $this->instance[$class][$name];
return $this->create($instanceClassName, $arguments);
}
}

View file

@ -1,104 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Hooks\Model;
use Dice\Dice;
use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
use Friendica\Core\Hooks\Capabilities\ICanManageInstances;
use Friendica\Core\Hooks\Exceptions\HookInstanceException;
use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException;
/** {@inheritDoc} */
class InstanceManager implements ICanManageInstances
{
protected $instance = [];
protected $instanceArguments = [];
protected $decorator = [];
/** @var Dice */
protected $dice;
public function __construct(Dice $dice)
{
$this->dice = $dice;
}
/** {@inheritDoc} */
public function registerStrategy(string $interface, string $name, string $class, array $arguments = null): ICanManageInstances
{
if (!is_a($class, $interface, true)) {
throw new HookRegisterArgumentException(sprintf('%s is not a valid class for the interface %s', $class, $interface));
}
if (!is_a($class, IAmAStrategy::class, true)) {
throw new HookRegisterArgumentException(sprintf('%s does not inherit from the marker interface %s', $class, IAmAStrategy::class));
}
if (!empty($this->instance[$interface][$name])) {
throw new HookRegisterArgumentException(sprintf('A class with the name %s is already set for the interface %s', $name, $interface));
}
$this->instance[$interface][$name] = $class;
$this->instanceArguments[$interface][$name] = $arguments;
return $this;
}
/** {@inheritDoc} */
public function registerDecorator(string $class, string $decoratorClass, array $arguments = []): ICanManageInstances
{
if (!is_a($decoratorClass, $class, true)) {
throw new HookRegisterArgumentException(sprintf('%s is not a valid substitution for the given class or interface %s', $decoratorClass, $class));
}
$this->decorator[$class][] = [
'class' => $decoratorClass,
'arguments' => $arguments,
];
return $this;
}
/** {@inheritDoc} */
public function getInstance(string $class, string $name, array $arguments = []): object
{
if (empty($this->instance[$class][$name])) {
throw new HookInstanceException(sprintf('The class with the name %s isn\'t registered for the class or interface %s', $name, $class));
}
$instance = $this->dice->create($this->instance[$class][$name], array_merge($this->instanceArguments[$class][$name] ?? [], $arguments));
foreach ($this->decorator[$class] ?? [] as $decorator) {
$this->dice = $this->dice->addRule($class, [
'instanceOf' => $decorator['class'],
'constructParams' => empty($decorator['arguments']) ? null : $decorator['arguments'],
/// @todo maybe support call structures for hooks as well in a later stage - could make factory calls easier
'call' => null,
'substitutions' => [$class => $instance],
]);
$instance = $this->dice->create($class);
}
return $instance;
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Hooks\Util;
use Friendica\Core\Addon\Capabilities\ICanLoadAddons;
use Friendica\Core\Hooks\Capabilities\HookType;
use Friendica\Core\Hooks\Capabilities\ICanRegisterInstances;
use Friendica\Core\Hooks\Exceptions\HookConfigException;
/**
* Manage all hooks.config.php files
*/
class HookFileManager
{
const STATIC_DIR = 'static';
const CONFIG_NAME = 'hooks';
/** @var ICanLoadAddons */
protected $addonLoader;
/** @var array */
protected $hookConfig = [];
/** @var string */
protected $basePath;
public function __construct(string $basePath, ICanLoadAddons $addonLoader)
{
$this->basePath = $basePath;
$this->addonLoader = $addonLoader;
}
/**
* Loads all kinds of hooks and registers the corresponding instances
*
* @param ICanRegisterInstances $instanceRegister The instance register
*
* @return void
*/
public function setupHooks(ICanRegisterInstances $instanceRegister)
{
// In case it wasn't used before, reload the whole hook config
if (empty($this->hookConfig)) {
$this->reloadHookConfig();
}
foreach ($this->hookConfig as $hookType => $classList) {
switch ($hookType) {
case HookType::STRATEGY:
foreach ($classList as $interface => $strategy) {
foreach ($strategy as $dependencyName => $names) {
if (is_array($names)) {
foreach ($names as $name) {
$instanceRegister->registerStrategy($interface, $dependencyName, $name);
}
} else {
$instanceRegister->registerStrategy($interface, $dependencyName, $names);
}
}
}
break;
case HookType::DECORATOR:
foreach ($classList as $interface => $decorators) {
if (is_array($decorators)) {
foreach ($decorators as $decorator) {
$instanceRegister->registerDecorator($interface, $decorator);
}
} else {
$instanceRegister->registerDecorator($interface, $decorators);
}
}
break;
}
}
}
/**
* Reloads all hook config files into the config cache for later usage
*
* Merges all hook configs from every addon - if present - as well
*
* @return void
*/
protected function reloadHookConfig()
{
// load core hook config
$configFile = $this->basePath . '/' . static::STATIC_DIR . '/' . static::CONFIG_NAME . '.config.php';
if (!file_exists($configFile)) {
throw new HookConfigException(sprintf('config file %s does not exit.', $configFile));
}
$config = include $configFile;
if (!is_array($config)) {
throw new HookConfigException('Error loading config file ' . $configFile);
}
$this->hookConfig = array_merge_recursive($config, $this->addonLoader->getActiveAddonConfig(static::CONFIG_NAME));
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Capabilities;
/**
* Whenever a logging specific check is necessary, use this interface to encapsulate and centralize this logic
*/
interface ICheckLoggerSettings
{
/**
* Checks if the logfile is set and usable
*
* @return string|null null in case everything is ok, otherwise returns the error
*/
public function checkLogfile(): ?string;
/**
* Checks if the debugging logfile is usable in case it is set!
*
* @return string|null null in case everything is ok, otherwise returns the error
*/
public function checkDebugLogfile(): ?string;
}

View file

@ -2,7 +2,7 @@
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as

View file

@ -0,0 +1,43 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Capabilities;
/**
* An enum class for the Log channels
*/
interface LogChannel
{
/** @var string channel for the auth_ejabbered script */
public const AUTH_JABBERED = 'auth_ejabberd';
/** @var string Default channel in case it isn't set explicit */
public const DEFAULT = self::APP;
/** @var string channel for console execution */
public const CONSOLE = 'console';
/** @var string channel for developer focused logging */
public const DEV = 'dev';
/** @var string channel for daemon executions */
public const DAEMON = 'daemon';
/** @var string channel for worker execution */
public const WORKER = 'worker';
/** @var string channel for frontend app executions */
public const APP = 'app';
}

View file

@ -2,7 +2,7 @@
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -23,6 +23,9 @@ namespace Friendica\Core\Logger\Exception;
use Throwable;
/**
* Exception in case the loglevel isn't set or isn't valid
*/
class LogLevelException extends \InvalidArgumentException
{
public function __construct($message = "", Throwable $previous = null)

View file

@ -2,7 +2,7 @@
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -23,6 +23,9 @@ namespace Friendica\Core\Logger\Exception;
use Throwable;
/**
* Exception in case an argument of a logger class isn't valid
*/
class LoggerArgumentException extends \InvalidArgumentException
{
public function __construct($message = "", Throwable $previous = null)

View file

@ -2,7 +2,7 @@
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -23,6 +23,9 @@ namespace Friendica\Core\Logger\Exception;
use Throwable;
/**
* A generic exception of the logging namespace
*/
class LoggerException extends \Exception
{
public function __construct($message = "", Throwable $previous = null)

View file

@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Exception;
use Throwable;
/**
* Exception in case the used logging instance is unusable because of some circumstances
*/
class LoggerUnusableException extends \RuntimeException
{
public function __construct($message = "", Throwable $previous = null)
{
parent::__construct($message, 500, $previous);
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections;
use Psr\Log\LogLevel;
/**
* Abstract class for creating logger types, which includes common necessary logic/content
*/
abstract class AbstractLoggerTypeFactory
{
/** @var string */
protected $channel;
/** @var IHaveCallIntrospections */
protected $introspection;
/**
* @param string $channel The channel for the logger
*/
public function __construct(IHaveCallIntrospections $introspection, string $channel)
{
$this->channel = $channel;
$this->introspection = $introspection;
}
/**
* Mapping a legacy level to the PSR-3 compliant levels
*
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
*
* @param string $level the level to be mapped
*
* @return string the PSR-3 compliant level
*/
protected static function mapLegacyConfigDebugLevel(string $level): string
{
switch ($level) {
// legacy WARNING
case "0":
return LogLevel::ERROR;
// legacy INFO
case "1":
return LogLevel::WARNING;
// legacy TRACE
case "2":
return LogLevel::NOTICE;
// legacy DEBUG
case "3":
return LogLevel::INFO;
// legacy DATA
case "4":
// legacy ALL
case "5":
return LogLevel::DEBUG;
// default if nothing set
default:
return $level;
}
}
}

View file

@ -22,134 +22,38 @@
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capabilities\ICanManageInstances;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Core\Logger\Type\ProfilerLogger;
use Friendica\Core\Logger\Type\StreamLogger;
use Friendica\Core\Logger\Type\SyslogLogger;
use Friendica\Core\Hooks\Capabilities\ICanCreateInstances;
use Friendica\Core\Logger\Capabilities\LogChannel;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
use Throwable;
/**
* A logger factory
* The logger factory for the core logging instances
*/
class Logger
{
const DEV_CHANNEL = 'dev';
/** @var string The log-channel (app, worker, ...) */
/** @var string The channel */
protected $channel;
/** @var ICanManageInstances */
protected $instanceManager;
/** @var IManageConfigValues */
protected $config;
public function __construct(string $channel, ICanManageInstances $instanceManager, IManageConfigValues $config, string $logfile = null)
public function __construct(string $channel = LogChannel::DEFAULT)
{
$this->channel = $channel;
$this->instanceManager = $instanceManager;
$this->config = $config;
$this->instanceManager
->registerStrategy(LoggerInterface::class, 'syslog', SyslogLogger::class)
->registerStrategy(LoggerInterface::class, 'stream', StreamLogger::class, isset($logfile) ? [$logfile] : null);
if ($this->config->get('system', 'profiling') ?? false) {
$this->instanceManager->registerDecorator(LoggerInterface::class, ProfilerLogger::class);
}
$this->channel = $channel;
}
/**
* Creates a new PSR-3 compliant logger instances
*
* @param string|null $loglevel (optional) A given loglevel in case the loglevel in the config isn't applicable
*
* @return LoggerInterface The PSR-3 compliant logger instance
*/
public function create(string $loglevel = null): LoggerInterface
public function create(ICanCreateInstances $createInstances, IManageConfigValues $config): LoggerInterface
{
if (empty($this->config->get('system', 'debugging') ?? false)) {
if (empty($config->get('system', 'debugging') ?? false)) {
return new NullLogger();
}
$loglevel = $loglevel ?? static::mapLegacyConfigDebugLevel($this->config->get('system', 'loglevel'));
$name = $this->config->get('system', 'logger_config') ?? 'stream';
$name = $config->get('system', 'logger_config') ?? '';
try {
/** @var LoggerInterface */
return $this->instanceManager->getInstance(LoggerInterface::class, $name, [$this->channel, $loglevel]);
} catch (LogLevelException $exception) {
// If there's a wrong config value for loglevel, try again with standard
$logger = $this->create(LogLevel::NOTICE);
$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
return $logger;
} catch (\Throwable $e) {
return $createInstances->createWithName(LoggerInterface::class, $name, [$this->channel]);
} catch (Throwable $e) {
// No logger ...
return new NullLogger();
}
}
/**
* Creates a new PSR-3 compliant develop logger
*
* If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
* you'll use this logger instance for the duration of your work.
*
* It should never get filled during normal usage of Friendica
*
* @return LoggerInterface The PSR-3 compliant logger instance
* @throws \Exception
*/
public function createDev()
{
$debugging = $this->config->get('system', 'debugging');
$stream = $this->config->get('system', 'dlogfile');
$developerIp = $this->config->get('system', 'dlogip');
if ((!isset($developerIp) || !$debugging) &&
(!is_file($stream) || is_writable($stream))) {
return new NullLogger();
}
$name = $this->config->get('system', 'logger_config') ?? 'stream';
/** @var LoggerInterface */
return $this->instanceManager->getInstance(LoggerInterface::class, $name, [self::DEV_CHANNEL, LogLevel::DEBUG, $stream]);
}
/**
* Mapping a legacy level to the PSR-3 compliant levels
*
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
*
* @param string $level the level to be mapped
*
* @return string the PSR-3 compliant level
*/
private static function mapLegacyConfigDebugLevel(string $level): string
{
switch ($level) {
// legacy WARNING
case "0":
return LogLevel::ERROR;
// legacy INFO
case "1":
return LogLevel::WARNING;
// legacy TRACE
case "2":
return LogLevel::NOTICE;
// legacy DEBUG
case "3":
return LogLevel::INFO;
// legacy DATA
case "4":
// legacy ALL
case "5":
return LogLevel::DEBUG;
// default if nothing set
default:
return $level;
}
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Type\ProfilerLogger as ProfilerLoggerClass;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* The logger factory for the ProfilerLogger
*
* @see ProfilerLoggerClass
*/
class ProfilerLogger extends AbstractLoggerTypeFactory
{
/**
* Wraps a given Logger with profiling information in case profiling is enabled
*
* @param IManageConfigValues $config The system configuration
* @param LoggerInterface $logger The given logger class, which should get wrapped
* @param Profiler $profiler The profiler utility
*
* @return LoggerInterface The PSR-3 compliant logger instance
*/
public function create(IManageConfigValues $config, LoggerInterface $logger, Profiler $profiler): LoggerInterface
{
if ($config->get('system', 'profiling') ?? false) {
return $logger;
} else {
return new ProfilerLoggerClass($logger, $profiler);
}
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capabilities\LogChannel;
use Friendica\Core\Logger\Exception\LoggerArgumentException;
use Friendica\Core\Logger\Exception\LoggerException;
use Friendica\Core\Logger\Type\StreamLogger as StreamLoggerClass;
use Friendica\Core\Logger\Util\FileSystem;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
/**
* The logger factory for the StreamLogger instance
*
* @see StreamLoggerClass
*/
class StreamLogger extends AbstractLoggerTypeFactory
{
/**
* Creates a new PSR-3 compliant stream logger instance
*
* @param IManageConfigValues $config The system configuration
* @param string|null $logfile (optional) A given logfile which should be used as stream (e.g. in case of
* developer logging)
* @param string|null $channel (optional) A given channel in case it is different from the default
*
* @return LoggerInterface The PSR-3 compliant logger instance
*
* @throws LoggerException in case the logger cannot get created
*/
public function create(IManageConfigValues $config, string $logfile = null, string $channel = null): LoggerInterface
{
$fileSystem = new FileSystem();
$logfile = $logfile ?? $config->get('system', 'logfile');
if ((@file_exists($logfile) && !@is_writable($logfile)) && !@is_writable(basename($logfile))) {
throw new LoggerArgumentException(sprintf('%s is not a valid logfile', $logfile));
}
$loglevel = static::mapLegacyConfigDebugLevel($config->get('system', 'loglevel'));
if (array_key_exists($loglevel, StreamLoggerClass::levelToInt)) {
$loglevel = StreamLoggerClass::levelToInt[$loglevel];
} else {
$loglevel = StreamLoggerClass::levelToInt[LogLevel::NOTICE];
}
$stream = $fileSystem->createStream($logfile);
return new StreamLoggerClass($channel ?? $this->channel, $this->introspection, $stream, $loglevel, getmypid());
}
/**
* Creates a new PSR-3 compliant develop logger
*
* If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
* you'll use this logger instance for the duration of your work.
*
* It should never get filled during normal usage of Friendica
*
* @return LoggerInterface The PSR-3 compliant logger instance
*
* @throws LoggerException
*/
public function createDev(IManageConfigValues $config)
{
$debugging = $config->get('system', 'debugging');
$logfile = $config->get('system', 'dlogfile');
$developerIp = $config->get('system', 'dlogip');
if ((!isset($developerIp) || !$debugging) &&
(!is_file($logfile) || is_writable($logfile))) {
return new NullLogger();
}
return $this->create($config, $logfile, LogChannel::DEV);
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Exception\LoggerException;
use Friendica\Core\Logger\Type\SyslogLogger as SyslogLoggerClass;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* The logger factory for the SyslogLogger instance
*
* @see SyslogLoggerClass
*/
class SyslogLogger extends AbstractLoggerTypeFactory
{
/**
* Creates a new PSR-3 compliant syslog logger instance
*
* @param IManageConfigValues $config The system configuration
*
* @return LoggerInterface The PSR-3 compliant logger instance
*
* @throws LoggerException in case the logger cannot get created
*/
public function create(IManageConfigValues $config): LoggerInterface
{
$logOpts = $config->get('system', 'syslog_flags') ?? SyslogLoggerClass::DEFAULT_FLAGS;
$logFacility = $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY;
$loglevel = SyslogLogger::mapLegacyConfigDebugLevel($config->get('system', 'loglevel'));
if (!array_key_exists($loglevel, SyslogLoggerClass::logLevels)) {
$loglevel = SyslogLoggerClass::logLevels[$loglevel];
} else {
$loglevel = SyslogLoggerClass::logLevels[LogLevel::NOTICE];
}
return new SyslogLoggerClass($this->channel, $this->introspection, $loglevel, $logOpts, $logFacility);
}
}

View file

@ -1,26 +0,0 @@
## Friendica\Util\Logger
This namespace contains the different implementations of a Logger.
### Configuration guideline
The following settings are possible for `logger_config`:
- [`stream`](StreamLogger.php): A small logger for files or streams
- [`syslog`](SyslogLogger.php): Prints the logging output into the syslog
[`VoidLogger`](VoidLogger.php) is a fallback logger without any function if no debugging is enabled.
[`ProfilerLogger`](ProfilerLogger.php) is a wrapper around an existing logger in case profiling is enabled for Friendica.
Every log call will be saved to the `Profiler` with a timestamp.
### Implementation guideline
Each logging implementation should pe capable of printing at least the following information:
- An unique ID for each Request/Call
- The process ID (PID)
- A timestamp of the logging entry
- The critically of the log entry
- A log message
- A context of the log message (f.e which user)
If possible, a Logger should extend [`AbstractLogger`](AbstractLogger.php), because it contains additional, Friendica specific business logic for each logging call.

View file

@ -21,20 +21,16 @@
namespace Friendica\Core\Logger\Type;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections;
use Friendica\Core\Logger\Exception\LoggerArgumentException;
use Friendica\Core\Logger\Exception\LoggerException;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\FileSystem;
use Psr\Log\LogLevel;
/**
* A Logger instance for logging into a stream (file, stdout, stderr)
*/
class StreamLogger extends AbstractLogger implements IAmAStrategy
class StreamLogger extends AbstractLogger
{
/**
* The minimum loglevel at which this logger will be triggered
@ -42,12 +38,6 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
*/
private $logLevel;
/**
* The file URL of the stream (if needed)
* @var string
*/
private $url;
/**
* The stream, where the current logger is writing into
* @var resource
@ -60,16 +50,11 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
*/
private $pid;
/**
* @var FileSystem
*/
private $fileSystem;
/**
* Translates LogLevel log levels to integer values
* @var array
*/
private $levelToInt = [
public const levelToInt = [
LogLevel::EMERGENCY => 0,
LogLevel::ALERT => 1,
LogLevel::CRITICAL => 2,
@ -84,41 +69,20 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
* {@inheritdoc}
* @param string $level The minimum loglevel at which this logger will be triggered
*
* @throws LoggerArgumentException
* @throws LogLevelException
* @throws LoggerException
*/
public function __construct(string $channel, IManageConfigValues $config, IHaveCallIntrospections $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG)
public function __construct(string $channel, IHaveCallIntrospections $introspection, $stream, int $logLevel, int $pid)
{
$this->fileSystem = $fileSystem;
$stream = $this->logfile ?? $config->get('system', 'logfile');
if ((@file_exists($stream) && !@is_writable($stream)) && !@is_writable(basename($stream))) {
throw new LoggerArgumentException(sprintf('%s is not a valid logfile', $stream));
}
parent::__construct($channel, $introspection);
if (is_resource($stream)) {
$this->stream = $stream;
} elseif (is_string($stream)) {
$this->url = $stream;
} else {
throw new LoggerArgumentException('A stream must either be a resource or a string.');
}
$this->pid = getmypid();
if (array_key_exists($level, $this->levelToInt)) {
$this->logLevel = $this->levelToInt[$level];
} else {
throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
}
$this->checkStream();
$this->stream = $stream;
$this->pid = $pid;
$this->logLevel = $logLevel;
}
public function close()
{
if ($this->url && is_resource($this->stream)) {
if (is_resource($this->stream)) {
fclose($this->stream);
}
@ -139,18 +103,16 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
*/
protected function addEntry($level, string $message, array $context = [])
{
if (!array_key_exists($level, $this->levelToInt)) {
if (!array_key_exists($level, static::levelToInt)) {
throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
}
$logLevel = $this->levelToInt[$level];
$logLevel = static::levelToInt[$level];
if ($logLevel > $this->logLevel) {
return;
}
$this->checkStream();
$formattedLog = $this->formatLog($level, $message, $context);
fwrite($this->stream, $formattedLog);
}
@ -185,27 +147,4 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
return $logMessage;
}
/**
* Checks the current stream
*
* @throws LoggerException
* @throws LoggerArgumentException
*/
private function checkStream()
{
if (is_resource($this->stream)) {
return;
}
if (empty($this->url)) {
throw new LoggerArgumentException('Missing stream URL.');
}
try {
$this->stream = $this->fileSystem->createStream($this->url);
} catch (\UnexpectedValueException $exception) {
throw new LoggerException('Cannot create stream.', $exception);
}
}
}

View file

@ -21,8 +21,6 @@
namespace Friendica\Core\Logger\Type;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections;
use Friendica\Core\Logger\Exception\LoggerException;
use Friendica\Core\Logger\Exception\LogLevelException;
@ -32,7 +30,7 @@ use Psr\Log\LogLevel;
* A Logger instance for syslogging (fast, but simple)
* @see http://php.net/manual/en/function.syslog.php
*/
class SyslogLogger extends AbstractLogger implements IAmAStrategy
class SyslogLogger extends AbstractLogger
{
const IDENT = 'Friendica';
@ -45,7 +43,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
* Translates LogLevel log levels to syslog log priorities.
* @var array
*/
private $logLevels = [
public const logLevels = [
LogLevel::DEBUG => LOG_DEBUG,
LogLevel::INFO => LOG_INFO,
LogLevel::NOTICE => LOG_NOTICE,
@ -60,7 +58,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
* Translates log priorities to string outputs
* @var array
*/
private $logToString = [
protected const logToString = [
LOG_DEBUG => 'DEBUG',
LOG_INFO => 'INFO',
LOG_NOTICE => 'NOTICE',
@ -101,19 +99,18 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
/**
* {@inheritdoc}
* @param string $level The minimum loglevel at which this logger will be triggered
*
* @throws LogLevelException
* @throws LoggerException
* @param string $logLevel The minimum loglevel at which this logger will be triggered
* @param string $logOptions
* @param string $logFacility
*/
public function __construct(string $channel, IManageConfigValues $config, IHaveCallIntrospections $introspection, string $level = LogLevel::NOTICE)
public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility)
{
parent::__construct($channel, $introspection);
$this->logOpts = $config->get('system', 'syslog_flags') ?? static::DEFAULT_FLAGS;
$this->logFacility = $config->get('system', 'syslog_facility') ?? static::DEFAULT_FACILITY;
$this->logLevel = $this->mapLevelToPriority($level);
$this->introspection->addClasses([self::class]);
$this->logOpts = $logOptions;
$this->logFacility = $logFacility;
$this->logLevel = $logLevel;
}
/**
@ -149,11 +146,11 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
*/
public function mapLevelToPriority(string $level): int
{
if (!array_key_exists($level, $this->logLevels)) {
if (!array_key_exists($level, static::logLevels)) {
throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
}
return $this->logLevels[$level];
return static::logLevels[$level];
}
/**
@ -202,7 +199,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
$record = array_merge($record, ['uid' => $this->logUid]);
$logMessage = $this->channel . ' ';
$logMessage .= '[' . $this->logToString[$level] . ']: ';
$logMessage .= '[' . static::logToString[$level] . ']: ';
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
$logMessage .= $this->jsonEncodeArray($context) . ' - ';
$logMessage .= $this->jsonEncodeArray($record);

View file

@ -19,10 +19,10 @@
*
*/
namespace Friendica\Util;
namespace Friendica\Core\Logger\Util;
/**
* Util class for filesystem manipulation
* Util class for filesystem manipulation for Logger classes
*/
class FileSystem
{
@ -38,7 +38,7 @@ class FileSystem
*
* @return string The directory name (empty if no directory is found, like urls)
*/
public function createDir(string $file)
public function createDir(string $file): string
{
$dirname = null;
$pos = strpos($file, '://');

View file

@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Util;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Logger\Capabilities\ICheckLoggerSettings;
use Friendica\Core\Logger\Exception\LoggerUnusableException;
/** {@inheritDoc} */
class LoggerSettingsCheck implements ICheckLoggerSettings
{
/** @var IManageConfigValues */
protected $config;
/** @var $fileSystem */
protected $fileSystem;
/** @var L10n */
protected $l10n;
public function __construct(IManageConfigValues $config, FileSystem $fileSystem, L10n $l10n)
{
$this->config = $config;
$this->fileSystem = $fileSystem;
$this->l10n = $l10n;
}
/** {@inheritDoc} */
public function checkLogfile(): ?string
{
// Check logfile permission
if ($this->config->get('system', 'debugging')) {
$file = $this->config->get('system', 'logfile');
try {
$stream = $this->fileSystem->createStream($file);
if (!isset($stream)) {
throw new LoggerUnusableException('Stream is null.');
}
} catch (\Throwable $exception) {
return $this->l10n->t('The logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage());
}
}
return null;
}
/** {@inheritDoc} */
public function checkDebugLogfile(): ?string
{
// Check logfile permission
if ($this->config->get('system', 'debugging')) {
$file = $this->config->get('system', 'dlogfile');
if (empty($file)) {
return null;
}
try {
$stream = $this->fileSystem->createStream($file);
if (!isset($stream)) {
throw new LoggerUnusableException('Stream is null.');
}
} catch (\Throwable $exception) {
return $this->l10n->t('The debug logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage());
}
}
return null;
}
}

View file

@ -22,10 +22,12 @@
namespace Friendica;
use Dice\Dice;
use Friendica\Core\Session\Capability\IHandleSessions;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Navigation\SystemMessages;
use Psr\Log\LoggerInterface;
use \Friendica\Core\Logger\Capabilities\ICheckLoggerSettings;
use \Friendica\Core\Logger\Util\LoggerSettingsCheck;
use \Friendica\Core\Session\Capability\IHandleSessions;
use \Friendica\Core\Session\Capability\IHandleUserSessions;
use \Friendica\Navigation\SystemMessages;
use \Psr\Log\LoggerInterface;
/**
* This class is capable of getting all dynamic created classes
@ -295,6 +297,11 @@ abstract class DI
static::init($flushDice);
}
public static function loggCheck(): ICheckLoggerSettings
{
return self::$dice->create(LoggerSettingsCheck::class);
}
/**
* @return LoggerInterface
*/
@ -692,14 +699,6 @@ abstract class DI
return self::$dice->create(Util\DateTimeFormat::class);
}
/**
* @return Util\FileSystem
*/
public static function fs()
{
return self::$dice->create(Util\FileSystem::class);
}
/**
* @return Util\Profiler
*/

View file

@ -126,35 +126,11 @@ class Summary extends BaseAdmin
}
// Check logfile permission
if (DI::config()->get('system', 'debugging')) {
$file = DI::config()->get('system', 'logfile');
$fileSystem = DI::fs();
try {
$stream = $fileSystem->createStream($file);
if (!isset($stream)) {
throw new ServiceUnavailableException('Stream is null.');
}
} catch (\Throwable $exception) {
$warningtext[] = DI::l10n()->t('The logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage());
}
$file = DI::config()->get('system', 'dlogfile');
try {
if (!empty($file)) {
$stream = $fileSystem->createStream($file);
if (!isset($stream)) {
throw new ServiceUnavailableException('Stream is null.');
}
}
} catch (\Throwable $exception) {
$warningtext[] = DI::l10n()->t('The debug logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage());
}
if (($return = DI::loggCheck()->checkLogfile()) !== null) {
$warningtext[] = $return;
}
if (($return = DI::loggCheck()->checkDebugLogfile()) !== null) {
$warningtext[] = $return;
}
// check legacy basepath settings

View file

@ -37,8 +37,9 @@ use Dice\Dice;
use Friendica\App;
use Friendica\Core\Cache;
use Friendica\Core\Config;
use Friendica\Core\Hooks\Capabilities\ICanManageInstances;
use Friendica\Core\Hooks\Model\InstanceManager;
use Friendica\Core\Hooks\Capabilities\ICanCreateInstances;
use Friendica\Core\Hooks\Capabilities\ICanRegisterInstances;
use Friendica\Core\Hooks\Model\DiceInstanceManager;
use Friendica\Core\PConfig;
use Friendica\Core\L10n;
use Friendica\Core\Lock;
@ -62,6 +63,13 @@ return [
// one instance for the whole execution
'shared' => true,
],
\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class => [
'instanceOf' => \Friendica\Core\Addon\Model\AddonLoader::class,
'constructParams' => [
[Dice::INSTANCE => '$basepath'],
[Dice::INSTANCE => Dice::SELF],
],
],
'$basepath' => [
'instanceOf' => Util\BasePath::class,
'call' => [
@ -78,8 +86,24 @@ return [
$_SERVER
]
],
ICanManageInstances::class => [
'instanceOf' => InstanceManager::class,
DiceInstanceManager::class => [
'constructParams' => [
[Dice::INSTANCE => Dice::SELF],
]
],
\Friendica\Core\Hooks\Util\HookFileManager::class => [
'constructParams' => [
[Dice::INSTANCE => '$basepath'],
],
],
ICanRegisterInstances::class => [
'instanceOf' => DiceInstanceManager::class,
'constructParams' => [
[Dice::INSTANCE => Dice::SELF],
],
],
ICanCreateInstances::class => [
'instanceOf' => DiceInstanceManager::class,
'constructParams' => [
[Dice::INSTANCE => Dice::SELF],
],
@ -156,40 +180,40 @@ return [
[Dice::INSTANCE => '$basepath'],
],
],
/**
* Create a Logger, which implements the LoggerInterface
*
* Same as:
* $loggerFactory = new Factory\LoggerFactory();
* $logger = $loggerFactory->create($channel, $configuration, $profiler);
*
* Attention1: We can use DICE for detecting dependencies inside "chained" calls too
* Attention2: The variable "$channel" is passed inside the creation of the dependencies per:
* $app = $dice->create(App::class, [], ['$channel' => 'index']);
* and is automatically passed as an argument with the same name
*/
LoggerInterface::class => [
\Psr\Log\LoggerInterface::class => [
'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
'constructParams' => [
'index',
],
'call' => [
['create', [], Dice::CHAIN_CALL],
],
],
'$devLogger' => [
'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
'constructParams' => [
'dev',
],
\Friendica\Core\Logger\Type\SyslogLogger::class => [
'instanceOf' => \Friendica\Core\Logger\Factory\SyslogLogger::class,
'call' => [
['createDev', [], Dice::CHAIN_CALL],
]
['create', [], Dice::CHAIN_CALL],
],
],
\Friendica\Core\Logger\Type\StreamLogger::class => [
'instanceOf' => \Friendica\Core\Logger\Factory\StreamLogger::class,
'call' => [
['create', [], Dice::CHAIN_CALL],
],
],
\Friendica\Core\Logger\Type\ProfilerLogger::class => [
'instanceOf' => \Friendica\Core\Logger\Factory\ProfilerLogger::class,
'call' => [
['create', [], Dice::CHAIN_CALL],
],
],
\Friendica\Core\Logger\Capabilities\IHaveCallIntrospections::class => [
'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class,
'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class,
'constructParams' => [
\Friendica\Core\Logger\Util\Introspection::IGNORE_CLASS_LIST,
\Friendica\Core\Logger\Capabilities\IHaveCallIntrospections::IGNORE_CLASS_LIST,
],
],
'$devLogger' => [
'instanceOf' => \Friendica\Core\Logger\Factory\StreamLogger::class,
'call' => [
['createDev', [], Dice::CHAIN_CALL],
],
],
Cache\Capability\ICanCache::class => [

37
static/hooks.config.php Normal file
View file

@ -0,0 +1,37 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\Core\Hooks\Capabilities\HookType as H;
return [
H::STRATEGY => [
\Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''],
\Friendica\Core\Logger\Type\SyslogLogger::class => ['syslog'],
\Friendica\Core\Logger\Type\StreamLogger::class => ['stream'],
],
],
H::DECORATOR => [
\Psr\Log\LoggerInterface::class => [
\Friendica\Core\Logger\Type\ProfilerLogger::class,
],
],
];

View file

@ -21,9 +21,7 @@
namespace Friendica\Test\Util\Hooks\InstanceMocks;
use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
class FakeInstance implements IAmADecoratedInterface, IAmAStrategy
class FakeInstance
{
protected $aText = null;
protected $cBool = null;

View file

@ -22,7 +22,7 @@
namespace Friendica\Test\src\Core\Hooks\Model;
use Dice\Dice;
use Friendica\Core\Hooks\Model\InstanceManager;
use Friendica\Core\Hooks\Model\DiceInstanceManager;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance;
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator;
@ -32,12 +32,12 @@ class InstanceManagerTest extends MockedTest
{
public function testEqualButNotSameInstance()
{
$instance = new InstanceManager(new Dice());
$instance = new DiceInstanceManager(new Dice());
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class);
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake');
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake');
self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($getInstanceA, $getInstanceB);
@ -81,7 +81,7 @@ class InstanceManagerTest extends MockedTest
*/
public function testInstanceWithConstructorAnonymArgs(string $aString = null, bool $cBool = null, string $bString = null)
{
$instance = new InstanceManager(new Dice());
$instance = new DiceInstanceManager(new Dice());
$args = [];
@ -98,9 +98,9 @@ class InstanceManagerTest extends MockedTest
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
/** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake');
/** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake');
self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($getInstanceA, $getInstanceB);
@ -117,7 +117,7 @@ class InstanceManagerTest extends MockedTest
*/
public function testInstanceConstructorAndGetInstanceWithNamedArgs(string $aString = null, bool $cBool = null, string $bString = null)
{
$instance = new InstanceManager(new Dice());
$instance = new DiceInstanceManager(new Dice());
$args = [];
@ -131,9 +131,9 @@ class InstanceManagerTest extends MockedTest
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
/** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]);
/** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]);
self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($getInstanceA, $getInstanceB);
@ -150,7 +150,7 @@ class InstanceManagerTest extends MockedTest
*/
public function testInstanceWithTwoStrategies(string $aString = null, bool $cBool = null, string $bString = null)
{
$instance = new InstanceManager(new Dice());
$instance = new DiceInstanceManager(new Dice());
$args = [];
@ -165,9 +165,9 @@ class InstanceManagerTest extends MockedTest
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
/** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]);
/** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]);
self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($getInstanceA, $getInstanceB);
@ -184,7 +184,7 @@ class InstanceManagerTest extends MockedTest
*/
public function testDecorator(string $aString = null, bool $cBool = null, string $bString = null)
{
$instance = new InstanceManager(new Dice());
$instance = new DiceInstanceManager(new Dice());
$args = [];
@ -202,9 +202,9 @@ class InstanceManagerTest extends MockedTest
$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]);
/** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]);
/** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]);
self::assertEquals(2, FakeInstanceDecorator::$countInstance);
self::assertEquals($getInstanceA, $getInstanceB);
@ -222,7 +222,7 @@ class InstanceManagerTest extends MockedTest
*/
public function testTwoDecoratorWithPrefix(string $aString = null, bool $cBool = null, string $bString = null)
{
$instance = new InstanceManager(new Dice());
$instance = new DiceInstanceManager(new Dice());
$args = [];
@ -241,9 +241,9 @@ class InstanceManagerTest extends MockedTest
$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class);
/** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]);
/** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]);
self::assertEquals(4, FakeInstanceDecorator::$countInstance);
self::assertEquals($getInstanceA, $getInstanceB);

View file

@ -24,7 +24,7 @@ namespace Friendica\Test\src\Core\Logger;
use Friendica\Core\Logger\Exception\LoggerArgumentException;
use Friendica\Core\Logger\Exception\LoggerException;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Util\FileSystem;
use Friendica\Core\Logger\Util\FileSystem;
use Friendica\Test\Util\VFSTrait;
use Friendica\Core\Logger\Type\StreamLogger;
use org\bovigo\vfs\vfsStream;
@ -41,7 +41,7 @@ class StreamLoggerTest extends AbstractLoggerTest
private $logfile;
/**
* @var Filesystem
* @var \Friendica\Core\Logger\Util\Filesystem
*/
private $fileSystem;