3 * @copyright Copyright (C) 2010-2021, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Util\Logger;
24 use Friendica\Network\HTTPException\InternalServerErrorException;
25 use Friendica\Util\Introspection;
29 * A Logger instance for syslogging (fast, but simple)
30 * @see http://php.net/manual/en/function.syslog.php
32 class SyslogLogger extends AbstractLogger
34 const IDENT = 'Friendica';
37 * Translates LogLevel log levels to syslog log priorities.
40 private $logLevels = [
41 LogLevel::DEBUG => LOG_DEBUG,
42 LogLevel::INFO => LOG_INFO,
43 LogLevel::NOTICE => LOG_NOTICE,
44 LogLevel::WARNING => LOG_WARNING,
45 LogLevel::ERROR => LOG_ERR,
46 LogLevel::CRITICAL => LOG_CRIT,
47 LogLevel::ALERT => LOG_ALERT,
48 LogLevel::EMERGENCY => LOG_EMERG,
52 * Translates log priorities to string outputs
55 private $logToString = [
58 LOG_NOTICE => 'NOTICE',
59 LOG_WARNING => 'WARNING',
61 LOG_CRIT => 'CRITICAL',
63 LOG_EMERG => 'EMERGENCY'
67 * Indicates what logging options will be used when generating a log message
68 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
75 * Used to specify what type of program is logging the message
76 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
83 * The minimum loglevel at which this logger will be triggered
89 * A error message of the current operation
92 private $errorMessage;
96 * @param string $level The minimum loglevel at which this logger will be triggered
97 * @param int $logOpts Indicates what logging options will be used when generating a log message
98 * @param int $logFacility Used to specify what type of program is logging the message
102 public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
104 parent::__construct($channel, $introspection);
105 $this->logOpts = $logOpts;
106 $this->logFacility = $logFacility;
107 $this->logLevel = $this->mapLevelToPriority($level);
108 $this->introspection->addClasses(array(self::class));
112 * Adds a new entry to the syslog
115 * @param string $message
116 * @param array $context
118 * @throws InternalServerErrorException if the syslog isn't available
120 protected function addEntry($level, $message, $context = [])
122 $logLevel = $this->mapLevelToPriority($level);
124 if ($logLevel > $this->logLevel) {
128 $formattedLog = $this->formatLog($logLevel, $message, $context);
129 $this->write($logLevel, $formattedLog);
133 * Maps the LogLevel (@see LogLevel) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters)
135 * @param string $level A LogLevel
137 * @return int The SysLog priority
139 * @throws \Psr\Log\InvalidArgumentException If the loglevel isn't valid
141 public function mapLevelToPriority($level)
143 if (!array_key_exists($level, $this->logLevels)) {
144 throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
147 return $this->logLevels[$level];
153 public function close()
159 * Writes a message to the syslog
160 * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
162 * @param int $priority The Priority
163 * @param string $message The message of the log
165 * @throws InternalServerErrorException if syslog cannot be used
167 private function write($priority, $message)
169 set_error_handler([$this, 'customErrorHandler']);
170 $opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
171 restore_error_handler();
174 throw new \UnexpectedValueException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
177 $this->syslogWrapper($priority, $message);
181 * Formats a log record for the syslog output
183 * @param int $level The loglevel/priority
184 * @param string $message The message
185 * @param array $context The context of this call
187 * @return string the formatted syslog output
189 private function formatLog($level, $message, $context = [])
191 $record = $this->introspection->getRecord();
192 $record = array_merge($record, ['uid' => $this->logUid]);
195 $logMessage .= $this->channel . ' ';
196 $logMessage .= '[' . $this->logToString[$level] . ']: ';
197 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
198 $logMessage .= $this->jsonEncodeArray($context) . ' - ';
199 $logMessage .= $this->jsonEncodeArray($record);
204 private function customErrorHandler($code, $msg)
206 $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
210 * A syslog wrapper to make syslog functionality testable
212 * @param int $level The syslog priority
213 * @param string $entry The message to send to the syslog function
215 protected function syslogWrapper($level, $entry)
217 set_error_handler([$this, 'customErrorHandler']);
218 $written = syslog($level, $entry);
219 restore_error_handler();
222 throw new \UnexpectedValueException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));