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