X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FUtil%2FLogger%2FStreamLogger.php;h=3031461061f3a20299d6f1204210bfc18317a92b;hb=d1435bef6144f517ae4392d200e2eb14114adf1c;hp=8d9ffec3873c4fe0126d5b038f1455bf254ef044;hpb=22f1983cc0c834f88f6a519e81c456a8a34d14cc;p=friendica.git diff --git a/src/Util/Logger/StreamLogger.php b/src/Util/Logger/StreamLogger.php index 8d9ffec387..3031461061 100644 --- a/src/Util/Logger/StreamLogger.php +++ b/src/Util/Logger/StreamLogger.php @@ -2,13 +2,14 @@ namespace Friendica\Util\Logger; +use Friendica\Util\DateTimeFormat; use Friendica\Util\Introspection; -use Friendica\Util\Profiler; +use Psr\Log\LogLevel; /** * A Logger instance for logging into a stream (file, stdout, stderr) */ -class StreamLogger extends AbstractFriendicaLogger +class StreamLogger extends AbstractLogger { /** * The minimum loglevel at which this logger will be triggered @@ -16,10 +17,79 @@ class StreamLogger extends AbstractFriendicaLogger */ private $logLevel; - public function __construct($channel, Introspection $introspection, Profiler $profiler, $level) + /** + * 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; + + /** + * An error message + * @var string + */ + private $errorMessage; + + /** + * 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, $level = LogLevel::DEBUG) + { + 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)); + } + } + + public function close() { - parent::__construct($channel, $introspection, $profiler); - $this->logLevel = $level; + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + + $this->stream = null; } /** @@ -33,6 +103,95 @@ class StreamLogger extends AbstractFriendicaLogger */ protected function addEntry($level, $message, $context = []) { - // TODO: Implement addEntry() method. + 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() . ' '; + $logMessage .= $this->channel . ' '; + $logMessage .= '[' . strtoupper($level) . ']: '; + $logMessage .= $this->psrInterpolate($message, $context) . ' '; + $logMessage .= @json_encode($context) . ' - '; + $logMessage .= @json_encode($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->createDir(); + set_error_handler([$this, 'customErrorHandler']); + $this->stream = fopen($this->url, 'ab'); + restore_error_handler(); + + if (!is_resource($this->stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: ' . $this->errorMessage, $this->url)); + } + } + + private function createDir() + { + $dirname = null; + $pos = strpos($this->url, '://'); + if (!$pos) { + $dirname = dirname($this->url); + } + + if (substr($this->url, 0, 7) === 'file://') { + $dirname = dirname(substr($this->url, 7)); + } + + if (isset($dirname) && !is_dir($dirname)) { + set_error_handler([$this, 'customErrorHandler']); + $status = mkdir($dirname, 0777, true); + restore_error_handler(); + + if (!$status && !is_dir($dirname)) { + throw new \UnexpectedValueException(sprintf('Directory "%s" cannot get created: ' . $this->errorMessage, $dirname)); + } + } + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); } }