3 namespace Friendica\Util\Logger;
5 use Friendica\Network\HTTPException\InternalServerErrorException;
6 use Friendica\Util\Introspection;
7 use Friendica\Util\Strings;
8 use Psr\Log\InvalidArgumentException;
9 use Psr\Log\LoggerInterface;
13 * A Logger instance for logging into a stream
14 * @see http://php.net/manual/en/function.syslog.php
16 class StreamLogger implements LoggerInterface
18 const IDENT = 'Friendica';
21 * Translates LogLevel log levels to syslog log priorities.
24 private $logLevels = [
25 LogLevel::DEBUG => LOG_DEBUG,
26 LogLevel::INFO => LOG_INFO,
27 LogLevel::NOTICE => LOG_NOTICE,
28 LogLevel::WARNING => LOG_WARNING,
29 LogLevel::ERROR => LOG_ERR,
30 LogLevel::CRITICAL => LOG_CRIT,
31 LogLevel::ALERT => LOG_ALERT,
32 LogLevel::EMERGENCY => LOG_EMERG,
36 * Translates log priorities to string outputs
39 private $logToString = [
42 LOG_NOTICE => 'NOTICE',
43 LOG_WARNING => 'WARNING',
45 LOG_CRIT => 'CRITICAL',
47 LOG_EMERG => 'EMERGENCY'
51 * The channel of the current process (added to each message)
57 * Indicates what logging options will be used when generating a log message
58 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
65 * Used to specify what type of program is logging the message
66 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
73 * The minimum loglevel at which this logger will be triggered
79 * The Introspection for the current call
82 private $introspection;
85 * The UID of the current call
91 * @param string $channel The output channel
92 * @param Introspection $introspection The introspection of the current call
93 * @param string $level The minimum loglevel at which this logger will be triggered
94 * @param int $logOpts Indicates what logging options will be used when generating a log message
95 * @param int $logFacility Used to specify what type of program is logging the message
99 public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
101 $this->logUid = Strings::getRandomHex(6);
102 $this->channel = $channel;
103 $this->logOpts = $logOpts;
104 $this->logFacility = $logFacility;
105 $this->logLevel = $this->mapLevelToPriority($level);
106 $this->introspection = $introspection;
110 * Maps the LogLevel (@see LogLevel ) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters )
112 * @param string $level A LogLevel
114 * @return int The SysLog priority
116 * @throws \Psr\Log\InvalidArgumentException If the loglevel isn't valid
118 public function mapLevelToPriority($level)
120 if (!array_key_exists($level, $this->logLevels)) {
121 throw new InvalidArgumentException('LogLevel \'' . $level . '\' isn\'t valid.');
124 return $this->logLevels[$level];
128 * Writes a message to the syslog
129 * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
131 * @param int $priority The Priority
132 * @param string $message The message of the log
134 * @throws InternalServerErrorException if syslog cannot be used
136 private function write($priority, $message)
138 if (!openlog(self::IDENT, $this->logOpts, $this->logFacility)) {
139 throw new InternalServerErrorException('Can\'t open syslog for ident "' . $this->channel . '" and facility "' . $this->logFacility . '""');
142 syslog($priority, $message);
148 public function close()
154 * Formats a log record for the syslog output
156 * @param int $level The loglevel/priority
157 * @param string $message The message
158 * @param array $context The context of this call
160 * @return string the formatted syslog output
162 private function formatLog($level, $message, $context = [])
164 $record = $this->introspection->getRecord();
165 $record = array_merge($record, ['uid' => $this->logUid]);
168 $logMessage .= $this->channel . ' ';
169 $logMessage .= '[' . $this->logToString[$level] . ']: ';
170 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
171 $logMessage .= @json_encode($context) . ' - ';
172 $logMessage .= @json_encode($record);
178 * Simple interpolation of PSR-3 compliant replacements ( variables between '{' and '}' )
179 * @see https://www.php-fig.org/psr/psr-3/#12-message
181 * @param string $message
182 * @param array $context
184 * @return string the interpolated message
186 private function psrInterpolate($message, array $context = array())
189 foreach ($context as $key => $value) {
190 // check that the value can be casted to string
191 if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
192 $replace['{' . $key . '}'] = $value;
193 } elseif (is_array($value)) {
194 $replace['{' . $key . '}'] = @json_encode($value);
198 return strtr($message, $replace);
202 * Adds a new entry to the syslog
205 * @param string $message
206 * @param array $context
208 * @throws InternalServerErrorException if the syslog isn't available
210 private function addEntry($level, $message, $context = [])
212 if ($level >= $this->logLevel) {
216 $formattedLog = $this->formatLog($level, $message, $context);
217 $this->write($level, $formattedLog);
222 * @throws InternalServerErrorException if the syslog isn't available
224 public function emergency($message, array $context = array())
226 $this->addEntry(LOG_EMERG, $message, $context);
231 * @throws InternalServerErrorException if the syslog isn't available
233 public function alert($message, array $context = array())
235 $this->addEntry(LOG_ALERT, $message, $context);
240 * @throws InternalServerErrorException if the syslog isn't available
242 public function critical($message, array $context = array())
244 $this->addEntry(LOG_CRIT, $message, $context);
249 * @throws InternalServerErrorException if the syslog isn't available
251 public function error($message, array $context = array())
253 $this->addEntry(LOG_ERR, $message, $context);
258 * @throws InternalServerErrorException if the syslog isn't available
260 public function warning($message, array $context = array())
262 $this->addEntry(LOG_WARNING, $message, $context);
267 * @throws InternalServerErrorException if the syslog isn't available
269 public function notice($message, array $context = array())
271 $this->addEntry(LOG_NOTICE, $message, $context);
276 * @throws InternalServerErrorException if the syslog isn't available
278 public function info($message, array $context = array())
280 $this->addEntry(LOG_INFO, $message, $context);
285 * @throws InternalServerErrorException if the syslog isn't available
287 public function debug($message, array $context = array())
289 $this->addEntry(LOG_DEBUG, $message, $context);
294 * @throws InternalServerErrorException if the syslog isn't available
296 public function log($level, $message, array $context = array())
298 $logLevel = $this->mapLevelToPriority($level);
299 $this->addEntry($logLevel, $message, $context);