3 * @copyright Copyright (C) 2010-2022, 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\Core\Logger\Type;
24 use Friendica\Core\Logger\Exception\LoggerException;
25 use Friendica\Core\Logger\Exception\LogLevelException;
26 use Friendica\Core\Logger\Util\Introspection;
30 * A Logger instance for syslogging (fast, but simple)
31 * @see http://php.net/manual/en/function.syslog.php
33 class SyslogLogger extends AbstractLogger
35 const IDENT = 'Friendica';
37 /** @var int The default syslog flags */
38 const DEFAULT_FLAGS = LOG_PID | LOG_ODELAY | LOG_CONS;
39 /** @var int The default syslog facility */
40 const DEFAULT_FACILITY = LOG_USER;
43 * Translates LogLevel log levels to syslog log priorities.
46 private $logLevels = [
47 LogLevel::DEBUG => LOG_DEBUG,
48 LogLevel::INFO => LOG_INFO,
49 LogLevel::NOTICE => LOG_NOTICE,
50 LogLevel::WARNING => LOG_WARNING,
51 LogLevel::ERROR => LOG_ERR,
52 LogLevel::CRITICAL => LOG_CRIT,
53 LogLevel::ALERT => LOG_ALERT,
54 LogLevel::EMERGENCY => LOG_EMERG,
58 * Translates log priorities to string outputs
61 private $logToString = [
64 LOG_NOTICE => 'NOTICE',
65 LOG_WARNING => 'WARNING',
67 LOG_CRIT => 'CRITICAL',
69 LOG_EMERG => 'EMERGENCY'
73 * Indicates what logging options will be used when generating a log message
74 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
81 * Used to specify what type of program is logging the message
82 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
89 * The minimum loglevel at which this logger will be triggered
95 * A error message of the current operation
98 private $errorMessage;
102 * @param string $level The minimum loglevel at which this logger will be triggered
103 * @param int $logOpts Indicates what logging options will be used when generating a log message
104 * @param int $logFacility Used to specify what type of program is logging the message
106 * @throws LogLevelException
107 * @throws LoggerException
109 public function __construct($channel, Introspection $introspection, string $level = LogLevel::NOTICE, int $logOpts = self::DEFAULT_FLAGS, int $logFacility = self::DEFAULT_FACILITY )
111 parent::__construct($channel, $introspection);
112 $this->logOpts = $logOpts;
113 $this->logFacility = $logFacility;
114 $this->logLevel = $this->mapLevelToPriority($level);
115 $this->introspection->addClasses([self::class]);
119 * Adds a new entry to the syslog
121 * @param mixed $level
122 * @param string $message
123 * @param array $context
125 * @throws LogLevelException in case the level isn't valid
126 * @throws LoggerException In case the syslog cannot be opened for writing
128 protected function addEntry($level, string $message, array $context = [])
130 $logLevel = $this->mapLevelToPriority($level);
132 if ($logLevel > $this->logLevel) {
136 $formattedLog = $this->formatLog($logLevel, $message, $context);
137 $this->write($logLevel, $formattedLog);
141 * Maps the LogLevel (@see LogLevel) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters)
143 * @param string $level A LogLevel
145 * @return int The SysLog priority
147 * @throws LogLevelException If the loglevel isn't valid
149 public function mapLevelToPriority(string $level): int
151 if (!array_key_exists($level, $this->logLevels)) {
152 throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
155 return $this->logLevels[$level];
161 public function close()
167 * Writes a message to the syslog
169 * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
171 * @param int $priority The Priority
172 * @param string $message The message of the log
174 * @throws LoggerException In case the syslog cannot be opened/written
176 private function write(int $priority, string $message)
178 set_error_handler([$this, 'customErrorHandler']);
179 $opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
180 restore_error_handler();
183 throw new LoggerException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
186 $this->syslogWrapper($priority, $message);
190 * Formats a log record for the syslog output
192 * @param int $level The loglevel/priority
193 * @param string $message The message
194 * @param array $context The context of this call
196 * @return string the formatted syslog output
198 private function formatLog(int $level, string $message, array $context = []): string
200 $record = $this->introspection->getRecord();
201 $record = array_merge($record, ['uid' => $this->logUid]);
203 $logMessage = $this->channel . ' ';
204 $logMessage .= '[' . $this->logToString[$level] . ']: ';
205 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
206 $logMessage .= $this->jsonEncodeArray($context) . ' - ';
207 $logMessage .= $this->jsonEncodeArray($record);
212 private function customErrorHandler($code, $msg)
214 $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
218 * A syslog wrapper to make syslog functionality testable
220 * @param int $level The syslog priority
221 * @param string $entry The message to send to the syslog function
223 * @throws LoggerException
225 protected function syslogWrapper(int $level, string $entry)
227 set_error_handler([$this, 'customErrorHandler']);
228 $written = syslog($level, $entry);
229 restore_error_handler();
232 throw new LoggerException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));