Adding SyslogLogger
authorPhilipp Holzer <admin@philipp.info>
Wed, 27 Feb 2019 15:40:35 +0000 (16:40 +0100)
committerHypolite Petovan <hypolite@mrpetovan.com>
Sat, 23 Mar 2019 02:52:45 +0000 (22:52 -0400)
config/defaults.config.php
src/Factory/LoggerFactory.php
src/Util/Logger/FriendicaIntrospectionProcessor.php [deleted file]
src/Util/Logger/Introspection.php [new file with mode: 0644]
src/Util/Logger/SyslogLogger.php [new file with mode: 0644]

index a6f90f3..e2aa13d 100644 (file)
@@ -214,6 +214,10 @@ return [
                // If activated, all hashtags will point to the local server.
                'local_tags' => false,
 
+               // logger_adapter (String)
+               // Sets the logging adapter of Friendica globally (monolog, syslog)
+               'logger_adapter' => 'syslog',
+
                // max_batch_queue (Integer)
                // Maximum number of batched queue items for a single contact before subsequent messages are discarded.
                'max_batch_queue' => 1000,
index 81c15bd..863b30e 100644 (file)
@@ -6,7 +6,8 @@ use Friendica\Core\Config\Configuration;
 use Friendica\Core\Logger;
 use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Util\Logger\FriendicaDevelopHandler;
-use Friendica\Util\Logger\FriendicaIntrospectionProcessor;
+use Friendica\Util\Logger\Introspection;
+use Friendica\Util\Logger\SyslogLogger;
 use Friendica\Util\Logger\WorkerLogger;
 use Friendica\Util\Profiler;
 use Monolog;
@@ -37,27 +38,39 @@ class LoggerFactory
         * @param Configuration $config  The config
         *
         * @return LoggerInterface The PSR-3 compliant logger instance
+        * @throws InternalServerErrorException
         */
        public static function create($channel, Configuration $config)
        {
-               $loggerTimeZone = new \DateTimeZone('UTC');
-               Monolog\Logger::setTimezone($loggerTimeZone);
-
-               $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 FriendicaIntrospectionProcessor(LogLevel::DEBUG, self::$ignoreClassList));
-
-               $debugging = $config->get('system', 'debugging');
-               $stream    = $config->get('system', 'logfile');
-               $level     = $config->get('system', 'loglevel');
-
-               if ($debugging) {
-                       $loglevel = self::mapLegacyConfigDebugLevel((string)$level);
-                       static::addStreamHandler($logger, $stream, $loglevel);
-               } else {
-                       static::addVoidHandler($logger);
+               $introspector = new Introspection(LogLevel::DEBUG, self::$ignoreClassList);
+               switch ($config->get('system', 'logger_adapter', 'monolog')) {
+                       case 'syslog':
+                               $level = $config->get('system', 'loglevel');
+
+                               $logger = new SyslogLogger($channel, $introspector, $level);
+                               break;
+                       case 'monolog':
+                       default:
+                               $loggerTimeZone = new \DateTimeZone('UTC');
+                               Monolog\Logger::setTimezone($loggerTimeZone);
+
+                               $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($introspector);
+
+                               $debugging = $config->get('system', 'debugging');
+                               $stream    = $config->get('system', 'logfile');
+                               $level     = $config->get('system', 'loglevel');
+
+                               if ($debugging) {
+                                       $loglevel = self::mapLegacyConfigDebugLevel((string)$level);
+                                       static::addStreamHandler($logger, $stream, $loglevel);
+                               } else {
+                                       static::addVoidHandler($logger);
+                               }
+                               break;
                }
 
                Logger::init($logger);
@@ -77,6 +90,7 @@ class LoggerFactory
         * @param Configuration $config  The config
         *
         * @return LoggerInterface The PSR-3 compliant logger instance
+        * @throws InternalServerErrorException
         */
        public static function createDev($channel, Configuration $config)
        {
@@ -95,7 +109,7 @@ class LoggerFactory
                $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
                $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
                $logger->pushProcessor(new Monolog\Processor\UidProcessor());
-               $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, self::$ignoreClassList));
+               $logger->pushProcessor(new Introspection(LogLevel::DEBUG, self::$ignoreClassList));
 
                $logger->pushHandler(new FriendicaDevelopHandler($developerIp));
 
diff --git a/src/Util/Logger/FriendicaIntrospectionProcessor.php b/src/Util/Logger/FriendicaIntrospectionProcessor.php
deleted file mode 100644 (file)
index aa3933a..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?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 FriendicaIntrospectionProcessor 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;
-               }
-
-               $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
-
-               $i = 1;
-
-               while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
-                       $i++;
-               }
-
-               $i += $this->skipStackFramesCount;
-
-               // we should have the call source now
-               $record['extra'] = array_merge(
-                       $record['extra'],
-                       [
-                               '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,
-                       ]
-               );
-
-               return $record;
-       }
-
-       /**
-        * 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;
-       }
-}
diff --git a/src/Util/Logger/Introspection.php b/src/Util/Logger/Introspection.php
new file mode 100644 (file)
index 0000000..83fec6c
--- /dev/null
@@ -0,0 +1,103 @@
+<?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;
+       }
+}
diff --git a/src/Util/Logger/SyslogLogger.php b/src/Util/Logger/SyslogLogger.php
new file mode 100644 (file)
index 0000000..3734e68
--- /dev/null
@@ -0,0 +1,215 @@
+<?php
+
+namespace Friendica\Util\Logger;
+
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Psr\Log\InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * A Logger instance for syslogging (fast, but simple)
+ * @see http://php.net/manual/en/function.syslog.php
+ */
+class SyslogLogger implements LoggerInterface
+{
+       /**
+        * Translates LogLevel log levels to syslog log priorities.
+        */
+       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,
+       ];
+
+       /**
+        * The standard ident of the syslog (added to each message)
+        * @var string
+        */
+       private $ident;
+
+       /**
+        * 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 Introspector for the current call
+        * @var Introspection
+        */
+       private $introspection;
+
+       /**
+        * @param string $channel     The channel (Syslog ident)
+        * @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 InternalServerErrorException if the loglevel isn't valid
+        */
+       public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
+       {
+               $this->ident = $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
+        *
+        * @param int    $priority The Priority ( @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters )
+        * @param string $message The message of the log
+        * @throws InternalServerErrorException if syslog cannot be used
+        */
+       private function write($priority, $message)
+       {
+               if (!openlog($this->ident, $this->logOpts, $this->logFacility)) {
+                       throw new InternalServerErrorException('Can\'t open syslog for ident "' . $this->ident . '" and facility "' . $this->logFacility . '""');
+               }
+
+               syslog($priority, $message);
+       }
+
+       public function close()
+       {
+               closelog();
+       }
+
+       private function formatLog($level, $message, $context = [])
+       {
+               $logMessage  = '';
+
+               $logMessage .= $this->ident . ' ';
+               $logMessage .= '[' . $level . ']: ';
+               $logMessage .= $message . ' ';
+               $logMessage .= json_encode($context) . ' - ';
+               $logMessage .= json_encode($this->introspection->getRecord());
+
+               return $logMessage;
+       }
+
+       private function addEntry($level, $message, $context = [])
+       {
+               if ($level >= $this->logLevel) {
+                       return;
+               }
+
+               $formattedLog = $this->formatLog($level, $message, $context);
+               $this->write($level, $formattedLog);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function emergency($message, array $context = array())
+       {
+               $this->addEntry(LOG_EMERG, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function alert($message, array $context = array())
+       {
+               $this->addEntry(LOG_ALERT, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function critical($message, array $context = array())
+       {
+               $this->addEntry(LOG_CRIT, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function error($message, array $context = array())
+       {
+               $this->addEntry(LOG_ERR, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function warning($message, array $context = array())
+       {
+               $this->addEntry(LOG_WARNING, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function notice($message, array $context = array())
+       {
+               $this->addEntry(LOG_NOTICE, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function info($message, array $context = array())
+       {
+               $this->addEntry(LOG_INFO, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function debug($message, array $context = array())
+       {
+               $this->addEntry(LOG_DEBUG, $message, $context);
+       }
+
+       /**
+        * {@inheritdoc}
+        */
+       public function log($level, $message, array $context = array())
+       {
+               $logLevel = $this->mapLevelToPriority($level);
+               $this->addEntry($logLevel, $message, $context);
+       }
+}