use Friendica\Core\Config\Configuration;
use Friendica\Core\Logger;
use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\Logger\FriendicaDevelopHandler;
-use Friendica\Util\Logger\Introspection;
+use Friendica\Util\Introspection;
+use Friendica\Util\Logger\Monolog\FriendicaDevelopHandler;
+use Friendica\Util\Logger\Monolog\FriendicaIntrospectionProcessor;
use Friendica\Util\Logger\SyslogLogger;
use Friendica\Util\Logger\VoidLogger;
use Friendica\Util\Logger\WorkerLogger;
* @param Configuration $config The config
*
* @return LoggerInterface The PSR-3 compliant logger instance
+ *
+ * @throws \Exception
* @throws InternalServerErrorException
*/
public static function create($channel, Configuration $config)
return $logger;
}
- $introspector = new Introspection(LogLevel::DEBUG, self::$ignoreClassList);
+ $introspection = new Introspection(self::$ignoreClassList);
+
switch ($config->get('system', 'logger_adapter', 'monolog')) {
case 'syslog':
$level = $config->get('system', 'loglevel');
- $logger = new SyslogLogger($channel, $introspector, $level);
+ $logger = new SyslogLogger($channel, $introspection, $level);
break;
case 'monolog':
default:
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
- $logger->pushProcessor($introspector);
+ $logger->pushProcessor(new FriendicaIntrospectionProcessor($introspection, LogLevel::DEBUG));
$stream = $config->get('system', 'logfile');
$level = $config->get('system', 'loglevel');
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
+ $introspection = new Introspection(self::$ignoreClassList);
+
$logger = new Monolog\Logger($channel);
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
- $logger->pushProcessor(new Introspection(LogLevel::DEBUG, self::$ignoreClassList));
+ $logger->pushProcessor(new FriendicaIntrospectionProcessor($introspection, LogLevel::DEBUG));
$logger->pushHandler(new FriendicaDevelopHandler($developerIp));
--- /dev/null
+<?php
+
+namespace Friendica\Util;
+
+/**
+ * Get Introspection information about the current call
+ */
+class Introspection
+{
+ private $skipStackFramesCount;
+
+ private $skipClassesPartials;
+
+ private $skipFunctions = [
+ 'call_user_func',
+ 'call_user_func_array',
+ ];
+
+ /**
+ * @param array $skipClassesPartials An array of classes to skip during logging
+ * @param int $skipStackFramesCount If the logger should use information from other hierarchy levels of the call
+ */
+ public function __construct($skipClassesPartials = array(), $skipStackFramesCount = 0)
+ {
+ $this->skipClassesPartials = $skipClassesPartials;
+ $this->skipStackFramesCount = $skipStackFramesCount;
+ }
+
+ /**
+ * Adds new classes to get skipped
+ * @param array $classNames
+ */
+ public function addClasses(array $classNames)
+ {
+ $this->skipClassesPartials = array_merge($this->skipClassesPartials, $classNames);
+ }
+
+ /**
+ * Returns the introspection record of the current call
+ *
+ * @return array
+ */
+ public function getRecord()
+ {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+
+ $i = 1;
+
+ while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
+ $i++;
+ }
+
+ $i += $this->skipStackFramesCount;
+
+ return [
+ 'file' => isset($trace[$i - 1]['file']) ? basename($trace[$i - 1]['file']) : null,
+ 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
+ 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
+ ];
+ }
+
+ /**
+ * Checks if the current trace class or function has to be skipped
+ *
+ * @param array $trace The current trace array
+ * @param int $index The index of the current hierarchy level
+ * @return bool True if the class or function should get skipped, otherwise false
+ */
+ private function isTraceClassOrSkippedFunction(array $trace, $index)
+ {
+ if (!isset($trace[$index])) {
+ return false;
+ }
+
+ if (isset($trace[$index]['class'])) {
+ foreach ($this->skipClassesPartials as $part) {
+ if (strpos($trace[$index]['class'], $part) !== false) {
+ return true;
+ }
+ }
+ } elseif (in_array($trace[$index]['function'], $this->skipFunctions)) {
+ return true;
+ }
+
+ return false;
+ }
+}
+++ /dev/null
-<?php
-
-namespace Friendica\Util\Logger;
-
-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 FriendicaDevelopHandler 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
-
-namespace Friendica\Util\Logger;
-
-use Monolog\Logger;
-use Monolog\Processor\ProcessorInterface;
-
-/**
- * Injects line/file//function where the log message came from
- *
- * Based on the class IntrospectionProcessor without the "class" information
- * @see IntrospectionProcessor
- */
-class Introspection implements ProcessorInterface
-{
- private $level;
-
- private $skipStackFramesCount;
-
- private $skipClassesPartials;
-
- private $skipFunctions = [
- 'call_user_func',
- 'call_user_func_array',
- ];
-
- /**
- * @param string|int $level The minimum logging level at which this Processor will be triggered
- * @param array $skipClassesPartials An array of classes to skip during logging
- * @param int $skipStackFramesCount If the logger should use information from other hierarchy levels of the call
- */
- public function __construct($level = Logger::DEBUG, $skipClassesPartials = array(), $skipStackFramesCount = 0)
- {
- $this->level = Logger::toMonologLevel($level);
- $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials);
- $this->skipStackFramesCount = $skipStackFramesCount;
- }
-
- 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->getRecord()
- );
-
- return $record;
- }
-
- /**
- * Returns the introspection record of the current call
- *
- * @return array
- */
- public function getRecord()
- {
- $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
-
- $i = 1;
-
- while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
- $i++;
- }
-
- $i += $this->skipStackFramesCount;
-
- return [
- 'file' => isset($trace[$i - 1]['file']) ? basename($trace[$i - 1]['file']) : null,
- 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
- 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
- ];
- }
-
- /**
- * Checks if the current trace class or function has to be skipped
- *
- * @param array $trace The current trace array
- * @param int $index The index of the current hierarchy level
- * @return bool True if the class or function should get skipped, otherwise false
- */
- private function isTraceClassOrSkippedFunction(array $trace, $index)
- {
- if (!isset($trace[$index])) {
- return false;
- }
-
- if (isset($trace[$index]['class'])) {
- foreach ($this->skipClassesPartials as $part) {
- if (strpos($trace[$index]['class'], $part) !== false) {
- return true;
- }
- }
- } elseif (in_array($trace[$index]['function'], $this->skipFunctions)) {
- return true;
- }
-
- return false;
- }
-}
--- /dev/null
+<?php
+
+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 FriendicaDevelopHandler 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
+
+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 FriendicaIntrospectionProcessor 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
+
+namespace Friendica\Util\Logger;
+
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Util\Introspection;
+use Friendica\Util\Strings;
+use Psr\Log\InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * A Logger instance for logging into a stream
+ * @see http://php.net/manual/en/function.syslog.php
+ */
+class StreamLogger implements LoggerInterface
+{
+ 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'
+ ];
+
+ /**
+ * The channel of the current process (added to each message)
+ * @var string
+ */
+ private $channel;
+
+ /**
+ * 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;
+
+ /**
+ * The Introspection for the current call
+ * @var Introspection
+ */
+ private $introspection;
+
+ /**
+ * The UID of the current call
+ * @var string
+ */
+ private $logUid;
+
+ /**
+ * @param string $channel The output channel
+ * @param Introspection $introspection The introspection of the current call
+ * @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)
+ {
+ $this->logUid = Strings::getRandomHex(6);
+ $this->channel = $channel;
+ $this->logOpts = $logOpts;
+ $this->logFacility = $logFacility;
+ $this->logLevel = $this->mapLevelToPriority($level);
+ $this->introspection = $introspection;
+ }
+
+ /**
+ * 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('LogLevel \'' . $level . '\' isn\'t valid.');
+ }
+
+ return $this->logLevels[$level];
+ }
+
+ /**
+ * 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)
+ {
+ if (!openlog(self::IDENT, $this->logOpts, $this->logFacility)) {
+ throw new InternalServerErrorException('Can\'t open syslog for ident "' . $this->channel . '" and facility "' . $this->logFacility . '""');
+ }
+
+ syslog($priority, $message);
+ }
+
+ /**
+ * Closes the Syslog
+ */
+ public function close()
+ {
+ closelog();
+ }
+
+ /**
+ * 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 .= @json_encode($context) . ' - ';
+ $logMessage .= @json_encode($record);
+
+ return $logMessage;
+ }
+
+ /**
+ * 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
+ */
+ private 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);
+ }
+
+ /**
+ * Adds a new entry to the syslog
+ *
+ * @param int $level
+ * @param string $message
+ * @param array $context
+ *
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ private function addEntry($level, $message, $context = [])
+ {
+ if ($level >= $this->logLevel) {
+ return;
+ }
+
+ $formattedLog = $this->formatLog($level, $message, $context);
+ $this->write($level, $formattedLog);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->addEntry(LOG_EMERG, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->addEntry(LOG_ALERT, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->addEntry(LOG_CRIT, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function error($message, array $context = array())
+ {
+ $this->addEntry(LOG_ERR, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->addEntry(LOG_WARNING, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->addEntry(LOG_NOTICE, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function info($message, array $context = array())
+ {
+ $this->addEntry(LOG_INFO, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->addEntry(LOG_DEBUG, $message, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws InternalServerErrorException if the syslog isn't available
+ */
+ public function log($level, $message, array $context = array())
+ {
+ $logLevel = $this->mapLevelToPriority($level);
+ $this->addEntry($logLevel, $message, $context);
+ }
+}
namespace Friendica\Util\Logger;
use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Util\Introspection;
use Friendica\Util\Strings;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerInterface;
private $logLevel;
/**
- * The Introspector for the current call
+ * The Introspection for the current call
* @var Introspection
*/
private $introspection;
private $logUid;
/**
- * @param string $channel The output channel
- * @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
+ * @param string $channel The output channel
+ * @param Introspection $introspection The introspection of the current call
+ * @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)
{