3 * @copyright Copyright (C) 2010-2023, 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\Capabilities\IHaveCallIntrospections;
25 use Friendica\Core\Logger\Exception\LoggerException;
26 use Friendica\Core\Logger\Exception\LogLevelException;
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 public const 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 protected const 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;
103 * @param string $logLevel The minimum loglevel at which this logger will be triggered
104 * @param string $logOptions
105 * @param string $logFacility
107 public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility)
109 parent::__construct($channel, $introspection);
111 $this->logOpts = $logOptions;
112 $this->logFacility = $logFacility;
113 $this->logLevel = $logLevel;
117 * Adds a new entry to the syslog
119 * @param mixed $level
120 * @param string $message
121 * @param array $context
123 * @throws LogLevelException in case the level isn't valid
124 * @throws LoggerException In case the syslog cannot be opened for writing
126 protected function addEntry($level, string $message, array $context = [])
128 $logLevel = $this->mapLevelToPriority($level);
130 if ($logLevel > $this->logLevel) {
134 $formattedLog = $this->formatLog($logLevel, $message, $context);
135 $this->write($logLevel, $formattedLog);
139 * Maps the LogLevel (@see LogLevel) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters)
141 * @param string $level A LogLevel
143 * @return int The SysLog priority
145 * @throws LogLevelException If the loglevel isn't valid
147 public function mapLevelToPriority(string $level): int
149 if (!array_key_exists($level, static::logLevels)) {
150 throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
153 return static::logLevels[$level];
159 public function close()
165 * Writes a message to the syslog
167 * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
169 * @param int $priority The Priority
170 * @param string $message The message of the log
172 * @throws LoggerException In case the syslog cannot be opened/written
174 private function write(int $priority, string $message)
176 set_error_handler([$this, 'customErrorHandler']);
177 $opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
178 restore_error_handler();
181 throw new LoggerException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
184 $this->syslogWrapper($priority, $message);
188 * Formats a log record for the syslog output
190 * @param int $level The loglevel/priority
191 * @param string $message The message
192 * @param array $context The context of this call
194 * @return string the formatted syslog output
196 private function formatLog(int $level, string $message, array $context = []): string
198 $record = $this->introspection->getRecord();
199 $record = array_merge($record, ['uid' => $this->logUid]);
201 $logMessage = $this->channel . ' ';
202 $logMessage .= '[' . static::logToString[$level] . ']: ';
203 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
204 $logMessage .= $this->jsonEncodeArray($context) . ' - ';
205 $logMessage .= $this->jsonEncodeArray($record);
210 private function customErrorHandler($code, $msg)
212 $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
216 * A syslog wrapper to make syslog functionality testable
218 * @param int $level The syslog priority
219 * @param string $entry The message to send to the syslog function
221 * @throws LoggerException
223 protected function syslogWrapper(int $level, string $entry)
225 set_error_handler([$this, 'customErrorHandler']);
226 $written = syslog($level, $entry);
227 restore_error_handler();
230 throw new LoggerException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));