namespace Friendica\Core;
use Friendica\DI;
-use Friendica\Util\Logger\WorkerLogger;
+use Friendica\Core\Logger\Type\WorkerLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
--- /dev/null
+<?php
+
+namespace Friendica\Core\Logger\Exception;
+
+use Throwable;
+
+class LoggerArgumentException extends \InvalidArgumentException
+{
+ public function __construct($message = "", Throwable $previous = null)
+ {
+ parent::__construct($message, 500, $previous);
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Core\Logger\Exception;
+
+use Throwable;
+
+class LoggerException extends \Exception
+{
+ public function __construct($message = "", Throwable $previous = null)
+ {
+ parent::__construct($message, 500, $previous);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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;
+use Friendica\Database\Database;
+use Friendica\Util\FileSystem;
+use Friendica\Util\Introspection;
+use Friendica\Core\Logger\Type\Monolog\DevelopHandler;
+use Friendica\Core\Logger\Type\Monolog\IntrospectionProcessor;
+use Friendica\Core\Logger\Type\ProfilerLogger;
+use Friendica\Core\Logger\Type\StreamLogger;
+use Friendica\Core\Logger\Type\SyslogLogger;
+use Friendica\Core\Logger\Type\VoidLogger;
+use Friendica\Util\Profiler;
+use Monolog;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * A logger factory
+ */
+class Logger
+{
+ const DEV_CHANNEL = 'dev';
+
+ /**
+ * A list of classes, which shouldn't get logged
+ *
+ * @var string[]
+ */
+ private static $ignoreClassList = [
+ Core\Logger::class,
+ Profiler::class,
+ 'Friendica\\Core\\Logger\\Type',
+ 'Friendica\\Core\\Logger\\Type\\Monolog',
+ ];
+
+ /** @var string The log-channel (app, worker, ...) */
+ private $channel;
+
+ public function __construct(string $channel)
+ {
+ $this->channel = $channel;
+ }
+
+ /**
+ * 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
+ *
+ * @return LoggerInterface The PSR-3 compliant logger instance
+ */
+ public function create(Database $database, IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem): LoggerInterface
+ {
+ if (empty($config->get('system', 'debugging', false))) {
+ $logger = new VoidLogger();
+ $database->setLogger($logger);
+ return $logger;
+ }
+
+ $introspection = new Introspection(self::$ignoreClassList);
+ $level = $config->get('system', 'loglevel');
+ $loglevel = self::mapLegacyConfigDebugLevel((string)$level);
+
+ switch ($config->get('system', 'logger_config', 'stream')) {
+ case 'monolog':
+ $loggerTimeZone = new \DateTimeZone('UTC');
+ Monolog\Logger::setTimezone($loggerTimeZone);
+
+ $logger = new Monolog\Logger($this->channel);
+ $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
+ $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
+ $logger->pushProcessor(new Monolog\Processor\UidProcessor());
+ $logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
+
+ $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 {
+ static::addStreamHandler($logger, $stream, $loglevel);
+ } catch (\Throwable $e) {
+ // No Logger ..
+ /// @todo isn't it possible to give the admin any hint about this wrong configuration?
+ $logger = new VoidLogger();
+ }
+ }
+ break;
+
+ case 'syslog':
+ try {
+ $logger = new SyslogLogger($this->channel, $introspection, $loglevel);
+ } catch (\Throwable $e) {
+ // No logger ...
+ /// @todo isn't it possible to give the admin any hint about this wrong configuration?
+ $logger = new VoidLogger();
+ }
+ break;
+
+ case 'stream':
+ default:
+ $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 (\Throwable $t) {
+ // No logger ...
+ /// @todo isn't it possible to give the admin any hint about this wrong configuration?
+ $logger = new VoidLogger();
+ }
+ } else {
+ /// @todo isn't it possible to give the admin any hint about this wrong configuration?
+ $logger = new VoidLogger();
+ }
+ 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);
+ }
+
+ $database->setLogger($logger);
+ return $logger;
+ }
+
+ /**
+ * 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
+ *
+ * @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)
+ {
+ $debugging = $config->get('system', 'debugging');
+ $stream = $config->get('system', 'dlogfile');
+ $developerIp = $config->get('system', 'dlogip');
+
+ if ((!isset($developerIp) || !$debugging) &&
+ (!is_file($stream) || is_writable($stream))) {
+ return new VoidLogger();
+ }
+
+ $loggerTimeZone = new \DateTimeZone('UTC');
+ Monolog\Logger::setTimezone($loggerTimeZone);
+
+ $introspection = new Introspection(self::$ignoreClassList);
+
+ switch ($config->get('system', 'logger_config', 'stream')) {
+
+ case 'monolog':
+ $loggerTimeZone = new \DateTimeZone('UTC');
+ Monolog\Logger::setTimezone($loggerTimeZone);
+
+ $logger = new Monolog\Logger(self::DEV_CHANNEL);
+ $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
+ $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
+ $logger->pushProcessor(new Monolog\Processor\UidProcessor());
+ $logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
+
+ $logger->pushHandler(new DevelopHandler($developerIp));
+
+ static::addStreamHandler($logger, $stream, LogLevel::DEBUG);
+ break;
+
+ case 'syslog':
+ $logger = new SyslogLogger(self::DEV_CHANNEL, $introspection, LogLevel::DEBUG);
+ break;
+
+ case 'stream':
+ default:
+ $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);
+ }
+
+ return $logger;
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * Adding a handler to a given logger instance
+ *
+ * @param LoggerInterface $logger The logger instance
+ * @param mixed $stream The stream which handles the logger output
+ * @param string $level The level, for which this handler at least should handle logging
+ *
+ * @return void
+ *
+ * @throws LoggerException
+ */
+ public static function addStreamHandler(LoggerInterface $logger, $stream, string $level = LogLevel::NOTICE)
+ {
+ if ($logger instanceof Monolog\Logger) {
+ $loglevel = Monolog\Logger::toMonologLevel($level);
+
+ // fallback to notice if an invalid loglevel is set
+ if (!is_int($loglevel)) {
+ $loglevel = LogLevel::NOTICE;
+ }
+
+ try {
+ $fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
+
+ $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
+ $fileHandler->setFormatter($formatter);
+
+ $logger->pushHandler($fileHandler);
+ } catch (\Exception $exception) {
+ throw new LoggerException('Cannot create Monolog Logger.', $exception);
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type;
+
+use Friendica\Core\Logger\Exception\LoggerException;
+use Friendica\Util\Introspection;
+use Friendica\Util\Strings;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * This class contains all necessary dependencies and calls for Friendica
+ * Every new Logger should extend this class and define, how addEntry() works
+ *
+ * Additional information for each Logger, who extends this class:
+ * - Introspection
+ * - UID for each call
+ * - Channel of the current call (i.e. index, worker, daemon, ...)
+ */
+abstract class AbstractLogger implements LoggerInterface
+{
+ /**
+ * The output channel of this logger
+ * @var string
+ */
+ protected $channel;
+
+ /**
+ * The Introspection for the current call
+ * @var Introspection
+ */
+ protected $introspection;
+
+ /**
+ * The UID of the current call
+ * @var string
+ */
+ protected $logUid;
+
+ /**
+ * Adds a new entry to the log
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ abstract protected function addEntry($level, string $message, array $context = []);
+
+ /**
+ * @param string $channel The output channel
+ * @param Introspection $introspection The introspection of the current call
+ *
+ * @throws LoggerException
+ */
+ public function __construct(string $channel, Introspection $introspection)
+ {
+ $this->channel = $channel;
+ $this->introspection = $introspection;
+
+ try {
+ $this->logUid = Strings::getRandomHex(6);
+ } catch (\Exception $exception) {
+ throw new LoggerException('Cannot generate log Id', $exception);
+ }
+ }
+
+ /**
+ * Simple interpolation of PSR-3 compliant replacements ( variables between '{' and '}' )
+ *
+ * @see https://www.php-fig.org/psr/psr-3/#12-message
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return string the interpolated message
+ */
+ protected function psrInterpolate(string $message, array $context = []): string
+ {
+ $replace = [];
+ foreach ($context as $key => $value) {
+ // check that the value can be casted to string
+ if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
+ $replace['{' . $key . '}'] = $value;
+ } elseif (is_array($value)) {
+ $replace['{' . $key . '}'] = @json_encode($value);
+ }
+ }
+
+ return strtr($message, $replace);
+ }
+
+ /**
+ * JSON Encodes a complete array including objects with "__toString()" methods
+ *
+ * @param array $input an Input Array to encode
+ *
+ * @return false|string The json encoded output of the array
+ */
+ protected function jsonEncodeArray(array $input)
+ {
+ $output = [];
+
+ foreach ($input as $key => $value) {
+ if (is_object($value) && method_exists($value, '__toString')) {
+ $output[$key] = $value->__toString();
+ } else {
+ $output[$key] = $value;
+ }
+ }
+
+ return @json_encode($output);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function emergency($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::EMERGENCY, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function alert($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::ALERT, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function critical($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::CRITICAL, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function error($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::ERROR, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function warning($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::WARNING, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function notice($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::NOTICE, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function info($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::INFO, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function debug($message, array $context = [])
+ {
+ $this->addEntry(LogLevel::DEBUG, (string) $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $this->addEntry($level, (string) $message, $context);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type\Monolog;
+
+use Monolog\Handler;
+use Monolog\Logger;
+
+/**
+ * Simple handler for Friendica developers to use for deeper logging
+ *
+ * If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
+ * you'll use Logger::develop() for the duration of your work, and you clean it up when you're done before submitting your PR.
+ */
+class DevelopHandler extends Handler\AbstractHandler
+{
+ /**
+ * @var string The IP of the developer who wants to debug
+ */
+ private $developerIp;
+
+ /**
+ * @param string $developerIp The IP of the developer who wants to debug
+ * @param int $level The minimum logging level at which this handler will be triggered
+ * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
+ */
+ public function __construct($developerIp, $level = Logger::DEBUG, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+
+ $this->developerIp = $developerIp;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(array $record): bool
+ {
+ if (!$this->isHandling($record)) {
+ return false;
+ }
+
+ /// Just in case the remote IP is the same as the developer IP log the output
+ if (!is_null($this->developerIp) && $_SERVER['REMOTE_ADDR'] != $this->developerIp) {
+ return false;
+ }
+
+ return false === $this->bubble;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type\Monolog;
+
+use Friendica\Util\Introspection;
+use Monolog\Logger;
+use Monolog\Processor\ProcessorInterface;
+
+/**
+ * Injects line/file//function where the log message came from
+ */
+class IntrospectionProcessor implements ProcessorInterface
+{
+ private $level;
+
+ private $introspection;
+
+ /**
+ * @param Introspection $introspection Holds the Introspection of the current call
+ * @param string|int $level The minimum logging level at which this Processor will be triggered
+ */
+ public function __construct(Introspection $introspection, $level = Logger::DEBUG)
+ {
+ $this->level = Logger::toMonologLevel($level);
+ $introspection->addClasses(['Monolog\\']);
+ $this->introspection = $introspection;
+ }
+
+ public function __invoke(array $record): array
+ {
+ // return if the level is not high enough
+ if ($record['level'] < $this->level) {
+ return $record;
+ }
+ // we should have the call source now
+ $record['extra'] = array_merge(
+ $record['extra'],
+ $this->introspection->getRecord()
+ );
+
+ return $record;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type;
+
+use Friendica\Util\Profiler;
+use Psr\Log\LoggerInterface;
+
+/**
+ * This Logger adds additional profiling data in case profiling is enabled.
+ * It uses a predefined logger.
+ */
+class ProfilerLogger implements LoggerInterface
+{
+ /**
+ * The Logger of the current call
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * The Profiler for the current call
+ * @var Profiler
+ */
+ protected $profiler;
+
+ /**
+ * ProfilerLogger constructor.
+ * @param LoggerInterface $logger The Logger of the current call
+ * @param Profiler $profiler The profiler of the current call
+ */
+ public function __construct(LoggerInterface $logger, Profiler $profiler)
+ {
+ $this->logger = $logger;
+ $this->profiler = $profiler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function emergency($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->emergency($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function alert($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->alert($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function critical($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->critical($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function error($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->error($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function warning($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->warning($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function notice($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->notice($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function info($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->info($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function debug($message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->debug($message, $context);
+ $this->profiler->stopRecording();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $this->profiler->startRecording('file');
+ $this->logger->log($level, $message, $context);
+ $this->profiler->stopRecording();
+ }
+}
--- /dev/null
+## Friendica\Util\Logger
+
+This namespace contains the different implementations of a Logger.
+
+### Configuration guideline
+
+The following settings are possible for `logger_config`:
+- `monolog`: A Logging framework with lots of additions (see [Monolog](https://github.com/Seldaek/monolog/)). There are just Friendica additions inside the Monolog directory
+- [`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.
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type;
+
+use Friendica\Core\Logger\Exception\LoggerArgumentException;
+use Friendica\Core\Logger\Exception\LoggerException;
+use Friendica\Util\DateTimeFormat;
+use Friendica\Util\FileSystem;
+use Friendica\Util\Introspection;
+use Psr\Log\LogLevel;
+
+/**
+ * A Logger instance for logging into a stream (file, stdout, stderr)
+ */
+class StreamLogger extends AbstractLogger
+{
+ /**
+ * The minimum loglevel at which this logger will be triggered
+ * @var string
+ */
+ 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
+ */
+ private $stream;
+
+ /**
+ * The current process ID
+ * @var int
+ */
+ private $pid;
+
+ /**
+ * @var FileSystem
+ */
+ private $fileSystem;
+
+ /**
+ * Translates LogLevel log levels to integer values
+ * @var array
+ */
+ private $levelToInt = [
+ LogLevel::EMERGENCY => 0,
+ LogLevel::ALERT => 1,
+ LogLevel::CRITICAL => 2,
+ LogLevel::ERROR => 3,
+ LogLevel::WARNING => 4,
+ LogLevel::NOTICE => 5,
+ LogLevel::INFO => 6,
+ LogLevel::DEBUG => 7,
+ ];
+
+ /**
+ * {@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
+ */
+ public function __construct($channel, $stream, Introspection $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG)
+ {
+ $this->fileSystem = $fileSystem;
+
+ 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 LoggerArgumentException(sprintf('The level "%s" is not valid.', $level));
+ }
+
+ $this->checkStream();
+ }
+
+ public function close()
+ {
+ if ($this->url && is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+
+ $this->stream = null;
+ }
+
+ /**
+ * Adds a new entry to the log
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws LoggerException
+ * @throws LoggerArgumentException
+ */
+ protected function addEntry($level, string $message, array $context = [])
+ {
+ if (!array_key_exists($level, $this->levelToInt)) {
+ throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level));
+ }
+
+ $logLevel = $this->levelToInt[$level];
+
+ if ($logLevel > $this->logLevel) {
+ return;
+ }
+
+ $this->checkStream();
+
+ $formattedLog = $this->formatLog($level, $message, $context);
+ fwrite($this->stream, $formattedLog);
+ }
+
+ /**
+ * Formats a log record for the syslog output
+ *
+ * @param mixed $level The loglevel/priority
+ * @param string $message The message
+ * @param array $context The context of this call
+ *
+ * @return string the formatted syslog output
+ *
+ * @throws LoggerException
+ */
+ private function formatLog($level, string $message, array $context = []): string
+ {
+ $record = $this->introspection->getRecord();
+ $record = array_merge($record, ['uid' => $this->logUid, 'process_id' => $this->pid]);
+
+ try {
+ $logMessage = DateTimeFormat::utcNow(DateTimeFormat::ATOM) . ' ';
+ } catch (\Exception $exception) {
+ throw new LoggerException('Cannot get current datetime.', $exception);
+ }
+ $logMessage .= $this->channel . ' ';
+ $logMessage .= '[' . strtoupper($level) . ']: ';
+ $logMessage .= $this->psrInterpolate($message, $context) . ' ';
+ $logMessage .= $this->jsonEncodeArray($context) . ' - ';
+ $logMessage .= $this->jsonEncodeArray($record);
+ $logMessage .= PHP_EOL;
+
+ 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);
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type;
+
+use Friendica\Core\Logger\Exception\LoggerArgumentException;
+use Friendica\Core\Logger\Exception\LoggerException;
+use Friendica\Util\Introspection;
+use Psr\Log\InvalidArgumentException;
+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
+{
+ const IDENT = 'Friendica';
+
+ /**
+ * Translates LogLevel log levels to syslog log priorities.
+ * @var array
+ */
+ private $logLevels = [
+ LogLevel::DEBUG => LOG_DEBUG,
+ LogLevel::INFO => LOG_INFO,
+ LogLevel::NOTICE => LOG_NOTICE,
+ LogLevel::WARNING => LOG_WARNING,
+ LogLevel::ERROR => LOG_ERR,
+ LogLevel::CRITICAL => LOG_CRIT,
+ LogLevel::ALERT => LOG_ALERT,
+ LogLevel::EMERGENCY => LOG_EMERG,
+ ];
+
+ /**
+ * Translates log priorities to string outputs
+ * @var array
+ */
+ private $logToString = [
+ LOG_DEBUG => 'DEBUG',
+ LOG_INFO => 'INFO',
+ LOG_NOTICE => 'NOTICE',
+ LOG_WARNING => 'WARNING',
+ LOG_ERR => 'ERROR',
+ LOG_CRIT => 'CRITICAL',
+ LOG_ALERT => 'ALERT',
+ LOG_EMERG => 'EMERGENCY'
+ ];
+
+ /**
+ * Indicates what logging options will be used when generating a log message
+ * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
+ *
+ * @var int
+ */
+ private $logOpts;
+
+ /**
+ * Used to specify what type of program is logging the message
+ * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
+ *
+ * @var int
+ */
+ private $logFacility;
+
+ /**
+ * The minimum loglevel at which this logger will be triggered
+ * @var int
+ */
+ private $logLevel;
+
+ /**
+ * A error message of the current operation
+ * @var string
+ */
+ private $errorMessage;
+
+ /**
+ * {@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 LoggerArgumentException
+ */
+ public function __construct($channel, Introspection $introspection, string $level = LogLevel::NOTICE, int $logOpts = LOG_PID, int $logFacility = LOG_USER)
+ {
+ parent::__construct($channel, $introspection);
+ $this->logOpts = $logOpts;
+ $this->logFacility = $logFacility;
+ $this->logLevel = $this->mapLevelToPriority($level);
+ $this->introspection->addClasses([self::class]);
+ }
+
+ /**
+ * Adds a new entry to the syslog
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @throws LoggerArgumentException in case the level isn't valid
+ * @throws LoggerException In case the syslog cannot be opened for writing
+ */
+ protected function addEntry($level, string $message, array $context = [])
+ {
+ $logLevel = $this->mapLevelToPriority($level);
+
+ if ($logLevel > $this->logLevel) {
+ return;
+ }
+
+ $formattedLog = $this->formatLog($logLevel, $message, $context);
+ $this->write($logLevel, $formattedLog);
+ }
+
+ /**
+ * Maps the LogLevel (@see LogLevel) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters)
+ *
+ * @param string $level A LogLevel
+ *
+ * @return int The SysLog priority
+ *
+ * @throws LoggerArgumentException If the loglevel isn't valid
+ */
+ public function mapLevelToPriority(string $level): int
+ {
+ if (!array_key_exists($level, $this->logLevels)) {
+ throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level));
+ }
+
+ return $this->logLevels[$level];
+ }
+
+ /**
+ * Closes the Syslog
+ */
+ public function close()
+ {
+ closelog();
+ }
+
+ /**
+ * Writes a message to the syslog
+ *
+ * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
+ *
+ * @param int $priority The Priority
+ * @param string $message The message of the log
+ *
+ * @throws LoggerException In case the syslog cannot be opened/written
+ */
+ private function write(int $priority, string $message)
+ {
+ set_error_handler([$this, 'customErrorHandler']);
+ $opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
+ restore_error_handler();
+
+ if (!$opened) {
+ throw new LoggerException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
+ }
+
+ $this->syslogWrapper($priority, $message);
+ }
+
+ /**
+ * Formats a log record for the syslog output
+ *
+ * @param int $level The loglevel/priority
+ * @param string $message The message
+ * @param array $context The context of this call
+ *
+ * @return string the formatted syslog output
+ */
+ private function formatLog(int $level, string $message, array $context = []): string
+ {
+ $record = $this->introspection->getRecord();
+ $record = array_merge($record, ['uid' => $this->logUid]);
+
+ $logMessage = $this->channel . ' ';
+ $logMessage .= '[' . $this->logToString[$level] . ']: ';
+ $logMessage .= $this->psrInterpolate($message, $context) . ' ';
+ $logMessage .= $this->jsonEncodeArray($context) . ' - ';
+ $logMessage .= $this->jsonEncodeArray($record);
+
+ return $logMessage;
+ }
+
+ private function customErrorHandler($code, $msg)
+ {
+ $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
+ }
+
+ /**
+ * A syslog wrapper to make syslog functionality testable
+ *
+ * @param int $level The syslog priority
+ * @param string $entry The message to send to the syslog function
+ *
+ * @throws LoggerException
+ */
+ protected function syslogWrapper(int $level, string $entry)
+ {
+ set_error_handler([$this, 'customErrorHandler']);
+ $written = syslog($level, $entry);
+ restore_error_handler();
+
+ if (!$written) {
+ throw new LoggerException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type;
+
+use Psr\Log\LoggerInterface;
+
+/**
+ * A Logger instance to not log
+ */
+class VoidLogger implements LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ return;
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function log($level, $message, array $context = array())
+ {
+ return;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Type;
+
+use Friendica\Core\Logger\Exception\LoggerException;
+use Friendica\Util\Strings;
+use Psr\Log\LoggerInterface;
+
+/**
+ * A Logger for specific worker tasks, which adds a worker id to it.
+ * Uses the decorator pattern (https://en.wikipedia.org/wiki/Decorator_pattern)
+ */
+class WorkerLogger implements LoggerInterface
+{
+ /**
+ * @var LoggerInterface The original Logger instance
+ */
+ private $logger;
+
+ /**
+ * @var string the current worker ID
+ */
+ private $workerId;
+
+ /**
+ * @var string The called function name
+ */
+ private $functionName;
+
+ /**
+ * @param LoggerInterface $logger The logger for worker entries
+ * @param string $functionName The current function name of the worker
+ * @param int $idLength The length of the generated worker ID
+ *
+ * @throws LoggerException
+ */
+ public function __construct(LoggerInterface $logger, string $functionName = '', int $idLength = 7)
+ {
+ $this->logger = $logger;
+ $this->functionName = $functionName;
+ try {
+ $this->workerId = Strings::getRandomHex($idLength);
+ } catch (\Exception $exception) {
+ throw new LoggerException('Cannot generate random Hex.', $exception);
+ }
+ }
+
+ /**
+ * Sets the function name for additional logging
+ *
+ * @param string $functionName
+ */
+ public function setFunctionName(string $functionName)
+ {
+ $this->functionName = $functionName;
+ }
+
+ /**
+ * Adds the worker context for each log entry
+ *
+ * @param array $context
+ */
+ private function addContext(array &$context)
+ {
+ $context['worker_id'] = $this->workerId;
+ $context['worker_cmd'] = $this->functionName;
+ }
+
+ /**
+ * Returns the worker ID
+ *
+ * @return string
+ */
+ public function getWorkerId(): string
+ {
+ return $this->workerId;
+ }
+
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->emergency($message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->alert($message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->critical($message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->error($message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->warning($message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->notice($message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->info($message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->debug($message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $this->addContext($context);
+ $this->logger->log($level, $message, $context);
+ }
+}
*/
public static function workerLogger()
{
- return self::$dice->create(Util\Logger\WorkerLogger::class);
+ return self::$dice->create(Core\Logger\Type\WorkerLogger::class);
}
//
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Factory;
-
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Logger;
-use Friendica\Database\Database;
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\FileSystem;
-use Friendica\Util\Introspection;
-use Friendica\Util\Logger\Monolog\DevelopHandler;
-use Friendica\Util\Logger\Monolog\IntrospectionProcessor;
-use Friendica\Util\Logger\ProfilerLogger;
-use Friendica\Util\Logger\StreamLogger;
-use Friendica\Util\Logger\SyslogLogger;
-use Friendica\Util\Logger\VoidLogger;
-use Friendica\Util\Profiler;
-use Monolog;
-use Psr\Log\LoggerInterface;
-use Psr\Log\LogLevel;
-
-/**
- * A logger factory
- *
- * Currently only Monolog is supported
- */
-class LoggerFactory
-{
- const DEV_CHANNEL = 'dev';
-
- /**
- * A list of classes, which shouldn't get logged
- *
- * @var array
- */
- private static $ignoreClassList = [
- Logger::class,
- Profiler::class,
- 'Friendica\\Util\\Logger',
- ];
-
- private $channel;
-
- public function __construct(string $channel)
- {
- $this->channel = $channel;
- }
-
- /**
- * 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
- *
- * @return LoggerInterface The PSR-3 compliant logger instance
- */
- public function create(Database $database, IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem)
- {
- if (empty($config->get('system', 'debugging', false))) {
- $logger = new VoidLogger();
- $database->setLogger($logger);
- return $logger;
- }
-
- $introspection = new Introspection(self::$ignoreClassList);
- $level = $config->get('system', 'loglevel');
- $loglevel = self::mapLegacyConfigDebugLevel((string)$level);
-
- switch ($config->get('system', 'logger_config', 'stream')) {
- case 'monolog':
- $loggerTimeZone = new \DateTimeZone('UTC');
- Monolog\Logger::setTimezone($loggerTimeZone);
-
- $logger = new Monolog\Logger($this->channel);
- $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
- $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
- $logger->pushProcessor(new Monolog\Processor\UidProcessor());
- $logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
-
- $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 {
- static::addStreamHandler($logger, $stream, $loglevel);
- } catch (\Throwable $e) {
- // No Logger ..
- $logger = new VoidLogger();
- }
- }
- break;
-
- case 'syslog':
- try {
- $logger = new SyslogLogger($this->channel, $introspection, $loglevel);
- } catch (\Throwable $e) {
- // No logger ...
- $logger = new VoidLogger();
- }
- break;
-
- case 'stream':
- default:
- $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 (\Throwable $t) {
- // No logger ...
- $logger = new VoidLogger();
- }
- } else {
- $logger = new VoidLogger();
- }
- 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);
- }
-
- $database->setLogger($logger);
- return $logger;
- }
-
- /**
- * 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
- *
- * @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 InternalServerErrorException
- * @throws \Exception
- */
- public static function createDev(IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem)
- {
- $debugging = $config->get('system', 'debugging');
- $stream = $config->get('system', 'dlogfile');
- $developerIp = $config->get('system', 'dlogip');
-
- if ((!isset($developerIp) || !$debugging) &&
- (!is_file($stream) || is_writable($stream))) {
- $logger = new VoidLogger();
- return $logger;
- }
-
- $loggerTimeZone = new \DateTimeZone('UTC');
- Monolog\Logger::setTimezone($loggerTimeZone);
-
- $introspection = new Introspection(self::$ignoreClassList);
-
- switch ($config->get('system', 'logger_config', 'stream')) {
-
- case 'monolog':
- $loggerTimeZone = new \DateTimeZone('UTC');
- Monolog\Logger::setTimezone($loggerTimeZone);
-
- $logger = new Monolog\Logger(self::DEV_CHANNEL);
- $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
- $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
- $logger->pushProcessor(new Monolog\Processor\UidProcessor());
- $logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
-
- $logger->pushHandler(new DevelopHandler($developerIp));
-
- static::addStreamHandler($logger, $stream, LogLevel::DEBUG);
- break;
-
- case 'syslog':
- $logger = new SyslogLogger(self::DEV_CHANNEL, $introspection, LogLevel::DEBUG);
- break;
-
- case 'stream':
- default:
- $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);
- }
-
- return $logger;
- }
-
- /**
- * 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($level)
- {
- 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;
- }
- }
-
- /**
- * Adding a handler to a given logger instance
- *
- * @param LoggerInterface $logger The logger instance
- * @param mixed $stream The stream which handles the logger output
- * @param string $level The level, for which this handler at least should handle logging
- *
- * @return void
- *
- * @throws \Exception in case of general failures
- */
- public static function addStreamHandler($logger, $stream, $level = LogLevel::NOTICE)
- {
- if ($logger instanceof Monolog\Logger) {
- $loglevel = Monolog\Logger::toMonologLevel($level);
-
- // fallback to notice if an invalid loglevel is set
- if (!is_int($loglevel)) {
- $loglevel = LogLevel::NOTICE;
- }
-
- $fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
-
- $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
- $fileHandler->setFormatter($formatter);
-
- $logger->pushHandler($fileHandler);
- }
- }
-
- public static function addVoidHandler($logger)
- {
- if ($logger instanceof Monolog\Logger) {
- $logger->pushHandler(new Monolog\Handler\NullHandler());
- }
- }
-}
*
* @param string $url The file/url
*
- * @return false|resource the open stream ressource
+ * @return resource the open stream rssource
+ *
+ * @throws \UnexpectedValueException
*/
public function createStream(string $url)
{
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Util\Introspection;
-use Friendica\Util\Strings;
-use Psr\Log\LoggerInterface;
-use Psr\Log\LogLevel;
-
-/**
- * This class contains all necessary dependencies and calls for Friendica
- * Every new Logger should extend this class and define, how addEntry() works
- *
- * Additional information for each Logger, who extends this class:
- * - Introspection
- * - UID for each call
- * - Channel of the current call (i.e. index, worker, daemon, ...)
- */
-abstract class AbstractLogger implements LoggerInterface
-{
- /**
- * The output channel of this logger
- * @var string
- */
- protected $channel;
-
- /**
- * The Introspection for the current call
- * @var Introspection
- */
- protected $introspection;
-
- /**
- * The UID of the current call
- * @var string
- */
- protected $logUid;
-
- /**
- * Adds a new entry to the log
- *
- * @param int $level
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- abstract protected function addEntry($level, $message, $context = []);
-
- /**
- * @param string $channel The output channel
- * @param Introspection $introspection The introspection of the current call
- *
- * @throws \Exception
- */
- public function __construct($channel, Introspection $introspection)
- {
- $this->channel = $channel;
- $this->introspection = $introspection;
- $this->logUid = Strings::getRandomHex(6);
- }
-
- /**
- * Simple interpolation of PSR-3 compliant replacements ( variables between '{' and '}' )
- * @see https://www.php-fig.org/psr/psr-3/#12-message
- *
- * @param string $message
- * @param array $context
- *
- * @return string the interpolated message
- */
- protected function psrInterpolate($message, array $context = array())
- {
- $replace = [];
- foreach ($context as $key => $value) {
- // check that the value can be casted to string
- if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
- $replace['{' . $key . '}'] = $value;
- } elseif (is_array($value)) {
- $replace['{' . $key . '}'] = @json_encode($value);
- }
- }
-
- return strtr($message, $replace);
- }
-
- /**
- * JSON Encodes an complete array including objects with "__toString()" methods
- *
- * @param array $input an Input Array to encode
- *
- * @return false|string The json encoded output of the array
- */
- protected function jsonEncodeArray(array $input)
- {
- $output = [];
-
- foreach ($input as $key => $value) {
- if (is_object($value) && method_exists($value, '__toString')) {
- $output[$key] = $value->__toString();
- } else {
- $output[$key] = $value;
- }
- }
-
- return @json_encode($output);
- }
-
- /**
- * {@inheritdoc}
- */
- public function emergency($message, array $context = array())
- {
- $this->addEntry(LogLevel::EMERGENCY, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function alert($message, array $context = array())
- {
- $this->addEntry(LogLevel::ALERT, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function critical($message, array $context = array())
- {
- $this->addEntry(LogLevel::CRITICAL, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function error($message, array $context = array())
- {
- $this->addEntry(LogLevel::ERROR, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function warning($message, array $context = array())
- {
- $this->addEntry(LogLevel::WARNING, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function notice($message, array $context = array())
- {
- $this->addEntry(LogLevel::NOTICE, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function info($message, array $context = array())
- {
- $this->addEntry(LogLevel::INFO, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function debug($message, array $context = array())
- {
- $this->addEntry(LogLevel::DEBUG, (string) $message, $context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function log($level, $message, array $context = array())
- {
- $this->addEntry($level, (string) $message, $context);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger\Monolog;
-
-use Monolog\Handler;
-use Monolog\Logger;
-
-/**
- * Simple handler for Friendica developers to use for deeper logging
- *
- * If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
- * you'll use Logger::develop() for the duration of your work, and you clean it up when you're done before submitting your PR.
- */
-class DevelopHandler extends Handler\AbstractHandler
-{
- /**
- * @var string The IP of the developer who wants to debug
- */
- private $developerIp;
-
- /**
- * @param string $developerIp The IP of the developer who wants to debug
- * @param int $level The minimum logging level at which this handler will be triggered
- * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
- */
- public function __construct($developerIp, $level = Logger::DEBUG, $bubble = true)
- {
- parent::__construct($level, $bubble);
-
- $this->developerIp = $developerIp;
- }
-
- /**
- * {@inheritdoc}
- */
- public function handle(array $record)
- {
- if (!$this->isHandling($record)) {
- return false;
- }
-
- /// Just in case the remote IP is the same as the developer IP log the output
- if (!is_null($this->developerIp) && $_SERVER['REMOTE_ADDR'] != $this->developerIp)
- {
- return false;
- }
-
- return false === $this->bubble;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger\Monolog;
-
-use Friendica\Util\Introspection;
-use Monolog\Logger;
-use Monolog\Processor\ProcessorInterface;
-
-/**
- * Injects line/file//function where the log message came from
- */
-class IntrospectionProcessor implements ProcessorInterface
-{
- private $level;
-
- private $introspection;
-
- /**
- * @param Introspection $introspection Holds the Introspection of the current call
- * @param string|int $level The minimum logging level at which this Processor will be triggered
- */
- public function __construct(Introspection $introspection, $level = Logger::DEBUG)
- {
- $this->level = Logger::toMonologLevel($level);
- $introspection->addClasses(array('Monolog\\'));
- $this->introspection = $introspection;
- }
-
- public function __invoke(array $record)
- {
- // return if the level is not high enough
- if ($record['level'] < $this->level) {
- return $record;
- }
- // we should have the call source now
- $record['extra'] = array_merge(
- $record['extra'],
- $this->introspection->getRecord()
- );
-
- return $record;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Core\System;
-use Friendica\Util\Profiler;
-use Psr\Log\LoggerInterface;
-
-/**
- * This Logger adds additional profiling data in case profiling is enabled.
- * It uses a predefined logger.
- */
-class ProfilerLogger implements LoggerInterface
-{
- /**
- * The Logger of the current call
- * @var LoggerInterface
- */
- private $logger;
-
- /**
- * The Profiler for the current call
- * @var Profiler
- */
- protected $profiler;
-
- /**
- * ProfilerLogger constructor.
- * @param LoggerInterface $logger The Logger of the current call
- * @param Profiler $profiler The profiler of the current call
- */
- public function __construct(LoggerInterface $logger, Profiler $profiler)
- {
- $this->logger = $logger;
- $this->profiler = $profiler;
- }
-
- /**
- * {@inheritdoc}
- */
- public function emergency($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->emergency($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function alert($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->alert($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function critical($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->critical($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function error($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->error($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function warning($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->warning($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function notice($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->notice($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function info($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->info($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function debug($message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->debug($message, $context);
- $this->profiler->stopRecording();
- }
-
- /**
- * {@inheritdoc}
- */
- public function log($level, $message, array $context = array())
- {
- $this->profiler->startRecording('file');
- $this->logger->log($level, $message, $context);
- $this->profiler->stopRecording();
- }
-}
+++ /dev/null
-## Friendica\Util\Logger
-
-This namespace contains the different implementations of a Logger.
-
-### Configuration guideline
-
-The following settings are possible for `logger_config`:
-- `monolog`: A Logging framework with lots of additions (see [Monolog](https://github.com/Seldaek/monolog/)). There are just Friendica additions inside the Monolog directory
-- [`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.
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Util\DateTimeFormat;
-use Friendica\Util\FileSystem;
-use Friendica\Util\Introspection;
-use Psr\Log\LogLevel;
-
-/**
- * A Logger instance for logging into a stream (file, stdout, stderr)
- */
-class StreamLogger extends AbstractLogger
-{
- /**
- * The minimum loglevel at which this logger will be triggered
- * @var string
- */
- 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
- */
- private $stream;
-
- /**
- * The current process ID
- * @var int
- */
- private $pid;
-
- /**
- * @var FileSystem
- */
- private $fileSystem;
-
- /**
- * Translates LogLevel log levels to integer values
- * @var array
- */
- private $levelToInt = [
- LogLevel::EMERGENCY => 0,
- LogLevel::ALERT => 1,
- LogLevel::CRITICAL => 2,
- LogLevel::ERROR => 3,
- LogLevel::WARNING => 4,
- LogLevel::NOTICE => 5,
- LogLevel::INFO => 6,
- LogLevel::DEBUG => 7,
- ];
-
- /**
- * {@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 \Exception
- */
- public function __construct($channel, $stream, Introspection $introspection, FileSystem $fileSystem, $level = LogLevel::DEBUG)
- {
- $this->fileSystem = $fileSystem;
-
- parent::__construct($channel, $introspection);
-
- if (is_resource($stream)) {
- $this->stream = $stream;
- } elseif (is_string($stream)) {
- $this->url = $stream;
- } else {
- throw new \InvalidArgumentException('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 \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
- }
-
- $this->checkStream();
- }
-
- public function close()
- {
- if ($this->url && is_resource($this->stream)) {
- fclose($this->stream);
- }
-
- $this->stream = null;
- }
-
- /**
- * Adds a new entry to the log
- *
- * @param int $level
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- protected function addEntry($level, $message, $context = [])
- {
- if (!array_key_exists($level, $this->levelToInt)) {
- throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
- }
-
- $logLevel = $this->levelToInt[$level];
-
- if ($logLevel > $this->logLevel) {
- return;
- }
-
- $this->checkStream();
-
- $formattedLog = $this->formatLog($level, $message, $context);
- fwrite($this->stream, $formattedLog);
- }
-
- /**
- * Formats a log record for the syslog output
- *
- * @param int $level The loglevel/priority
- * @param string $message The message
- * @param array $context The context of this call
- *
- * @return string the formatted syslog output
- */
- private function formatLog($level, $message, $context = [])
- {
- $record = $this->introspection->getRecord();
- $record = array_merge($record, ['uid' => $this->logUid, 'process_id' => $this->pid]);
- $logMessage = '';
-
- $logMessage .= DateTimeFormat::utcNow(DateTimeFormat::ATOM) . ' ';
- $logMessage .= $this->channel . ' ';
- $logMessage .= '[' . strtoupper($level) . ']: ';
- $logMessage .= $this->psrInterpolate($message, $context) . ' ';
- $logMessage .= $this->jsonEncodeArray($context) . ' - ';
- $logMessage .= $this->jsonEncodeArray($record);
- $logMessage .= PHP_EOL;
-
- return $logMessage;
- }
-
- private function checkStream()
- {
- if (is_resource($this->stream)) {
- return;
- }
-
- if (empty($this->url)) {
- throw new \LogicException('Missing stream URL.');
- }
-
- $this->stream = $this->fileSystem->createStream($this->url);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\Introspection;
-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
-{
- const IDENT = 'Friendica';
-
- /**
- * Translates LogLevel log levels to syslog log priorities.
- * @var array
- */
- private $logLevels = [
- LogLevel::DEBUG => LOG_DEBUG,
- LogLevel::INFO => LOG_INFO,
- LogLevel::NOTICE => LOG_NOTICE,
- LogLevel::WARNING => LOG_WARNING,
- LogLevel::ERROR => LOG_ERR,
- LogLevel::CRITICAL => LOG_CRIT,
- LogLevel::ALERT => LOG_ALERT,
- LogLevel::EMERGENCY => LOG_EMERG,
- ];
-
- /**
- * Translates log priorities to string outputs
- * @var array
- */
- private $logToString = [
- LOG_DEBUG => 'DEBUG',
- LOG_INFO => 'INFO',
- LOG_NOTICE => 'NOTICE',
- LOG_WARNING => 'WARNING',
- LOG_ERR => 'ERROR',
- LOG_CRIT => 'CRITICAL',
- LOG_ALERT => 'ALERT',
- LOG_EMERG => 'EMERGENCY'
- ];
-
- /**
- * Indicates what logging options will be used when generating a log message
- * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
- *
- * @var int
- */
- private $logOpts;
-
- /**
- * Used to specify what type of program is logging the message
- * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
- *
- * @var int
- */
- private $logFacility;
-
- /**
- * The minimum loglevel at which this logger will be triggered
- * @var int
- */
- private $logLevel;
-
- /**
- * A error message of the current operation
- * @var string
- */
- private $errorMessage;
-
- /**
- * {@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 \Exception
- */
- public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
- {
- parent::__construct($channel, $introspection);
- $this->logOpts = $logOpts;
- $this->logFacility = $logFacility;
- $this->logLevel = $this->mapLevelToPriority($level);
- $this->introspection->addClasses(array(self::class));
- }
-
- /**
- * Adds a new entry to the syslog
- *
- * @param int $level
- * @param string $message
- * @param array $context
- *
- * @throws InternalServerErrorException if the syslog isn't available
- */
- protected function addEntry($level, $message, $context = [])
- {
- $logLevel = $this->mapLevelToPriority($level);
-
- if ($logLevel > $this->logLevel) {
- return;
- }
-
- $formattedLog = $this->formatLog($logLevel, $message, $context);
- $this->write($logLevel, $formattedLog);
- }
-
- /**
- * Maps the LogLevel (@see LogLevel) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters)
- *
- * @param string $level A LogLevel
- *
- * @return int The SysLog priority
- *
- * @throws \Psr\Log\InvalidArgumentException If the loglevel isn't valid
- */
- public function mapLevelToPriority($level)
- {
- if (!array_key_exists($level, $this->logLevels)) {
- throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
- }
-
- return $this->logLevels[$level];
- }
-
- /**
- * Closes the Syslog
- */
- public function close()
- {
- closelog();
- }
-
- /**
- * Writes a message to the syslog
- * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
- *
- * @param int $priority The Priority
- * @param string $message The message of the log
- *
- * @throws InternalServerErrorException if syslog cannot be used
- */
- private function write($priority, $message)
- {
- set_error_handler([$this, 'customErrorHandler']);
- $opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
- restore_error_handler();
-
- if (!$opened) {
- throw new \UnexpectedValueException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
- }
-
- $this->syslogWrapper($priority, $message);
- }
-
- /**
- * Formats a log record for the syslog output
- *
- * @param int $level The loglevel/priority
- * @param string $message The message
- * @param array $context The context of this call
- *
- * @return string the formatted syslog output
- */
- private function formatLog($level, $message, $context = [])
- {
- $record = $this->introspection->getRecord();
- $record = array_merge($record, ['uid' => $this->logUid]);
- $logMessage = '';
-
- $logMessage .= $this->channel . ' ';
- $logMessage .= '[' . $this->logToString[$level] . ']: ';
- $logMessage .= $this->psrInterpolate($message, $context) . ' ';
- $logMessage .= $this->jsonEncodeArray($context) . ' - ';
- $logMessage .= $this->jsonEncodeArray($record);
-
- return $logMessage;
- }
-
- private function customErrorHandler($code, $msg)
- {
- $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
- }
-
- /**
- * A syslog wrapper to make syslog functionality testable
- *
- * @param int $level The syslog priority
- * @param string $entry The message to send to the syslog function
- */
- protected function syslogWrapper($level, $entry)
- {
- set_error_handler([$this, 'customErrorHandler']);
- $written = syslog($level, $entry);
- restore_error_handler();
-
- if (!$written) {
- throw new \UnexpectedValueException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Psr\Log\LoggerInterface;
-
-/**
- * A Logger instance to not log
- */
-class VoidLogger implements LoggerInterface
-{
- /**
- * System is unusable.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function emergency($message, array $context = array())
- {
- return;
- }
-
- /**
- * Action must be taken immediately.
- *
- * Example: Entire website down, database unavailable, etc. This should
- * trigger the SMS alerts and wake you up.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function alert($message, array $context = array())
- {
- return;
- }
-
- /**
- * Critical conditions.
- *
- * Example: Application component unavailable, unexpected exception.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function critical($message, array $context = array())
- {
- return;
- }
-
- /**
- * Runtime errors that do not require immediate action but should typically
- * be logged and monitored.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function error($message, array $context = array())
- {
- return;
- }
-
- /**
- * Exceptional occurrences that are not errors.
- *
- * Example: Use of deprecated APIs, poor use of an API, undesirable things
- * that are not necessarily wrong.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function warning($message, array $context = array())
- {
- return;
- }
-
- /**
- * Normal but significant events.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function notice($message, array $context = array())
- {
- return;
- }
-
- /**
- * Interesting events.
- *
- * Example: User logs in, SQL logs.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function info($message, array $context = array())
- {
- return;
- }
-
- /**
- * Detailed debug information.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function debug($message, array $context = array())
- {
- return;
- }
-
- /**
- * Logs with an arbitrary level.
- *
- * @param mixed $level
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function log($level, $message, array $context = array())
- {
- return;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Util\Strings;
-use Psr\Log\LoggerInterface;
-
-/**
- * A Logger for specific worker tasks, which adds an additional woker-id to it.
- * Uses the decorator pattern (https://en.wikipedia.org/wiki/Decorator_pattern)
- */
-class WorkerLogger implements LoggerInterface
-{
- /**
- * @var LoggerInterface The original Logger instance
- */
- private $logger;
-
- /**
- * @var string the current worker ID
- */
- private $workerId;
-
- /**
- * @var string The called function name
- */
- private $functionName;
-
- /**
- * @param LoggerInterface $logger The logger for worker entries
- * @param string $functionName The current function name of the worker
- * @param int $idLength The length of the generated worker ID
- */
- public function __construct(LoggerInterface $logger, $functionName = '', $idLength = 7)
- {
- $this->logger = $logger;
- $this->functionName = $functionName;
- $this->workerId = Strings::getRandomHex($idLength);
- }
-
- /**
- * Sets the function name for additional logging
- *
- * @param string $functionName
- */
- public function setFunctionName(string $functionName)
- {
- $this->functionName = $functionName;
- }
-
- /**
- * Adds the worker context for each log entry
- *
- * @param array $context
- */
- private function addContext(array &$context)
- {
- $context['worker_id'] = $this->workerId;
- $context['worker_cmd'] = $this->functionName;
- }
-
- /**
- * Returns the worker ID
- *
- * @return string
- */
- public function getWorkerId()
- {
- return $this->workerId;
- }
-
- /**
- * System is unusable.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function emergency($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->emergency($message, $context);
- }
-
- /**
- * Action must be taken immediately.
- *
- * Example: Entire website down, database unavailable, etc. This should
- * trigger the SMS alerts and wake you up.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function alert($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->alert($message, $context);
- }
-
- /**
- * Critical conditions.
- *
- * Example: Application component unavailable, unexpected exception.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function critical($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->critical($message, $context);
- }
-
- /**
- * Runtime errors that do not require immediate action but should typically
- * be logged and monitored.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function error($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->error($message, $context);
- }
-
- /**
- * Exceptional occurrences that are not errors.
- *
- * Example: Use of deprecated APIs, poor use of an API, undesirable things
- * that are not necessarily wrong.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function warning($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->warning($message, $context);
- }
-
- /**
- * Normal but significant events.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function notice($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->notice($message, $context);
- }
-
- /**
- * Interesting events.
- *
- * Example: User logs in, SQL logs.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function info($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->info($message, $context);
- }
-
- /**
- * Detailed debug information.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function debug($message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->debug($message, $context);
- }
-
- /**
- * Logs with an arbitrary level.
- *
- * @param mixed $level
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function log($level, $message, array $context = [])
- {
- $this->addContext($context);
- $this->logger->log($level, $message, $context);
- }
-}
* and is automatically passed as an argument with the same name
*/
LoggerInterface::class => [
- 'instanceOf' => Factory\LoggerFactory::class,
+ 'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
'constructParams' => [
'index',
],
],
],
'$devLogger' => [
- 'instanceOf' => Factory\LoggerFactory::class,
+ 'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
'constructParams' => [
'dev',
],
use Friendica\DI;
use Friendica\Test\Util\RendererMockTrait;
use Friendica\Test\Util\VFSTrait;
-use Friendica\Util\Logger\VoidLogger;
+use Friendica\Core\Logger\Type\VoidLogger;
use Mockery;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStream;
use Friendica\Contact\FriendSuggest\Factory\FriendSuggest;
use Friendica\Contact\FriendSuggest\Entity;
use Friendica\Test\MockedTest;
-use Friendica\Util\Logger\VoidLogger;
+use Friendica\Core\Logger\Type\VoidLogger;
class FriendSuggestTest extends MockedTest
{
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Test\MockedTest;
+use Friendica\Util\Introspection;
+use Mockery\MockInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+abstract class AbstractLoggerTest extends MockedTest
+{
+ use LoggerDataTrait;
+
+ const LOGLINE = '/.* \[.*\]: .* \{.*\"file\":\".*\".*,.*\"line\":\d*,.*\"function\":\".*\".*,.*\"uid\":\".*\".*}/';
+
+ const FILE = 'test';
+ const LINE = 666;
+ const FUNC = 'myfunction';
+
+ /**
+ * @var Introspection|MockInterface
+ */
+ protected $introspection;
+
+ /**
+ * Returns the content of the current logger instance
+ *
+ * @return string
+ */
+ abstract protected function getContent();
+
+ /**
+ * Returns the current logger instance
+ *
+ * @param string $level the default loglevel
+ *
+ * @return LoggerInterface
+ */
+ abstract protected function getInstance($level = LogLevel::DEBUG);
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->introspection = \Mockery::mock(Introspection::class);
+ $this->introspection->shouldReceive('getRecord')->andReturn([
+ 'file' => self::FILE,
+ 'line' => self::LINE,
+ 'function' => self::FUNC
+ ]);
+ }
+
+ public function assertLogline($string)
+ {
+ self::assertRegExp(self::LOGLINE, $string);
+ }
+
+ public function assertLoglineNums($assertNum, $string)
+ {
+ self::assertEquals($assertNum, preg_match_all(self::LOGLINE, $string));
+ }
+
+ /**
+ * Test if the logger works correctly
+ */
+ public function testNormal()
+ {
+ $logger = $this->getInstance();
+ $logger->emergency('working!');
+ $logger->alert('working too!');
+ $logger->debug('and now?');
+ $logger->notice('message', ['an' => 'context']);
+
+ $text = $this->getContent();
+ self::assertLogline($text);
+ self::assertLoglineNums(4, $text);
+ }
+
+ /**
+ * Test if a log entry is correctly interpolated
+ */
+ public function testPsrInterpolate()
+ {
+ $logger = $this->getInstance();
+
+ $logger->emergency('A {psr} test', ['psr' => 'working']);
+ $logger->alert('An {array} test', ['array' => ['it', 'is', 'working']]);
+ $text = $this->getContent();
+ self::assertStringContainsString('A working test', $text);
+ self::assertStringContainsString('An ["it","is","working"] test', $text);
+ }
+
+ /**
+ * Test if a log entry contains all necessary information
+ */
+ public function testContainsInformation()
+ {
+ $logger = $this->getInstance();
+ $logger->emergency('A test');
+
+ $text = $this->getContent();
+ self::assertStringContainsString('"file":"' . self::FILE . '"', $text);
+ self::assertStringContainsString('"line":' . self::LINE, $text);
+ self::assertStringContainsString('"function":"' . self::FUNC . '"', $text);
+ }
+
+ /**
+ * Test if the minimum level is working
+ */
+ public function testMinimumLevel()
+ {
+ $logger = $this->getInstance(LogLevel::NOTICE);
+
+ $logger->emergency('working');
+ $logger->alert('working');
+ $logger->error('working');
+ $logger->warning('working');
+ $logger->notice('working');
+ $logger->info('not working');
+ $logger->debug('not working');
+
+ $text = $this->getContent();
+
+ self::assertLoglineNums(5, $text);
+ }
+
+ /**
+ * Test with different logging data
+ * @dataProvider dataTests
+ */
+ public function testDifferentTypes($function, $message, array $context)
+ {
+ $logger = $this->getInstance();
+ $logger->$function($message, $context);
+
+ $text = $this->getContent();
+
+ self::assertLogline($text);
+
+ self::assertStringContainsString(@json_encode($context), $text);
+ }
+
+ /**
+ * Test a message with an exception
+ */
+ public function testExceptionHandling()
+ {
+ $e = new \Exception("Test String", 123);
+ $eFollowUp = new \Exception("FollowUp", 456, $e);
+
+ $assertion = $eFollowUp->__toString();
+
+ $logger = $this->getInstance();
+ $logger->alert('test', ['e' => $eFollowUp]);
+ $text = $this->getContent();
+
+ self::assertLogline($text);
+
+ self::assertStringContainsString(@json_encode($assertion), $this->getContent());
+ }
+
+ public function testNoObjectHandling()
+ {
+ $logger = $this->getInstance();
+ $logger->alert('test', ['e' => ['test' => 'test']]);
+ $text = $this->getContent();
+
+ self::assertLogline($text);
+
+ self::assertStringContainsString('test', $this->getContent());
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+trait LoggerDataTrait
+{
+ public function dataTests()
+ {
+ return [
+ 'emergency' => [
+ 'function' => 'emergency',
+ 'message' => 'test',
+ 'context' => ['a' => 'context'],
+ ],
+ 'alert' => [
+ 'function' => 'alert',
+ 'message' => 'test {test}',
+ 'context' => ['a' => 'context', 2 => 'so', 'test' => 'works'],
+ ],
+ 'critical' => [
+ 'function' => 'critical',
+ 'message' => 'test crit 2345',
+ 'context' => ['a' => 'context', 'wit' => ['more', 'array']],
+ ],
+ 'error' => [
+ 'function' => 'error',
+ 'message' => 2.554,
+ 'context' => [],
+ ],
+ 'warning' => [
+ 'function' => 'warning',
+ 'message' => 'test warn',
+ 'context' => ['a' => 'context'],
+ ],
+ 'notice' => [
+ 'function' => 'notice',
+ 'message' => 2346,
+ 'context' => ['a' => 'context'],
+ ],
+ 'info' => [
+ 'function' => 'info',
+ 'message' => null,
+ 'context' => ['a' => 'context'],
+ ],
+ 'debug' => [
+ 'function' => 'debug',
+ 'message' => true,
+ 'context' => ['a' => false],
+ ],
+ ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Test\MockedTest;
+use Friendica\Core\Logger\Type\ProfilerLogger;
+use Friendica\Util\Profiler;
+use Mockery\MockInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+class ProfilerLoggerTest extends MockedTest
+{
+ use LoggerDataTrait;
+
+ /**
+ * @var LoggerInterface|MockInterface
+ */
+ private $logger;
+ /**
+ * @var Profiler|MockInterface
+ */
+ private $profiler;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->logger = \Mockery::mock(LoggerInterface::class);
+ $this->profiler = \Mockery::mock(Profiler::class);
+ }
+
+ /**
+ * Test if the profiler is profiling data
+ * @dataProvider dataTests
+ * @doesNotPerformAssertions
+ */
+ public function testProfiling($function, $message, array $context)
+ {
+ $logger = new ProfilerLogger($this->logger, $this->profiler);
+
+ $this->logger->shouldReceive($function)->with($message, $context)->once();
+ $this->profiler->shouldReceive('startRecording')->with('file')->once();
+ $this->profiler->shouldReceive('stopRecording');
+ $this->profiler->shouldReceive('saveTimestamp');
+ $logger->$function($message, $context);
+ }
+
+ /**
+ * Test the log() function
+ * @doesNotPerformAssertions
+ */
+ public function testProfilingLog()
+ {
+ $logger = new ProfilerLogger($this->logger, $this->profiler);
+
+ $this->logger->shouldReceive('log')->with(LogLevel::WARNING, 'test', ['a' => 'context'])->once();
+ $this->profiler->shouldReceive('startRecording')->with('file')->once();
+ $this->profiler->shouldReceive('stopRecording');
+ $this->profiler->shouldReceive('saveTimestamp');
+
+ $logger->log(LogLevel::WARNING, 'test', ['a' => 'context']);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Core\Logger\Exception\LoggerArgumentException;
+use Friendica\Core\Logger\Exception\LoggerException;
+use Friendica\Util\FileSystem;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Core\Logger\Type\StreamLogger;
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamFile;
+use Psr\Log\LogLevel;
+
+class StreamLoggerTest extends AbstractLoggerTest
+{
+ use VFSTrait;
+
+ /**
+ * @var vfsStreamFile
+ */
+ private $logfile;
+
+ /**
+ * @var Filesystem
+ */
+ private $fileSystem;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->setUpVfsDir();
+
+ $this->fileSystem = new FileSystem();
+ }
+
+ /**
+ * {@@inheritdoc}
+ */
+ protected function getInstance($level = LogLevel::DEBUG)
+ {
+ $this->logfile = vfsStream::newFile('friendica.log')
+ ->at($this->root);
+
+ $logger = new StreamLogger('test', $this->logfile->url(), $this->introspection, $this->fileSystem, $level);
+
+ return $logger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getContent()
+ {
+ 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
+ */
+ public function testNoUrl()
+ {
+ $this->expectException(LoggerArgumentException::class);
+ $this->expectExceptionMessage("Missing stream URL.");
+
+ $logger = new StreamLogger('test', '', $this->introspection, $this->fileSystem);
+
+ $logger->emergency('not working');
+ }
+
+ /**
+ * Test when a file cannot be opened
+ */
+ public function testWrongUrl()
+ {
+ $this->expectException(LoggerException::class);
+ $this->expectExceptionMessage("Cannot create stream.");
+
+ $logfile = vfsStream::newFile('friendica.log')
+ ->at($this->root)->chmod(0);
+
+ $logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
+
+ $logger->emergency('not working');
+ }
+
+ /**
+ * Test when the directory cannot get created
+ */
+ public function testWrongDir()
+ {
+ $this->expectException(\UnexpectedValueException::class);
+ $this->expectExceptionMessageMatches("/Directory .* cannot get created: .* /");
+
+ static::markTestIncomplete('We need a platform independent way to set directory to readonly');
+
+ $logger = new StreamLogger('test', '/$%/wrong/directory/file.txt', $this->introspection, $this->fileSystem);
+
+ $logger->emergency('not working');
+ }
+
+ /**
+ * Test when the minimum level is not valid
+ */
+ public function testWrongMinimumLevel()
+ {
+ $this->expectException(LoggerArgumentException::class);
+ $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
+
+ $logger = new StreamLogger('test', 'file.text', $this->introspection, $this->fileSystem, 'NOPE');
+ }
+
+ /**
+ * Test when the minimum level is not valid
+ */
+ public function testWrongLogLevel()
+ {
+ $this->expectException(LoggerArgumentException::class);
+ $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
+
+ $logfile = vfsStream::newFile('friendica.log')
+ ->at($this->root);
+
+ $logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
+
+ $logger->log('NOPE', 'a test');
+ }
+
+ /**
+ * Test when the file is null
+ */
+ public function testWrongFile()
+ {
+ $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);
+ }
+
+ /**
+ * Test a relative path
+ * @doesNotPerformAssertions
+ */
+ public function testRealPath()
+ {
+ static::markTestSkipped('vfsStream isn\'t compatible with chdir, so not testable.');
+
+ $logfile = vfsStream::newFile('friendica.log')
+ ->at($this->root);
+
+ chdir($this->root->getChild('logs')->url());
+
+ $logger = new StreamLogger('test', '../friendica.log' , $this->introspection, $this->fileSystem);
+
+ $logger->info('Test');
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Core\Logger\Exception\LoggerArgumentException;
+use Friendica\Core\Logger\Exception\LoggerException;
+use Friendica\Core\Logger\Type\SyslogLogger;
+use Psr\Log\LogLevel;
+
+class SyslogLoggerTest extends AbstractLoggerTest
+{
+ /**
+ * @var SyslogLoggerWrapper
+ */
+ private $logger;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->introspection->shouldReceive('addClasses')->with([SyslogLogger::class]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getContent()
+ {
+ return $this->logger->getContent();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getInstance($level = LogLevel::DEBUG)
+ {
+ $this->logger = new SyslogLoggerWrapper('test', $this->introspection, $level);
+
+ return $this->logger;
+ }
+
+
+ /**
+ * Test when the minimum level is not valid
+ */
+ public function testWrongMinimumLevel()
+ {
+ $this->expectException(LoggerArgumentException::class);
+ $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
+
+ $logger = new SyslogLoggerWrapper('test', $this->introspection, 'NOPE');
+ }
+
+ /**
+ * Test when the minimum level is not valid
+ */
+ public function testWrongLogLevel()
+ {
+ $this->expectException(LoggerArgumentException::class);
+ $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
+
+ $logger = new SyslogLoggerWrapper('test', $this->introspection);
+
+ $logger->log('NOPE', 'a test');
+ }
+
+ /**
+ * Test the close() method
+ * @doesNotPerformAssertions
+ */
+ public function testClose()
+ {
+ $logger = new SyslogLoggerWrapper('test', $this->introspection);
+ $logger->emergency('test');
+ $logger->close();
+ // Reopened itself
+ $logger->emergency('test');
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Core\Logger\Type\SyslogLogger;
+use Friendica\Util\Introspection;
+use Psr\Log\LogLevel;
+
+/**
+ * Wraps the SyslogLogger for replacing the syslog call with a string field.
+ */
+class SyslogLoggerWrapper extends SyslogLogger
+{
+ private $content;
+
+ public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
+ {
+ parent::__construct($channel, $introspection, $level, $logOpts, $logFacility);
+
+ $this->content = '';
+ }
+
+ /**
+ * Gets the content from the wrapped Syslog
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @noinspection PhpMissingParentCallCommonInspection
+ */
+ protected function syslogWrapper(int $level, string $entry)
+ {
+ $this->content .= $entry . PHP_EOL;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Test\MockedTest;
+use Friendica\Core\Logger\Type\VoidLogger;
+use Psr\Log\LogLevel;
+
+class VoidLoggerTest extends MockedTest
+{
+ use LoggerDataTrait;
+
+ /**
+ * Test if the profiler is profiling data
+ * @dataProvider dataTests
+ * @doesNotPerformAssertions
+ */
+ public function testNormal($function, $message, array $context)
+ {
+ $logger = new VoidLogger();
+ $logger->$function($message, $context);
+ }
+
+ /**
+ * Test the log() function
+ * @doesNotPerformAssertions
+ */
+ public function testProfilingLog()
+ {
+ $logger = new VoidLogger();
+ $logger->log(LogLevel::WARNING, 'test', ['a' => 'context']);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, 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\Logger;
+
+use Friendica\Core\Logger\Type\WorkerLogger;
+use Friendica\Test\MockedTest;
+use Psr\Log\LoggerInterface;
+
+class WorkerLoggerTest extends MockedTest
+{
+ private function assertUid($uid, $length = 7)
+ {
+ self::assertRegExp('/^[a-zA-Z0-9]{' . $length . '}+$/', $uid);
+ }
+
+ /**
+ * Test the a id with length zero
+ *
+ */
+ public function testGetWorkerIdZero()
+ {
+ $this->expectException(\Error::class);
+
+ $logger = \Mockery::mock(LoggerInterface::class);
+ new WorkerLogger($logger, 'test', 0);
+ }
+
+ /**
+ * Test the generated Uid
+ */
+ public function testGetWorkerId()
+ {
+ $logger = \Mockery::mock(LoggerInterface::class);
+ for ($i = 1; $i < 14; $i++) {
+ $workLogger = new WorkerLogger($logger, 'test', $i);
+ $uid = $workLogger->getWorkerId();
+ self::assertUid($uid, $i);
+ }
+ }
+
+ public function dataTest()
+ {
+ return [
+ 'info' => [
+ 'func' => 'info',
+ 'msg' => 'the alert',
+ 'context' => [],
+ ],
+ 'alert' => [
+ 'func' => 'alert',
+ 'msg' => 'another alert',
+ 'context' => ['test' => 'it'],
+ ],
+ 'critical' => [
+ 'func' => 'critical',
+ 'msg' => 'Critical msg used',
+ 'context' => ['test' => 'it', 'more' => 0.24545],
+ ],
+ 'error' => [
+ 'func' => 'error',
+ 'msg' => 21345623,
+ 'context' => ['test' => 'it', 'yet' => true],
+ ],
+ 'warning' => [
+ 'func' => 'warning',
+ 'msg' => 'another alert' . 123523 . 324.54534 . 'test',
+ 'context' => ['test' => 'it', 2 => 'nope'],
+ ],
+ 'notice' => [
+ 'func' => 'notice',
+ 'msg' => 'Notice' . ' alert' . true . 'with' . '\'strange\'' . 1.24. 'behavior',
+ 'context' => ['test' => 'it'],
+ ],
+ 'debug' => [
+ 'func' => 'debug',
+ 'msg' => 'at last a debug',
+ 'context' => ['test' => 'it'],
+ ],
+ ];
+ }
+
+ /**
+ * Test the WorkerLogger with different log calls
+ * @dataProvider dataTest
+ */
+ public function testEmergency($func, $msg, $context = [])
+ {
+ $logger = \Mockery::mock(LoggerInterface::class);
+ $workLogger = new WorkerLogger($logger, 'test');
+ $testContext = $context;
+ $testContext['worker_id'] = $workLogger->getWorkerId();
+ $testContext['worker_cmd'] = 'test';
+ self::assertUid($testContext['worker_id']);
+ $logger
+ ->shouldReceive($func)
+ ->with($msg, $testContext)
+ ->once();
+ $workLogger->$func($msg, $context);
+ }
+
+ /**
+ * Test the WorkerLogger with
+ */
+ public function testLog()
+ {
+ $logger = \Mockery::mock(LoggerInterface::class);
+ $workLogger = new WorkerLogger($logger, 'test');
+ $context = $testContext = ['test' => 'it'];
+ $testContext['worker_id'] = $workLogger->getWorkerId();
+ $testContext['worker_cmd'] = 'test';
+ self::assertUid($testContext['worker_id']);
+ $logger
+ ->shouldReceive('log')
+ ->with('debug', 'a test', $testContext)
+ ->once();
+ $workLogger->log('debug', 'a test', $context);
+ }
+}
use Friendica\Test\MockedTest;
use Friendica\Util\ACLFormatter;
use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Logger\VoidLogger;
+use Friendica\Core\Logger\Type\VoidLogger;
use Mockery\MockInterface;
class ProfileFieldTest extends MockedTest
use Friendica\Security\TwoFactor\Factory\TrustedBrowser;
use Friendica\Test\MockedTest;
use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Logger\VoidLogger;
+use Friendica\Core\Logger\Type\VoidLogger;
use Friendica\Util\Strings;
class TrustedBrowserTest extends MockedTest
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Test\MockedTest;
-use Friendica\Util\Introspection;
-use Mockery\MockInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\LogLevel;
-
-abstract class AbstractLoggerTest extends MockedTest
-{
- use LoggerDataTrait;
-
- const LOGLINE = '/.* \[.*\]: .* \{.*\"file\":\".*\".*,.*\"line\":\d*,.*\"function\":\".*\".*,.*\"uid\":\".*\".*}/';
-
- const FILE = 'test';
- const LINE = 666;
- const FUNC = 'myfunction';
-
- /**
- * @var Introspection|MockInterface
- */
- protected $introspection;
-
- /**
- * Returns the content of the current logger instance
- *
- * @return string
- */
- abstract protected function getContent();
-
- /**
- * Returns the current logger instance
- *
- * @param string $level the default loglevel
- *
- * @return LoggerInterface
- */
- abstract protected function getInstance($level = LogLevel::DEBUG);
-
- protected function setUp(): void
- {
- parent::setUp();
-
- $this->introspection = \Mockery::mock(Introspection::class);
- $this->introspection->shouldReceive('getRecord')->andReturn([
- 'file' => self::FILE,
- 'line' => self::LINE,
- 'function' => self::FUNC
- ]);
- }
-
- public function assertLogline($string)
- {
- self::assertRegExp(self::LOGLINE, $string);
- }
-
- public function assertLoglineNums($assertNum, $string)
- {
- self::assertEquals($assertNum, preg_match_all(self::LOGLINE, $string));
- }
-
- /**
- * Test if the logger works correctly
- */
- public function testNormal()
- {
- $logger = $this->getInstance();
- $logger->emergency('working!');
- $logger->alert('working too!');
- $logger->debug('and now?');
- $logger->notice('message', ['an' => 'context']);
-
- $text = $this->getContent();
- self::assertLogline($text);
- self::assertLoglineNums(4, $text);
- }
-
- /**
- * Test if a log entry is correctly interpolated
- */
- public function testPsrInterpolate()
- {
- $logger = $this->getInstance();
-
- $logger->emergency('A {psr} test', ['psr' => 'working']);
- $logger->alert('An {array} test', ['array' => ['it', 'is', 'working']]);
- $text = $this->getContent();
- self::assertStringContainsString('A working test', $text);
- self::assertStringContainsString('An ["it","is","working"] test', $text);
- }
-
- /**
- * Test if a log entry contains all necessary information
- */
- public function testContainsInformation()
- {
- $logger = $this->getInstance();
- $logger->emergency('A test');
-
- $text = $this->getContent();
- self::assertStringContainsString('"file":"' . self::FILE . '"', $text);
- self::assertStringContainsString('"line":' . self::LINE, $text);
- self::assertStringContainsString('"function":"' . self::FUNC . '"', $text);
- }
-
- /**
- * Test if the minimum level is working
- */
- public function testMinimumLevel()
- {
- $logger = $this->getInstance(LogLevel::NOTICE);
-
- $logger->emergency('working');
- $logger->alert('working');
- $logger->error('working');
- $logger->warning('working');
- $logger->notice('working');
- $logger->info('not working');
- $logger->debug('not working');
-
- $text = $this->getContent();
-
- self::assertLoglineNums(5, $text);
- }
-
- /**
- * Test with different logging data
- * @dataProvider dataTests
- */
- public function testDifferentTypes($function, $message, array $context)
- {
- $logger = $this->getInstance();
- $logger->$function($message, $context);
-
- $text = $this->getContent();
-
- self::assertLogline($text);
-
- self::assertStringContainsString(@json_encode($context), $text);
- }
-
- /**
- * Test a message with an exception
- */
- public function testExceptionHandling()
- {
- $e = new \Exception("Test String", 123);
- $eFollowUp = new \Exception("FollowUp", 456, $e);
-
- $assertion = $eFollowUp->__toString();
-
- $logger = $this->getInstance();
- $logger->alert('test', ['e' => $eFollowUp]);
- $text = $this->getContent();
-
- self::assertLogline($text);
-
- self::assertStringContainsString(@json_encode($assertion), $this->getContent());
- }
-
- public function testNoObjectHandling()
- {
- $logger = $this->getInstance();
- $logger->alert('test', ['e' => ['test' => 'test']]);
- $text = $this->getContent();
-
- self::assertLogline($text);
-
- self::assertStringContainsString('test', $this->getContent());
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-trait LoggerDataTrait
-{
- public function dataTests()
- {
- return [
- 'emergency' => [
- 'function' => 'emergency',
- 'message' => 'test',
- 'context' => ['a' => 'context'],
- ],
- 'alert' => [
- 'function' => 'alert',
- 'message' => 'test {test}',
- 'context' => ['a' => 'context', 2 => 'so', 'test' => 'works'],
- ],
- 'critical' => [
- 'function' => 'critical',
- 'message' => 'test crit 2345',
- 'context' => ['a' => 'context', 'wit' => ['more', 'array']],
- ],
- 'error' => [
- 'function' => 'error',
- 'message' => 2.554,
- 'context' => [],
- ],
- 'warning' => [
- 'function' => 'warning',
- 'message' => 'test warn',
- 'context' => ['a' => 'context'],
- ],
- 'notice' => [
- 'function' => 'notice',
- 'message' => 2346,
- 'context' => ['a' => 'context'],
- ],
- 'info' => [
- 'function' => 'info',
- 'message' => null,
- 'context' => ['a' => 'context'],
- ],
- 'debug' => [
- 'function' => 'debug',
- 'message' => true,
- 'context' => ['a' => false],
- ],
- ];
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Test\MockedTest;
-use Friendica\Util\Logger\ProfilerLogger;
-use Friendica\Util\Profiler;
-use Mockery\MockInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\LogLevel;
-
-class ProfilerLoggerTest extends MockedTest
-{
- use LoggerDataTrait;
-
- /**
- * @var LoggerInterface|MockInterface
- */
- private $logger;
- /**
- * @var Profiler|MockInterface
- */
- private $profiler;
-
- protected function setUp(): void
- {
- parent::setUp();
-
- $this->logger = \Mockery::mock(LoggerInterface::class);
- $this->profiler = \Mockery::mock(Profiler::class);
- }
-
- /**
- * Test if the profiler is profiling data
- * @dataProvider dataTests
- * @doesNotPerformAssertions
- */
- public function testProfiling($function, $message, array $context)
- {
- $logger = new ProfilerLogger($this->logger, $this->profiler);
-
- $this->logger->shouldReceive($function)->with($message, $context)->once();
- $this->profiler->shouldReceive('startRecording')->with('file')->once();
- $this->profiler->shouldReceive('stopRecording');
- $this->profiler->shouldReceive('saveTimestamp');
- $logger->$function($message, $context);
- }
-
- /**
- * Test the log() function
- * @doesNotPerformAssertions
- */
- public function testProfilingLog()
- {
- $logger = new ProfilerLogger($this->logger, $this->profiler);
-
- $this->logger->shouldReceive('log')->with(LogLevel::WARNING, 'test', ['a' => 'context'])->once();
- $this->profiler->shouldReceive('startRecording')->with('file')->once();
- $this->profiler->shouldReceive('stopRecording');
- $this->profiler->shouldReceive('saveTimestamp');
-
- $logger->log(LogLevel::WARNING, 'test', ['a' => 'context']);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Util\FileSystem;
-use Friendica\Test\Util\VFSTrait;
-use Friendica\Util\Logger\StreamLogger;
-use org\bovigo\vfs\vfsStream;
-use org\bovigo\vfs\vfsStreamFile;
-use Psr\Log\LogLevel;
-
-class StreamLoggerTest extends AbstractLoggerTest
-{
- use VFSTrait;
-
- /**
- * @var vfsStreamFile
- */
- private $logfile;
-
- /**
- * @var Filesystem
- */
- private $fileSystem;
-
- protected function setUp(): void
- {
- parent::setUp();
-
- $this->setUpVfsDir();
-
- $this->fileSystem = new FileSystem();
- }
-
- /**
- * {@@inheritdoc}
- */
- protected function getInstance($level = LogLevel::DEBUG)
- {
- $this->logfile = vfsStream::newFile('friendica.log')
- ->at($this->root);
-
- $logger = new StreamLogger('test', $this->logfile->url(), $this->introspection, $this->fileSystem, $level);
-
- return $logger;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function getContent()
- {
- 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 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
- */
- public function testNoUrl()
- {
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage("Missing stream URL.");
-
- $logger = new StreamLogger('test', '', $this->introspection, $this->fileSystem);
-
- $logger->emergency('not working');
- }
-
- /**
- * Test when a file cannot be opened
- */
- public function testWrongUrl()
- {
- $this->expectException(\UnexpectedValueException::class);
- $this->expectExceptionMessageMatches("/The stream or file .* could not be opened: .* /");
-
- $logfile = vfsStream::newFile('friendica.log')
- ->at($this->root)->chmod(0);
-
- $logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
-
- $logger->emergency('not working');
- }
-
- /**
- * Test when the directory cannot get created
- */
- public function testWrongDir()
- {
- $this->expectException(\UnexpectedValueException::class);
- $this->expectExceptionMessageMatches("/Directory .* cannot get created: .* /");
-
- static::markTestIncomplete('We need a platform independent way to set directory to readonly');
-
- $logger = new StreamLogger('test', '/$%/wrong/directory/file.txt', $this->introspection, $this->fileSystem);
-
- $logger->emergency('not working');
- }
-
- /**
- * Test when the minimum level is not valid
- */
- public function testWrongMinimumLevel()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
-
- $logger = new StreamLogger('test', 'file.text', $this->introspection, $this->fileSystem, 'NOPE');
- }
-
- /**
- * Test when the minimum level is not valid
- */
- public function testWrongLogLevel()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
-
- $logfile = vfsStream::newFile('friendica.log')
- ->at($this->root);
-
- $logger = new StreamLogger('test', $logfile->url(), $this->introspection, $this->fileSystem);
-
- $logger->log('NOPE', 'a test');
- }
-
- /**
- * Test when the file is null
- */
- public function testWrongFile()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage("A stream must either be a resource or a string.");
-
- $logger = new StreamLogger('test', null, $this->introspection, $this->fileSystem);
- }
-
- /**
- * Test a relative path
- * @doesNotPerformAssertions
- */
- public function testRealPath()
- {
- static::markTestSkipped('vfsStream isn\'t compatible with chdir, so not testable.');
-
- $logfile = vfsStream::newFile('friendica.log')
- ->at($this->root);
-
- chdir($this->root->getChild('logs')->url());
-
- $logger = new StreamLogger('test', '../friendica.log' , $this->introspection, $this->fileSystem);
-
- $logger->info('Test');
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Util\Logger\SyslogLogger;
-use Psr\Log\LogLevel;
-
-class SyslogLoggerTest extends AbstractLoggerTest
-{
- /**
- * @var SyslogLoggerWrapper
- */
- private $logger;
-
- protected function setUp(): void
- {
- parent::setUp();
-
- $this->introspection->shouldReceive('addClasses')->with([SyslogLogger::class]);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function getContent()
- {
- return $this->logger->getContent();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function getInstance($level = LogLevel::DEBUG)
- {
- $this->logger = new SyslogLoggerWrapper('test', $this->introspection, $level);
-
- return $this->logger;
- }
-
-
- /**
- * Test when the minimum level is not valid
- */
- public function testWrongMinimumLevel()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
-
- $logger = new SyslogLoggerWrapper('test', $this->introspection, 'NOPE');
- }
-
- /**
- * Test when the minimum level is not valid
- */
- public function testWrongLogLevel()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
-
- $logger = new SyslogLoggerWrapper('test', $this->introspection);
-
- $logger->log('NOPE', 'a test');
- }
-
- /**
- * Test when the logfacility is wrong (string)
- */
- public function testServerException()
- {
- if (PHP_MAJOR_VERSION < 8) {
- $this->expectException(\UnexpectedValueException::class);
- $this->expectExceptionMessageMatches("/Can\'t open syslog for ident \".*\" and facility \".*\": .* /");
- } else {
- $this->expectException(\TypeError::class);
- $this->expectExceptionMessage("openlog(): Argument #3 (\$facility) must be of type int, string given");
- }
-
- $logger = new SyslogLoggerWrapper('test', $this->introspection, LogLevel::DEBUG, null, 'a string');
- $logger->emergency('not working');
- }
-
- /**
- * Test the close() method
- * @doesNotPerformAssertions
- */
- public function testClose()
- {
- $logger = new SyslogLoggerWrapper('test', $this->introspection);
- $logger->emergency('test');
- $logger->close();
- // Reopened itself
- $logger->emergency('test');
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Util\Introspection;
-use Friendica\Util\Logger\SyslogLogger;
-use Psr\Log\LogLevel;
-
-/**
- * Wraps the SyslogLogger for replacing the syslog call with a string field.
- */
-class SyslogLoggerWrapper extends SyslogLogger
-{
- private $content;
-
- public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
- {
- parent::__construct($channel, $introspection, $level, $logOpts, $logFacility);
-
- $this->content = '';
- }
-
- /**
- * Gets the content from the wrapped Syslog
- *
- * @return string
- */
- public function getContent()
- {
- return $this->content;
- }
-
- /**
- * {@inheritdoc}
- * @noinspection PhpMissingParentCallCommonInspection
- */
- protected function syslogWrapper($level, $entry)
- {
- $this->content .= $entry . PHP_EOL;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Test\MockedTest;
-use Friendica\Util\Logger\VoidLogger;
-use Psr\Log\LogLevel;
-
-class VoidLoggerTest extends MockedTest
-{
- use LoggerDataTrait;
-
- /**
- * Test if the profiler is profiling data
- * @dataProvider dataTests
- * @doesNotPerformAssertions
- */
- public function testNormal($function, $message, array $context)
- {
- $logger = new VoidLogger();
- $logger->$function($message, $context);
- }
-
- /**
- * Test the log() function
- * @doesNotPerformAssertions
- */
- public function testProfilingLog()
- {
- $logger = new VoidLogger();
- $logger->log(LogLevel::WARNING, 'test', ['a' => 'context']);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, 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\Util\Logger;
-
-use Friendica\Test\MockedTest;
-use Friendica\Util\Logger\WorkerLogger;
-use Psr\Log\LoggerInterface;
-
-class WorkerLoggerTest extends MockedTest
-{
- private function assertUid($uid, $length = 7)
- {
- self::assertRegExp('/^[a-zA-Z0-9]{' . $length . '}+$/', $uid);
- }
-
- /**
- * Test the a id with length zero
- *
- */
- public function testGetWorkerIdZero()
- {
- $this->expectException(\Error::class);
-
- $logger = \Mockery::mock(LoggerInterface::class);
- new WorkerLogger($logger, 'test', 0);
- }
-
- /**
- * Test the generated Uid
- */
- public function testGetWorkerId()
- {
- $logger = \Mockery::mock(LoggerInterface::class);
- for ($i = 1; $i < 14; $i++) {
- $workLogger = new WorkerLogger($logger, 'test', $i);
- $uid = $workLogger->getWorkerId();
- self::assertUid($uid, $i);
- }
- }
-
- public function dataTest()
- {
- return [
- 'info' => [
- 'func' => 'info',
- 'msg' => 'the alert',
- 'context' => [],
- ],
- 'alert' => [
- 'func' => 'alert',
- 'msg' => 'another alert',
- 'context' => ['test' => 'it'],
- ],
- 'critical' => [
- 'func' => 'critical',
- 'msg' => 'Critical msg used',
- 'context' => ['test' => 'it', 'more' => 0.24545],
- ],
- 'error' => [
- 'func' => 'error',
- 'msg' => 21345623,
- 'context' => ['test' => 'it', 'yet' => true],
- ],
- 'warning' => [
- 'func' => 'warning',
- 'msg' => 'another alert' . 123523 . 324.54534 . 'test',
- 'context' => ['test' => 'it', 2 => 'nope'],
- ],
- 'notice' => [
- 'func' => 'notice',
- 'msg' => 'Notice' . ' alert' . true . 'with' . '\'strange\'' . 1.24. 'behavior',
- 'context' => ['test' => 'it'],
- ],
- 'debug' => [
- 'func' => 'debug',
- 'msg' => 'at last a debug',
- 'context' => ['test' => 'it'],
- ],
- ];
- }
-
- /**
- * Test the WorkerLogger with different log calls
- * @dataProvider dataTest
- */
- public function testEmergency($func, $msg, $context = [])
- {
- $logger = \Mockery::mock(LoggerInterface::class);
- $workLogger = new WorkerLogger($logger, 'test');
- $testContext = $context;
- $testContext['worker_id'] = $workLogger->getWorkerId();
- $testContext['worker_cmd'] = 'test';
- self::assertUid($testContext['worker_id']);
- $logger
- ->shouldReceive($func)
- ->with($msg, $testContext)
- ->once();
- $workLogger->$func($msg, $context);
- }
-
- /**
- * Test the WorkerLogger with
- */
- public function testLog()
- {
- $logger = \Mockery::mock(LoggerInterface::class);
- $workLogger = new WorkerLogger($logger, 'test');
- $context = $testContext = ['test' => 'it'];
- $testContext['worker_id'] = $workLogger->getWorkerId();
- $testContext['worker_cmd'] = 'test';
- self::assertUid($testContext['worker_id']);
- $logger
- ->shouldReceive('log')
- ->with('debug', 'a test', $testContext)
- ->once();
- $workLogger->log('debug', 'a test', $context);
- }
-}