]> git.mxchange.org Git - friendica.git/blob - src/Core/Logger/Type/SyslogLogger.php
Merge branch '2023.03-rc' into stable
[friendica.git] / src / Core / Logger / Type / SyslogLogger.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
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.
11  *
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.
16  *
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/>.
19  *
20  */
21
22 namespace Friendica\Core\Logger\Type;
23
24 use Friendica\Core\Config\Capability\IManageConfigValues;
25 use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
26 use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections;
27 use Friendica\Core\Logger\Exception\LoggerException;
28 use Friendica\Core\Logger\Exception\LogLevelException;
29 use Psr\Log\LogLevel;
30
31 /**
32  * A Logger instance for syslogging (fast, but simple)
33  * @see http://php.net/manual/en/function.syslog.php
34  */
35 class SyslogLogger extends AbstractLogger implements IAmAStrategy
36 {
37         const IDENT = 'Friendica';
38
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;
43
44         /**
45          * Translates LogLevel log levels to syslog log priorities.
46          * @var array
47          */
48         private $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,
57         ];
58
59         /**
60          * Translates log priorities to string outputs
61          * @var array
62          */
63         private $logToString = [
64                 LOG_DEBUG   => 'DEBUG',
65                 LOG_INFO    => 'INFO',
66                 LOG_NOTICE  => 'NOTICE',
67                 LOG_WARNING => 'WARNING',
68                 LOG_ERR     => 'ERROR',
69                 LOG_CRIT    => 'CRITICAL',
70                 LOG_ALERT   => 'ALERT',
71                 LOG_EMERG   => 'EMERGENCY'
72         ];
73
74         /**
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
77          *
78          * @var int
79          */
80         private $logOpts;
81
82         /**
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
85          *
86          * @var int
87          */
88         private $logFacility;
89
90         /**
91          * The minimum loglevel at which this logger will be triggered
92          * @var int
93          */
94         private $logLevel;
95
96         /**
97          * A error message of the current operation
98          * @var string
99          */
100         private $errorMessage;
101
102         /**
103          * {@inheritdoc}
104          * @param string $level       The minimum loglevel at which this logger will be triggered
105          *
106          * @throws LogLevelException
107          * @throws LoggerException
108          */
109         public function __construct(string $channel, IManageConfigValues $config, IHaveCallIntrospections $introspection, string $level = LogLevel::NOTICE)
110         {
111                 parent::__construct($channel, $introspection);
112
113                 $this->logOpts     = $config->get('system', 'syslog_flags') ?? static::DEFAULT_FLAGS;
114                 $this->logFacility = $config->get('system', 'syslog_facility') ?? static::DEFAULT_FACILITY;
115                 $this->logLevel    = $this->mapLevelToPriority($level);
116                 $this->introspection->addClasses([self::class]);
117         }
118
119         /**
120          * Adds a new entry to the syslog
121          *
122          * @param mixed  $level
123          * @param string $message
124          * @param array  $context
125          *
126          * @throws LogLevelException in case the level isn't valid
127          * @throws LoggerException In case the syslog cannot be opened for writing
128          */
129         protected function addEntry($level, string $message, array $context = [])
130         {
131                 $logLevel = $this->mapLevelToPriority($level);
132
133                 if ($logLevel > $this->logLevel) {
134                         return;
135                 }
136
137                 $formattedLog = $this->formatLog($logLevel, $message, $context);
138                 $this->write($logLevel, $formattedLog);
139         }
140
141         /**
142          * Maps the LogLevel (@see LogLevel) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters)
143          *
144          * @param string $level A LogLevel
145          *
146          * @return int The SysLog priority
147          *
148          * @throws LogLevelException If the loglevel isn't valid
149          */
150         public function mapLevelToPriority(string $level): int
151         {
152                 if (!array_key_exists($level, $this->logLevels)) {
153                         throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
154                 }
155
156                 return $this->logLevels[$level];
157         }
158
159         /**
160          * Closes the Syslog
161          */
162         public function close()
163         {
164                 closelog();
165         }
166
167         /**
168          * Writes a message to the syslog
169          *
170          * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
171          *
172          * @param int    $priority The Priority
173          * @param string $message  The message of the log
174          *
175          * @throws LoggerException In case the syslog cannot be opened/written
176          */
177         private function write(int $priority, string $message)
178         {
179                 set_error_handler([$this, 'customErrorHandler']);
180                 $opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
181                 restore_error_handler();
182
183                 if (!$opened) {
184                         throw new LoggerException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
185                 }
186
187                 $this->syslogWrapper($priority, $message);
188         }
189
190         /**
191          * Formats a log record for the syslog output
192          *
193          * @param int    $level   The loglevel/priority
194          * @param string $message The message
195          * @param array  $context The context of this call
196          *
197          * @return string the formatted syslog output
198          */
199         private function formatLog(int $level, string $message, array $context = []): string
200         {
201                 $record = $this->introspection->getRecord();
202                 $record = array_merge($record, ['uid' => $this->logUid]);
203
204                 $logMessage = $this->channel . ' ';
205                 $logMessage .= '[' . $this->logToString[$level] . ']: ';
206                 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
207                 $logMessage .= $this->jsonEncodeArray($context) . ' - ';
208                 $logMessage .= $this->jsonEncodeArray($record);
209
210                 return $logMessage;
211         }
212
213         private function customErrorHandler($code, $msg)
214         {
215                 $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
216         }
217
218         /**
219          * A syslog wrapper to make syslog functionality testable
220          *
221          * @param int    $level The syslog priority
222          * @param string $entry The message to send to the syslog function
223          *
224          * @throws LoggerException
225          */
226         protected function syslogWrapper(int $level, string $entry)
227         {
228                 set_error_handler([$this, 'customErrorHandler']);
229                 $written = syslog($level, $entry);
230                 restore_error_handler();
231
232                 if (!$written) {
233                         throw new LoggerException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
234                 }
235         }
236 }