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\Capability\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 NAME = 'syslog';
37 const IDENT = 'Friendica';
39 /** @var int The default syslog flags */
40 const DEFAULT_FLAGS = LOG_PID | LOG_ODELAY | LOG_CONS;
41 /** @var int The default syslog facility */
42 const DEFAULT_FACILITY = LOG_USER;
45 * Translates LogLevel log levels to syslog log priorities.
48 public const logLevels = [
49 LogLevel::DEBUG => LOG_DEBUG,
50 LogLevel::INFO => LOG_INFO,
51 LogLevel::NOTICE => LOG_NOTICE,
52 LogLevel::WARNING => LOG_WARNING,
53 LogLevel::ERROR => LOG_ERR,
54 LogLevel::CRITICAL => LOG_CRIT,
55 LogLevel::ALERT => LOG_ALERT,
56 LogLevel::EMERGENCY => LOG_EMERG,
60 * Translates log priorities to string outputs
63 protected const logToString = [
66 LOG_NOTICE => 'NOTICE',
67 LOG_WARNING => 'WARNING',
69 LOG_CRIT => 'CRITICAL',
71 LOG_EMERG => 'EMERGENCY'
75 * Indicates what logging options will be used when generating a log message
76 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
83 * Used to specify what type of program is logging the message
84 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
91 * The minimum loglevel at which this logger will be triggered
97 * A error message of the current operation
100 private $errorMessage;
105 * @param string $logLevel The minimum loglevel at which this logger will be triggered
106 * @param string $logOptions
107 * @param string $logFacility
109 public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility)
111 parent::__construct($channel, $introspection);
113 $this->logOpts = $logOptions;
114 $this->logFacility = $logFacility;
115 $this->logLevel = $logLevel;
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, static::logLevels)) {
152 throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
155 return static::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 .= '[' . static::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));