From f609e38600f54fe8465d1194008d823103f41baa Mon Sep 17 00:00:00 2001
From: Philipp <admin@philipp.info>
Date: Sun, 15 Jan 2023 22:31:19 +0100
Subject: [PATCH] Introduce new Hook logic - InstanceManager for computing
 strategies and to allow decorators - Adapting Core\Logger to use it

---
 src/Core/Hooks/Capabilities/IAmAStrategy.php  |  29 ++
 .../Capabilities/ICanManageInstances.php      |  81 ++++++
 .../Exceptions/HookInstanceException.php      |  30 ++
 .../HookRegisterArgumentException.php         |  30 ++
 src/Core/Hooks/Model/InstanceManager.php      | 104 +++++++
 .../Exception/LoggerInvalidException.php      |  32 +++
 src/Core/Logger/Factory/Logger.php            | 184 +++----------
 src/Core/Logger/Type/StreamLogger.php         |  12 +-
 src/Core/Logger/Type/SyslogLogger.php         |  13 +-
 src/Database/Database.php                     |  26 +-
 static/dependencies.config.php                |   8 +
 tests/Util/CreateDatabaseTrait.php            |   3 +-
 .../Util/Hooks/InstanceMocks/FakeInstance.php |  60 ++++
 .../InstanceMocks/FakeInstanceDecorator.php   |  59 ++++
 .../InstanceMocks/IAmADecoratedInterface.php  |  33 +++
 tests/functional/DependencyCheckTest.php      |  20 +-
 tests/src/Core/Cache/DatabaseCacheTest.php    |   2 +-
 .../Core/Hooks/Model/InstanceManagerTest.php  | 258 ++++++++++++++++++
 tests/src/Core/Logger/AbstractLoggerTest.php  |   7 +
 tests/src/Core/Logger/StreamLoggerTest.php    |  68 ++---
 tests/src/Core/Logger/SyslogLoggerTest.php    |  12 +-
 tests/src/Core/Logger/SyslogLoggerWrapper.php |   5 +-
 22 files changed, 842 insertions(+), 234 deletions(-)
 create mode 100644 src/Core/Hooks/Capabilities/IAmAStrategy.php
 create mode 100644 src/Core/Hooks/Capabilities/ICanManageInstances.php
 create mode 100644 src/Core/Hooks/Exceptions/HookInstanceException.php
 create mode 100644 src/Core/Hooks/Exceptions/HookRegisterArgumentException.php
 create mode 100644 src/Core/Hooks/Model/InstanceManager.php
 create mode 100644 src/Core/Logger/Exception/LoggerInvalidException.php
 create mode 100644 tests/Util/Hooks/InstanceMocks/FakeInstance.php
 create mode 100644 tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php
 create mode 100644 tests/Util/Hooks/InstanceMocks/IAmADecoratedInterface.php
 create mode 100644 tests/src/Core/Hooks/Model/InstanceManagerTest.php

diff --git a/src/Core/Hooks/Capabilities/IAmAStrategy.php b/src/Core/Hooks/Capabilities/IAmAStrategy.php
new file mode 100644
index 0000000000..a830d912c0
--- /dev/null
+++ b/src/Core/Hooks/Capabilities/IAmAStrategy.php
@@ -0,0 +1,29 @@
+<?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;
+
+/**
+ * All interfaces, marking this interface are valid Strategies for Hook calls
+ */
+interface IAmAStrategy
+{
+}
diff --git a/src/Core/Hooks/Capabilities/ICanManageInstances.php b/src/Core/Hooks/Capabilities/ICanManageInstances.php
new file mode 100644
index 0000000000..08802a08e6
--- /dev/null
+++ b/src/Core/Hooks/Capabilities/ICanManageInstances.php
@@ -0,0 +1,81 @@
+<?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;
+use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException;
+
+/**
+ * Managing special instance and decorator treatments for classes
+ */
+interface ICanManageInstances
+{
+	/**
+	 * 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      The name of the given class, which will be used for factories, dependency injections etc.
+	 * @param string $class     The class of the given class
+	 * @param ?array  $arguments Additional arguments, which can be passed to the constructor
+	 *
+	 * @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;
+
+	/**
+	 * Register a new decorator for a given class or interface
+	 * @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 class or interface, which gets decorated by a class
+	 * @param string $decoratorClass 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"
+	 *
+	 * @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     A given class or interface, which will get returned
+	 * @param string $name      The name of the concrete class, wich
+	 * @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;
+}
diff --git a/src/Core/Hooks/Exceptions/HookInstanceException.php b/src/Core/Hooks/Exceptions/HookInstanceException.php
new file mode 100644
index 0000000000..bda8e7c8fb
--- /dev/null
+++ b/src/Core/Hooks/Exceptions/HookInstanceException.php
@@ -0,0 +1,30 @@
+<?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\Exceptions;
+
+class HookInstanceException extends \RuntimeException
+{
+	public function __construct($message = "", \Throwable $previous = null)
+	{
+		parent::__construct($message, 500, $previous);
+	}
+}
diff --git a/src/Core/Hooks/Exceptions/HookRegisterArgumentException.php b/src/Core/Hooks/Exceptions/HookRegisterArgumentException.php
new file mode 100644
index 0000000000..799c4294be
--- /dev/null
+++ b/src/Core/Hooks/Exceptions/HookRegisterArgumentException.php
@@ -0,0 +1,30 @@
+<?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\Exceptions;
+
+class HookRegisterArgumentException extends \RuntimeException
+{
+	public function __construct($message = "", \Throwable $previous = null)
+	{
+		parent::__construct($message, 500, $previous);
+	}
+}
diff --git a/src/Core/Hooks/Model/InstanceManager.php b/src/Core/Hooks/Model/InstanceManager.php
new file mode 100644
index 0000000000..4b8a0e6de8
--- /dev/null
+++ b/src/Core/Hooks/Model/InstanceManager.php
@@ -0,0 +1,104 @@
+<?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 subsituation 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;
+	}
+}
diff --git a/src/Core/Logger/Exception/LoggerInvalidException.php b/src/Core/Logger/Exception/LoggerInvalidException.php
new file mode 100644
index 0000000000..db6ecb78c5
--- /dev/null
+++ b/src/Core/Logger/Exception/LoggerInvalidException.php
@@ -0,0 +1,32 @@
+<?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;
+
+class LoggerInvalidException extends \RuntimeException
+{
+	public function __construct($message = "", Throwable $previous = null)
+	{
+		parent::__construct($message, 500, $previous);
+	}
+}
diff --git a/src/Core/Logger/Factory/Logger.php b/src/Core/Logger/Factory/Logger.php
index 3dd2a81dc2..2821a813c2 100644
--- a/src/Core/Logger/Factory/Logger.php
+++ b/src/Core/Logger/Factory/Logger.php
@@ -22,16 +22,11 @@
 namespace Friendica\Core\Logger\Factory;
 
 use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core;
-use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections;
+use Friendica\Core\Hooks\Capabilities\ICanManageInstances;
 use Friendica\Core\Logger\Exception\LogLevelException;
-use Friendica\Database\Database;
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\FileSystem;
 use Friendica\Core\Logger\Type\ProfilerLogger;
 use Friendica\Core\Logger\Type\StreamLogger;
 use Friendica\Core\Logger\Type\SyslogLogger;
-use Friendica\Util\Profiler;
 use Psr\Log\LoggerInterface;
 use Psr\Log\LogLevel;
 use Psr\Log\NullLogger;
@@ -44,115 +39,55 @@ class Logger
 	const DEV_CHANNEL = 'dev';
 
 	/** @var string The log-channel (app, worker, ...) */
-	private $channel;
+	protected $channel;
+	/** @var ICanManageInstances */
+	protected $instanceManager;
+	/** @var IManageConfigValues */
+	protected $config;
 
-	public function __construct(string $channel, bool $includeAddon = true)
+	public function __construct(string $channel, ICanManageInstances $instanceManager, IManageConfigValues $config, string $logfile = null)
 	{
-		$this->channel = $channel;
+		$this->channel         = $channel;
+		$this->instanceManager = $instanceManager;
+		$this->config          = $config;
 
-		/// @fixme clean solution = Making Addon & Hook dynamic and load them inside the constructor, so there's no custom load logic necessary anymore
-		if ($includeAddon) {
-			Core\Addon::loadAddons();
-			Core\Hook::loadHooks();
+		$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);
 		}
 	}
 
 	/**
 	 * Creates a new PSR-3 compliant logger instances
 	 *
-	 * @param Database            $database   The Friendica Database instance
-	 * @param IManageConfigValues $config     The config
-	 * @param Profiler            $profiler   The profiler of the app
-	 * @param FileSystem          $fileSystem FileSystem utils
-	 * @param string|null         $minLevel   (optional) Override minimum Loglevel to log
+	 * @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(Database $database, IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem, IHaveCallIntrospections $introspection, ?string $minLevel = null): LoggerInterface
+	public function create(string $loglevel = null): LoggerInterface
 	{
-		if (empty($config->get('system', 'debugging', false))) {
-			$logger = new NullLogger();
-			$database->setLogger($logger);
-			return $logger;
-		}
-
-		$minLevel      = $minLevel ?? $config->get('system', 'loglevel');
-		$loglevel      = self::mapLegacyConfigDebugLevel((string)$minLevel);
-
-		$name = $config->get('system', 'logger_config', 'stream');
-
-		switch ($name) {
-			case 'syslog':
-				try {
-					$logger = new SyslogLogger($this->channel, $introspection, $loglevel, $config->get('system', 'syslog_flags', SyslogLogger::DEFAULT_FLAGS), $config->get('system', 'syslog_facility', SyslogLogger::DEFAULT_FACILITY));
-				} catch (LogLevelException $exception) {
-					// If there's a wrong config value for loglevel, try again with standard
-					$logger = $this->create($database, $config, $profiler, $fileSystem,  $introspection, LogLevel::NOTICE);
-					$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
-				} catch (\Throwable $e) {
-					// No logger ...
-					$logger = new NullLogger();
-				}
-				break;
-
-			case 'stream':
-			default:
-				$data = [
-					'name'          => $name,
-					'channel'       => $this->channel,
-					'introspection' => $introspection,
-					'loglevel'      => $loglevel,
-					'logger'        => null,
-				];
-				try {
-					Core\Hook::callAll('logger_instance', $data);
-				} catch (InternalServerErrorException $exception) {
-					$data['logger'] = null;
-				}
-
-				if (($data['logger'] ?? null) instanceof LoggerInterface) {
-					$logger = $data['logger'];
-				}
-
-				if (empty($logger)) {
-					$stream = $config->get('system', 'logfile');
-					// just add a stream in case it's either writable or not file
-					if (!is_file($stream) || is_writable($stream)) {
-						try {
-							$logger = new StreamLogger($this->channel, $stream, $introspection, $fileSystem, $loglevel);
-						} catch (LogLevelException $exception) {
-							// If there's a wrong config value for loglevel, try again with standard
-							$logger = $this->create($database, $config, $profiler, $fileSystem, $introspection, LogLevel::NOTICE);
-							$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
-						} catch (\Throwable $t) {
-							// No logger ...
-							$logger = new NullLogger();
-						}
-					} else {
-						try {
-							$logger = new SyslogLogger($this->channel, $introspection, $loglevel);
-						} catch (LogLevelException $exception) {
-							// If there's a wrong config value for loglevel, try again with standard
-							$logger = $this->create($database, $config, $profiler, $fileSystem, $introspection, LogLevel::NOTICE);
-							$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
-						} catch (\Throwable $e) {
-							// No logger ...
-							$logger = new NullLogger();
-						}
-					}
-				}
-				break;
+		if (empty($this->config->get('system', 'debugging') ?? false)) {
+			return new NullLogger();
 		}
 
-		$profiling = $config->get('system', 'profiling', false);
+		$loglevel = $loglevel ?? static::mapLegacyConfigDebugLevel($this->config->get('system', 'loglevel'));
+		$name     = $this->config->get('system', 'logger_config') ?? 'stream';
 
-		// In case profiling is enabled, wrap the ProfilerLogger around the current logger
-		if (isset($profiling) && $profiling !== false) {
-			$logger = new ProfilerLogger($logger, $profiler);
+		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) {
+			// No logger ...
+			return new NullLogger();
 		}
-
-		$database->setLogger($logger);
-		return $logger;
 	}
 
 	/**
@@ -163,63 +98,24 @@ class Logger
 	 *
 	 * It should never get filled during normal usage of Friendica
 	 *
-	 * @param IManageConfigValues $config     The config
-	 * @param Profiler            $profiler   The profiler of the app
-	 * @param FileSystem          $fileSystem FileSystem utils
-	 *
 	 * @return LoggerInterface The PSR-3 compliant logger instance
 	 * @throws \Exception
 	 */
-	public static function createDev(IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem, IHaveCallIntrospections $introspection)
+	public function createDev()
 	{
-		$debugging   = $config->get('system', 'debugging');
-		$stream      = $config->get('system', 'dlogfile');
-		$developerIp = $config->get('system', 'dlogip');
+		$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 = $config->get('system', 'logger_config', 'stream');
-
-		switch ($name) {
-
-			case 'syslog':
-				$logger = new SyslogLogger(self::DEV_CHANNEL, $introspection, LogLevel::DEBUG);
-				break;
-
-			case 'stream':
-			default:
-				$data = [
-					'name'          => $name,
-					'channel'       => self::DEV_CHANNEL,
-					'introspection' => $introspection,
-					'loglevel'      => LogLevel::DEBUG,
-					'logger'        => null,
-				];
-				try {
-					Core\Hook::callAll('logger_instance', $data);
-				} catch (InternalServerErrorException $exception) {
-					$data['logger'] = null;
-				}
-
-				if (($data['logger'] ?? null) instanceof LoggerInterface) {
-					return $data['logger'];
-				}
-
-				$logger = new StreamLogger(self::DEV_CHANNEL, $stream, $introspection, $fileSystem, LogLevel::DEBUG);
-				break;
-		}
-
-		$profiling = $config->get('system', 'profiling', false);
-
-		// In case profiling is enabled, wrap the ProfilerLogger around the current logger
-		if (isset($profiling) && $profiling !== false) {
-			$logger = new ProfilerLogger($logger, $profiler);
-		}
+		$name = $this->config->get('system', 'logger_config') ?? 'stream';
 
-		return $logger;
+		/** @var LoggerInterface */
+		return $this->instanceManager->getInstance(LoggerInterface::class, $name, [self::DEV_CHANNEL, LogLevel::DEBUG, $stream]);
 	}
 
 	/**
diff --git a/src/Core/Logger/Type/StreamLogger.php b/src/Core/Logger/Type/StreamLogger.php
index a444faaaba..2568fb4a2e 100644
--- a/src/Core/Logger/Type/StreamLogger.php
+++ b/src/Core/Logger/Type/StreamLogger.php
@@ -21,6 +21,8 @@
 
 namespace Friendica\Core\Logger\Type;
 
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
 use Friendica\Core\Logger\Exception\LoggerArgumentException;
 use Friendica\Core\Logger\Exception\LoggerException;
 use Friendica\Core\Logger\Exception\LogLevelException;
@@ -32,7 +34,7 @@ use Psr\Log\LogLevel;
 /**
  * A Logger instance for logging into a stream (file, stdout, stderr)
  */
-class StreamLogger extends AbstractLogger
+class StreamLogger extends AbstractLogger implements IAmAStrategy
 {
 	/**
 	 * The minimum loglevel at which this logger will be triggered
@@ -80,16 +82,20 @@ class StreamLogger extends AbstractLogger
 
 	/**
 	 * {@inheritdoc}
-	 * @param string|resource $stream The stream to write with this logger (either a file or a stream, i.e. stdout)
 	 * @param string          $level  The minimum loglevel at which this logger will be triggered
 	 *
 	 * @throws LoggerArgumentException
 	 * @throws LogLevelException
 	 */
-	public function __construct($channel, $stream, Introspection $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG)
+	public function __construct(string $channel, IManageConfigValues $config, Introspection $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG)
 	{
 		$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)) {
diff --git a/src/Core/Logger/Type/SyslogLogger.php b/src/Core/Logger/Type/SyslogLogger.php
index b38cb01855..2e4bc94077 100644
--- a/src/Core/Logger/Type/SyslogLogger.php
+++ b/src/Core/Logger/Type/SyslogLogger.php
@@ -21,6 +21,8 @@
 
 namespace Friendica\Core\Logger\Type;
 
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
 use Friendica\Core\Logger\Exception\LoggerException;
 use Friendica\Core\Logger\Exception\LogLevelException;
 use Friendica\Core\Logger\Util\Introspection;
@@ -30,7 +32,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
+class SyslogLogger extends AbstractLogger implements IAmAStrategy
 {
 	const IDENT = 'Friendica';
 
@@ -100,17 +102,16 @@ class SyslogLogger extends AbstractLogger
 	/**
 	 * {@inheritdoc}
 	 * @param string $level       The minimum loglevel at which this logger will be triggered
-	 * @param int    $logOpts     Indicates what logging options will be used when generating a log message
-	 * @param int    $logFacility Used to specify what type of program is logging the message
 	 *
 	 * @throws LogLevelException
 	 * @throws LoggerException
 	 */
-	public function __construct($channel, Introspection $introspection, string $level = LogLevel::NOTICE, int $logOpts = self::DEFAULT_FLAGS, int $logFacility = self::DEFAULT_FACILITY )
+	public function __construct(string $channel, IManageConfigValues $config, Introspection $introspection, string $level = LogLevel::NOTICE)
 	{
 		parent::__construct($channel, $introspection);
-		$this->logOpts     = $logOpts;
-		$this->logFacility = $logFacility;
+
+		$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]);
 	}
diff --git a/src/Database/Database.php b/src/Database/Database.php
index 032a282030..a5fe7f978a 100644
--- a/src/Database/Database.php
+++ b/src/Database/Database.php
@@ -36,7 +36,6 @@ use PDO;
 use PDOException;
 use PDOStatement;
 use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
 
 /**
  * This class is for the low level database stuff that does driver specific things.
@@ -81,16 +80,14 @@ class Database
 	/** @var ViewDefinition */
 	protected $viewDefinition;
 
-	public function __construct(IManageConfigValues $config, Profiler $profiler, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition)
+	public function __construct(IManageConfigValues $config, Profiler $profiler, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition, LoggerInterface $logger)
 	{
 		// We are storing these values for being able to perform a reconnect
-		$this->config   = $config;
-		$this->profiler = $profiler;
+		$this->config         = $config;
+		$this->profiler       = $profiler;
 		$this->dbaDefinition  = $dbaDefinition;
 		$this->viewDefinition = $viewDefinition;
-
-		// Temporary NullLogger until we can fetch the logger class from the config
-		$this->logger = new NullLogger();
+		$this->logger         = $logger;
 
 		$this->connect();
 	}
@@ -196,21 +193,6 @@ class Database
 		$this->testmode = $test;
 	}
 
-	/**
-	 * Sets the logger for DBA
-	 *
-	 * @note this is necessary because if we want to load the logger configuration
-	 *       from the DB, but there's an error, we would print out an exception.
-	 *       So the logger gets updated after the logger configuration can be retrieved
-	 *       from the database
-	 *
-	 * @param LoggerInterface $logger
-	 */
-	public function setLogger(LoggerInterface $logger)
-	{
-		$this->logger = $logger;
-	}
-
 	/**
 	 * Sets the profiler for DBA
 	 *
diff --git a/static/dependencies.config.php b/static/dependencies.config.php
index 1836a44dd5..5b7a345685 100644
--- a/static/dependencies.config.php
+++ b/static/dependencies.config.php
@@ -37,6 +37,8 @@ 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\PConfig;
 use Friendica\Core\L10n;
 use Friendica\Core\Lock;
@@ -76,6 +78,12 @@ return [
 			$_SERVER
 		]
 	],
+	ICanManageInstances::class => [
+		'instanceOf' => InstanceManager::class,
+		'constructParams' => [
+			[Dice::INSTANCE => Dice::SELF],
+		],
+	],
 	Config\Util\ConfigFileManager::class => [
 		'instanceOf' => Config\Factory\Config::class,
 		'call'       => [
diff --git a/tests/Util/CreateDatabaseTrait.php b/tests/Util/CreateDatabaseTrait.php
index 51f74c7a52..127d8da2f1 100644
--- a/tests/Util/CreateDatabaseTrait.php
+++ b/tests/Util/CreateDatabaseTrait.php
@@ -30,6 +30,7 @@ use Friendica\Database\Definition\ViewDefinition;
 use Friendica\Test\DatabaseTestTrait;
 use Friendica\Test\Util\Database\StaticDatabase;
 use Friendica\Util\Profiler;
+use Psr\Log\NullLogger;
 
 trait CreateDatabaseTrait
 {
@@ -45,7 +46,7 @@ trait CreateDatabaseTrait
 			],
 		]));
 
-		$database = new StaticDatabase($config, new Profiler($config), (new DbaDefinition($this->root->url()))->load(), (new ViewDefinition($this->root->url()))->load());
+		$database = new StaticDatabase($config, new Profiler($config), (new DbaDefinition($this->root->url()))->load(), (new ViewDefinition($this->root->url()))->load(), new NullLogger());
 		$database->setTestmode(true);
 
 		return $database;
diff --git a/tests/Util/Hooks/InstanceMocks/FakeInstance.php b/tests/Util/Hooks/InstanceMocks/FakeInstance.php
new file mode 100644
index 0000000000..70fdbcd4fe
--- /dev/null
+++ b/tests/Util/Hooks/InstanceMocks/FakeInstance.php
@@ -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\Test\Util\Hooks\InstanceMocks;
+
+use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
+
+class FakeInstance implements IAmADecoratedInterface, IAmAStrategy
+{
+	protected $aText = null;
+	protected $cBool = null;
+	protected $bText = null;
+
+	public function __construct(string $aText = null, bool $cBool = null, string $bText = null)
+	{
+		$this->aText         = $aText;
+		$this->cBool         = $cBool;
+		$this->bText         = $bText;
+	}
+
+	public function createSomething(string $aText, bool $cBool, string $bText): string
+	{
+		$this->aText         = $aText;
+		$this->cBool         = $cBool;
+		$this->bText         = $bText;
+	}
+
+	public function getAText(): ?string
+	{
+		return $this->aText;
+	}
+
+	public function getBText(): ?string
+	{
+		return $this->bText;
+	}
+
+	public function getCBool(): ?bool
+	{
+		return $this->cBool;
+	}
+}
diff --git a/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php b/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php
new file mode 100644
index 0000000000..af4db96c44
--- /dev/null
+++ b/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php
@@ -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\Test\Util\Hooks\InstanceMocks;
+
+class FakeInstanceDecorator implements IAmADecoratedInterface
+{
+	public static $countInstance = 0;
+
+	/** @var IAmADecoratedInterface */
+	protected $orig;
+	protected $prefix = '';
+
+	public function __construct(IAmADecoratedInterface $orig, string $prefix = '')
+	{
+		$this->orig   = $orig;
+		$this->prefix = $prefix;
+
+		self::$countInstance++;
+	}
+
+	public function createSomething(string $aText, bool $cBool, string $bText): string
+	{
+		return $this->orig->createSomething($aText, $cBool, $bText);
+	}
+
+	public function getAText(): ?string
+	{
+		return $this->prefix . $this->orig->getAText();
+	}
+
+	public function getBText(): ?string
+	{
+		return $this->prefix . $this->orig->getBText();
+	}
+
+	public function getCBool(): ?bool
+	{
+		return $this->prefix . $this->orig->getCBool();
+	}
+}
diff --git a/tests/Util/Hooks/InstanceMocks/IAmADecoratedInterface.php b/tests/Util/Hooks/InstanceMocks/IAmADecoratedInterface.php
new file mode 100644
index 0000000000..fe93aa998d
--- /dev/null
+++ b/tests/Util/Hooks/InstanceMocks/IAmADecoratedInterface.php
@@ -0,0 +1,33 @@
+<?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\Test\Util\Hooks\InstanceMocks;
+
+interface IAmADecoratedInterface
+{
+	public function createSomething(string $aText, bool $cBool, string $bText): string;
+
+	public function getAText(): ?string;
+
+	public function getBText(): ?string;
+
+	public function getCBool(): ?bool;
+}
diff --git a/tests/functional/DependencyCheckTest.php b/tests/functional/DependencyCheckTest.php
index 86724bf437..27e693295e 100644
--- a/tests/functional/DependencyCheckTest.php
+++ b/tests/functional/DependencyCheckTest.php
@@ -25,7 +25,6 @@ use Dice\Dice;
 use Friendica\App;
 use Friendica\Core\Cache\Capability\ICanCache;
 use Friendica\Core\Cache\Capability\ICanCacheInMemory;
-use Friendica\Core\Config\Model\Config;
 use Friendica\Core\Config\ValueObject\Cache;
 use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Core\Lock\Capability\ICanLock;
@@ -33,7 +32,6 @@ use Friendica\Database\Database;
 use Friendica\Test\Util\VFSTrait;
 use Friendica\Util\BasePath;
 use Friendica\Core\Config\Util\ConfigFileManager;
-use Friendica\Util\Profiler;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
 
@@ -53,7 +51,18 @@ class DependencyCheckTest extends TestCase
 		$this->setUpVfsDir();
 
 		$this->dice = (new Dice())
-			->addRules(include __DIR__ . '/../../static/dependencies.config.php');
+			->addRules(include __DIR__ . '/../../static/dependencies.config.php')
+			->addRule(BasePath::class, [
+				'constructParams' => [
+					$this->root->url(),
+					[],
+				],
+			])
+			->addRule(LoggerInterface::class, ['constructParams' => ['test']]);
+
+		/** @var IManageConfigValues $config */
+		$config = $this->dice->create(IManageConfigValues::class);
+		$config->set('system', 'logfile', $this->root->url() . '/logs/friendica.log');
 	}
 
 	/**
@@ -142,7 +151,7 @@ class DependencyCheckTest extends TestCase
 	public function testLogger()
 	{
 		/** @var LoggerInterface $logger */
-		$logger = $this->dice->create(LoggerInterface::class, ['test']);
+		$logger = $this->dice->create(LoggerInterface::class, [['$channel' => 'test']]);
 
 		self::assertInstanceOf(LoggerInterface::class, $logger);
 	}
@@ -154,7 +163,7 @@ class DependencyCheckTest extends TestCase
 		$config->set('system', 'dlogfile', $this->root->url() . '/friendica.log');
 
 		/** @var LoggerInterface $logger */
-		$logger = $this->dice->create('$devLogger', ['dev']);
+		$logger = $this->dice->create('$devLogger', [['$channel' => 'dev']]);
 
 		self::assertInstanceOf(LoggerInterface::class, $logger);
 	}
@@ -164,6 +173,7 @@ class DependencyCheckTest extends TestCase
 		/** @var ICanCache $cache */
 		$cache = $this->dice->create(ICanCache::class);
 
+
 		self::assertInstanceOf(ICanCache::class, $cache);
 	}
 
diff --git a/tests/src/Core/Cache/DatabaseCacheTest.php b/tests/src/Core/Cache/DatabaseCacheTest.php
index e5f3359580..81804a806e 100644
--- a/tests/src/Core/Cache/DatabaseCacheTest.php
+++ b/tests/src/Core/Cache/DatabaseCacheTest.php
@@ -62,7 +62,7 @@ class DatabaseCacheTest extends CacheTest
 		$dbaDefinition  = (new DbaDefinition($configCache->get('system', 'basepath')))->load();
 		$viewDefinition = (new ViewDefinition($configCache->get('system', 'basepath')))->load();
 
-		$dba = new StaticDatabase($config, $profiler, $dbaDefinition, $viewDefinition);
+		$dba = new StaticDatabase($config, $profiler, $dbaDefinition, $viewDefinition, new NullLogger());
 
 		$this->cache = new Cache\Type\DatabaseCache('database', $dba);
 		return $this->cache;
diff --git a/tests/src/Core/Hooks/Model/InstanceManagerTest.php b/tests/src/Core/Hooks/Model/InstanceManagerTest.php
new file mode 100644
index 0000000000..94340b012f
--- /dev/null
+++ b/tests/src/Core/Hooks/Model/InstanceManagerTest.php
@@ -0,0 +1,258 @@
+<?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\Test\src\Core\Hooks\Model;
+
+use Dice\Dice;
+use Friendica\Core\Hooks\Model\InstanceManager;
+use Friendica\Test\MockedTest;
+use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance;
+use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator;
+use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface;
+
+class InstanceManagerTest extends MockedTest
+{
+	public function testEqualButNotSameInstance()
+	{
+		$instance = new InstanceManager(new Dice());
+
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class);
+
+		$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
+		$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
+
+		self::assertEquals($getInstanceA, $getInstanceB);
+		self::assertNotSame($getInstanceA, $getInstanceB);
+	}
+
+	protected function tearDown(): void
+	{
+		FakeInstanceDecorator::$countInstance = 0;
+
+		parent::tearDown();
+	}
+
+	public function dataTests(): array
+	{
+		return [
+			'only_a'           => [
+				'aString' => 'test',
+			],
+			'a_b'              => [
+				'aString' => 'test',
+				'cBool' => false,
+				'bString' => 'test23',
+
+			],
+			'a_c'              => [
+				'aString' => 'test',
+				'cBool'   => false,
+				'bString' => null,
+			],
+			'a_b_c'            => [
+				'aString' => 'test',
+				'cBool'   => false,
+				'bString' => 'test23',
+			],
+			'null'             => [],
+		];
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testInstanceWithConstructorAnonymArgs(string $aString = null, bool $cBool = null, string $bString = null)
+	{
+		$instance = new InstanceManager(new Dice());
+
+		$args = [];
+
+		if (isset($aString)) {
+			$args[] = $aString;
+		}
+		if (isset($bString)) {
+			$args[] = $bString;
+		}
+		if (isset($cBool)) {
+			$args[] = $cBool;
+		}
+
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
+
+		/** @var IAmADecoratedInterface $getInstanceA */
+		$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
+		/** @var IAmADecoratedInterface $getInstanceB */
+		$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
+
+		self::assertEquals($getInstanceA, $getInstanceB);
+		self::assertNotSame($getInstanceA, $getInstanceB);
+		self::assertEquals($aString, $getInstanceA->getAText());
+		self::assertEquals($aString, $getInstanceB->getAText());
+		self::assertEquals($bString, $getInstanceA->getBText());
+		self::assertEquals($bString, $getInstanceB->getBText());
+		self::assertEquals($cBool, $getInstanceA->getCBool());
+		self::assertEquals($cBool, $getInstanceB->getCBool());
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testInstanceConstructorAndGetInstanceWithNamedArgs(string $aString = null, bool $cBool = null, string $bString = null)
+	{
+		$instance = new InstanceManager(new Dice());
+
+		$args = [];
+
+		if (isset($aString)) {
+			$args[] = $aString;
+		}
+		if (isset($cBool)) {
+			$args[] = $cBool;
+		}
+
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
+
+		/** @var IAmADecoratedInterface $getInstanceA */
+		$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
+		/** @var IAmADecoratedInterface $getInstanceB */
+		$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
+
+		self::assertEquals($getInstanceA, $getInstanceB);
+		self::assertNotSame($getInstanceA, $getInstanceB);
+		self::assertEquals($aString, $getInstanceA->getAText());
+		self::assertEquals($aString, $getInstanceB->getAText());
+		self::assertEquals($bString, $getInstanceA->getBText());
+		self::assertEquals($bString, $getInstanceB->getBText());
+		self::assertEquals($cBool, $getInstanceA->getCBool());
+		self::assertEquals($cBool, $getInstanceB->getCBool());
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testInstanceWithTwoStrategies(string $aString = null, bool $cBool = null, string $bString = null)
+	{
+		$instance = new InstanceManager(new Dice());
+
+		$args = [];
+
+		if (isset($aString)) {
+			$args[] = $aString;
+		}
+		if (isset($cBool)) {
+			$args[] = $cBool;
+		}
+
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
+
+		/** @var IAmADecoratedInterface $getInstanceA */
+		$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
+		/** @var IAmADecoratedInterface $getInstanceB */
+		$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
+
+		self::assertEquals($getInstanceA, $getInstanceB);
+		self::assertNotSame($getInstanceA, $getInstanceB);
+		self::assertEquals($aString, $getInstanceA->getAText());
+		self::assertEquals($aString, $getInstanceB->getAText());
+		self::assertEquals($bString, $getInstanceA->getBText());
+		self::assertEquals($bString, $getInstanceB->getBText());
+		self::assertEquals($cBool, $getInstanceA->getCBool());
+		self::assertEquals($cBool, $getInstanceB->getCBool());
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testDecorator(string $aString = null, bool $cBool = null, string $bString = null)
+	{
+		$instance = new InstanceManager(new Dice());
+
+		$args = [];
+
+		if (isset($aString)) {
+			$args[] = $aString;
+		}
+		if (isset($cBool)) {
+			$args[] = $cBool;
+		}
+
+		$prefix = 'prefix1';
+
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
+		$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]);
+
+		/** @var IAmADecoratedInterface $getInstanceA */
+		$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
+		/** @var IAmADecoratedInterface $getInstanceB */
+		$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
+
+		self::assertEquals(2, FakeInstanceDecorator::$countInstance);
+		self::assertEquals($getInstanceA, $getInstanceB);
+		self::assertNotSame($getInstanceA, $getInstanceB);
+		self::assertEquals($prefix . $aString, $getInstanceA->getAText());
+		self::assertEquals($prefix . $aString, $getInstanceB->getAText());
+		self::assertEquals($prefix . $bString, $getInstanceA->getBText());
+		self::assertEquals($prefix . $bString, $getInstanceB->getBText());
+		self::assertEquals($prefix . $cBool, $getInstanceA->getCBool());
+		self::assertEquals($prefix . $cBool, $getInstanceB->getCBool());
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testTwoDecoratorWithPrefix(string $aString = null, bool $cBool = null, string $bString = null)
+	{
+		$instance = new InstanceManager(new Dice());
+
+		$args = [];
+
+		if (isset($aString)) {
+			$args[] = $aString;
+		}
+		if (isset($cBool)) {
+			$args[] = $cBool;
+		}
+
+		$prefix = 'prefix1';
+
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
+		$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
+		$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]);
+		$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class);
+
+		/** @var IAmADecoratedInterface $getInstanceA */
+		$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
+		/** @var IAmADecoratedInterface $getInstanceB */
+		$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
+
+		self::assertEquals(4, FakeInstanceDecorator::$countInstance);
+		self::assertEquals($getInstanceA, $getInstanceB);
+		self::assertNotSame($getInstanceA, $getInstanceB);
+		self::assertEquals($prefix . $aString, $getInstanceA->getAText());
+		self::assertEquals($prefix . $aString, $getInstanceB->getAText());
+		self::assertEquals($prefix . $bString, $getInstanceA->getBText());
+		self::assertEquals($prefix . $bString, $getInstanceB->getBText());
+		self::assertEquals($prefix . $cBool, $getInstanceA->getCBool());
+		self::assertEquals($prefix . $cBool, $getInstanceB->getCBool());
+	}
+}
diff --git a/tests/src/Core/Logger/AbstractLoggerTest.php b/tests/src/Core/Logger/AbstractLoggerTest.php
index 3a5e0ecd11..4605f6f75a 100644
--- a/tests/src/Core/Logger/AbstractLoggerTest.php
+++ b/tests/src/Core/Logger/AbstractLoggerTest.php
@@ -21,6 +21,7 @@
 
 namespace Friendica\Test\src\Core\Logger;
 
+use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Test\MockedTest;
 use Friendica\Core\Logger\Util\Introspection;
 use Mockery\MockInterface;
@@ -41,6 +42,10 @@ abstract class AbstractLoggerTest extends MockedTest
 	 * @var Introspection|MockInterface
 	 */
 	protected $introspection;
+	/**
+	 * @var IManageConfigValues|MockInterface
+	 */
+	protected $config;
 
 	/**
 	 * Returns the content of the current logger instance
@@ -68,6 +73,8 @@ abstract class AbstractLoggerTest extends MockedTest
 			'line'     => self::LINE,
 			'function' => self::FUNC
 		]);
+
+		$this->config = \Mockery::mock(IManageConfigValues::class);
 	}
 
 	public function assertLogline($string)
diff --git a/tests/src/Core/Logger/StreamLoggerTest.php b/tests/src/Core/Logger/StreamLoggerTest.php
index a83a5c2e4e..1ddddf4c1b 100644
--- a/tests/src/Core/Logger/StreamLoggerTest.php
+++ b/tests/src/Core/Logger/StreamLoggerTest.php
@@ -62,7 +62,9 @@ class StreamLoggerTest extends AbstractLoggerTest
 		$this->logfile = vfsStream::newFile('friendica.log')
 			->at($this->root);
 
-		$logger = new StreamLogger('test', $this->logfile->url(), $this->introspection, $this->fileSystem, $level);
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($this->logfile->url())->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem, $level);
 
 		return $logger;
 	}
@@ -75,43 +77,6 @@ class StreamLoggerTest extends AbstractLoggerTest
 		return $this->logfile->getContent();
 	}
 
-	/**
-	 * Test if a stream is working
-	 */
-	public function testStream()
-	{
-		$logfile = vfsStream::newFile('friendica.log')
-			->at($this->root);
-
-		$filehandler = fopen($logfile->url(), 'ab');
-
-		$logger = new \Friendica\Core\Logger\Type\StreamLogger('test', $filehandler, $this->introspection, $this->fileSystem);
-		$logger->emergency('working');
-
-		$text = $logfile->getContent();
-
-		self::assertLogline($text);
-	}
-
-	/**
-	 * Test if the close statement is working
-	 */
-	public function testClose()
-	{
-		$logfile = vfsStream::newFile('friendica.log')
-			->at($this->root);
-
-		$logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
-		$logger->emergency('working');
-		$logger->close();
-		// close doesn't affect
-		$logger->emergency('working too');
-
-		$text = $logfile->getContent();
-
-		self::assertLoglineNums(2, $text);
-	}
-
 	/**
 	 * Test when a file isn't set
 	 */
@@ -120,7 +85,9 @@ class StreamLoggerTest extends AbstractLoggerTest
 		$this->expectException(LoggerArgumentException::class);
 		$this->expectExceptionMessage("Missing stream URL.");
 
-		$logger = new StreamLogger('test', '', $this->introspection, $this->fileSystem);
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn('')->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem);
 
 		$logger->emergency('not working');
 	}
@@ -130,13 +97,14 @@ class StreamLoggerTest extends AbstractLoggerTest
 	 */
 	public function testWrongUrl()
 	{
-		$this->expectException(LoggerException::class);
-		$this->expectExceptionMessage("Cannot create stream.");
+		$this->expectException(LoggerArgumentException::class);
 
 		$logfile = vfsStream::newFile('friendica.log')
 			->at($this->root)->chmod(0);
 
-		$logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($logfile->url())->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem);
 
 		$logger->emergency('not working');
 	}
@@ -164,7 +132,9 @@ class StreamLoggerTest extends AbstractLoggerTest
 		$this->expectException(LogLevelException::class);
 		$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
 
-		$logger = new StreamLogger('test', 'file.text', $this->introspection, $this->fileSystem, 'NOPE');
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn('file.text')->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem, 'NOPE');
 	}
 
 	/**
@@ -178,7 +148,9 @@ class StreamLoggerTest extends AbstractLoggerTest
 		$logfile = vfsStream::newFile('friendica.log')
 			->at($this->root);
 
-		$logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($logfile->url())->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem);
 
 		$logger->log('NOPE', 'a test');
 	}
@@ -191,7 +163,9 @@ class StreamLoggerTest extends AbstractLoggerTest
 		$this->expectException(LoggerArgumentException::class);
 		$this->expectExceptionMessage("A stream must either be a resource or a string.");
 
-		$logger = new StreamLogger('test', null, $this->introspection, $this->fileSystem);
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn(null)->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem);
 	}
 
 	/**
@@ -207,7 +181,9 @@ class StreamLoggerTest extends AbstractLoggerTest
 
 		chdir($this->root->getChild('logs')->url());
 
-		$logger = new StreamLogger('test', '../friendica.log' , $this->introspection, $this->fileSystem);
+		$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn('../friendica.log')->once();
+
+		$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem);
 
 		$logger->info('Test');
 	}
diff --git a/tests/src/Core/Logger/SyslogLoggerTest.php b/tests/src/Core/Logger/SyslogLoggerTest.php
index 89f9d82e6c..53ace79c08 100644
--- a/tests/src/Core/Logger/SyslogLoggerTest.php
+++ b/tests/src/Core/Logger/SyslogLoggerTest.php
@@ -39,6 +39,10 @@ class SyslogLoggerTest extends AbstractLoggerTest
 		parent::setUp();
 
 		$this->introspection->shouldReceive('addClasses')->with([SyslogLogger::class]);
+		$this->config->shouldReceive('get')->with('system', 'syslog_flags')->andReturn(SyslogLogger::DEFAULT_FLAGS)
+					 ->once();
+		$this->config->shouldReceive('get')->with('system', 'syslog_facility')
+					 ->andReturn(SyslogLogger::DEFAULT_FACILITY)->once();
 	}
 
 	/**
@@ -54,7 +58,7 @@ class SyslogLoggerTest extends AbstractLoggerTest
 	 */
 	protected function getInstance($level = LogLevel::DEBUG)
 	{
-		$this->logger = new SyslogLoggerWrapper('test', $this->introspection, $level);
+		$this->logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection, $level);
 
 		return $this->logger;
 	}
@@ -68,7 +72,7 @@ class SyslogLoggerTest extends AbstractLoggerTest
 		$this->expectException(LogLevelException::class);
 		$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
 		
-		$logger = new SyslogLoggerWrapper('test', $this->introspection, 'NOPE');
+		$logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection, 'NOPE');
 	}
 
 	/**
@@ -79,7 +83,7 @@ class SyslogLoggerTest extends AbstractLoggerTest
 		$this->expectException(LogLevelException::class);
 		$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
 
-		$logger = new SyslogLoggerWrapper('test', $this->introspection);
+		$logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection);
 
 		$logger->log('NOPE', 'a test');
 	}
@@ -90,7 +94,7 @@ class SyslogLoggerTest extends AbstractLoggerTest
 	 */
 	public function testClose()
 	{
-		$logger = new SyslogLoggerWrapper('test', $this->introspection);
+		$logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection);
 		$logger->emergency('test');
 		$logger->close();
 		// Reopened itself
diff --git a/tests/src/Core/Logger/SyslogLoggerWrapper.php b/tests/src/Core/Logger/SyslogLoggerWrapper.php
index 634edc1195..9fd16706a8 100644
--- a/tests/src/Core/Logger/SyslogLoggerWrapper.php
+++ b/tests/src/Core/Logger/SyslogLoggerWrapper.php
@@ -21,6 +21,7 @@
 
 namespace Friendica\Test\src\Core\Logger;
 
+use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Core\Logger\Type\SyslogLogger;
 use Friendica\Core\Logger\Util\Introspection;
 use Psr\Log\LogLevel;
@@ -32,9 +33,9 @@ class SyslogLoggerWrapper extends SyslogLogger
 {
 	private $content;
 
-	public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
+	public function __construct($channel, IManageConfigValues $config, Introspection $introspection, $level = LogLevel::NOTICE)
 	{
-		parent::__construct($channel, $introspection, $level, $logOpts, $logFacility);
+		parent::__construct($channel, $config, $introspection, $level);
 
 		$this->content = '';
 	}
-- 
2.39.5