]> git.mxchange.org Git - friendica.git/blob - src/Util/Logger/SyslogLogger.php
Adding UID and Level To String mapping
[friendica.git] / src / Util / Logger / SyslogLogger.php
1 <?php
2
3 namespace Friendica\Util\Logger;
4
5 use Friendica\Network\HTTPException\InternalServerErrorException;
6 use Friendica\Util\Strings;
7 use Psr\Log\InvalidArgumentException;
8 use Psr\Log\LoggerInterface;
9 use Psr\Log\LogLevel;
10
11 /**
12  * A Logger instance for syslogging (fast, but simple)
13  * @see http://php.net/manual/en/function.syslog.php
14  */
15 class SyslogLogger implements LoggerInterface
16 {
17         const IDENT = 'Friendica';
18
19         /**
20          * Translates LogLevel log levels to syslog log priorities.
21          * @var array
22          */
23         private $logLevels = [
24                 LogLevel::DEBUG     => LOG_DEBUG,
25                 LogLevel::INFO      => LOG_INFO,
26                 LogLevel::NOTICE    => LOG_NOTICE,
27                 LogLevel::WARNING   => LOG_WARNING,
28                 LogLevel::ERROR     => LOG_ERR,
29                 LogLevel::CRITICAL  => LOG_CRIT,
30                 LogLevel::ALERT     => LOG_ALERT,
31                 LogLevel::EMERGENCY => LOG_EMERG,
32         ];
33
34         /**
35          * Translates log priorities to string outputs
36          * @var array
37          */
38         private $logToString = [
39                 LOG_DEBUG   => 'DEBUG',
40                 LOG_INFO    => 'INFO',
41                 LOG_NOTICE  => 'NOTICE',
42                 LOG_WARNING => 'WARNING',
43                 LOG_ERR     => 'ERROR',
44                 LOG_CRIT    => 'CRITICAL',
45                 LOG_ALERT   => 'ALERT',
46                 LOG_EMERG   => 'EMERGENCY'
47         ];
48
49         /**
50          * The channel of the current process (added to each message)
51          * @var string
52          */
53         private $channel;
54
55         /**
56          * Indicates what logging options will be used when generating a log message
57          * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
58          *
59          * @var int
60          */
61         private $logOpts;
62
63         /**
64          * Used to specify what type of program is logging the message
65          * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
66          *
67          * @var int
68          */
69         private $logFacility;
70
71         /**
72          * The minimum loglevel at which this logger will be triggered
73          * @var int
74          */
75         private $logLevel;
76
77         /**
78          * The Introspector for the current call
79          * @var Introspection
80          */
81         private $introspection;
82
83         /**
84          * The UID of the current call
85          * @var string
86          */
87         private $logUid;
88
89         /**
90          * @param string $channel     The output channel
91          * @param string $level       The minimum loglevel at which this logger will be triggered
92          * @param int    $logOpts     Indicates what logging options will be used when generating a log message
93          * @param int    $logFacility Used to specify what type of program is logging the message
94          */
95         public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
96         {
97                 $this->logUid = Strings::getRandomHex(6);
98                 $this->channel = $channel;
99                 $this->logOpts = $logOpts;
100                 $this->logFacility = $logFacility;
101                 $this->logLevel = $this->mapLevelToPriority($level);
102                 $this->introspection = $introspection;
103         }
104
105         /**
106          * Maps the LogLevel (@see LogLevel ) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters )
107          *
108          * @param string $level A LogLevel
109          *
110          * @return int The SysLog priority
111          *
112          * @throws \Psr\Log\InvalidArgumentException If the loglevel isn't valid
113          */
114         public function mapLevelToPriority($level)
115         {
116                 if (!array_key_exists($level, $this->logLevels)) {
117                         throw new InvalidArgumentException('LogLevel \'' . $level . '\' isn\'t valid.');
118                 }
119
120                 return $this->logLevels[$level];
121         }
122
123         /**
124          * Writes a message to the syslog
125          * @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
126          *
127          * @param int    $priority The Priority
128          * @param string $message  The message of the log
129          *
130          * @throws InternalServerErrorException if syslog cannot be used
131          */
132         private function write($priority, $message)
133         {
134                 if (!openlog(self::IDENT, $this->logOpts, $this->logFacility)) {
135                         throw new InternalServerErrorException('Can\'t open syslog for ident "' . $this->channel . '" and facility "' . $this->logFacility . '""');
136                 }
137
138                 syslog($priority, $message);
139         }
140
141         /**
142          * Closes the Syslog
143          */
144         public function close()
145         {
146                 closelog();
147         }
148
149         /**
150          * Formats a log record for the syslog output
151          *
152          * @param int    $level   The loglevel/priority
153          * @param string $message The message
154          * @param array  $context The context of this call
155          *
156          * @return string the formatted syslog output
157          */
158         private function formatLog($level, $message, $context = [])
159         {
160                 $record = $this->introspection->getRecord();
161                 $record = array_merge($record, ['uid' => $this->logUid]);
162                 $logMessage = '';
163
164                 $logMessage .= $this->channel . ' ';
165                 $logMessage .= '[' . $this->logToString[$level] . ']: ';
166                 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
167                 $logMessage .= @json_encode($context) . ' - ';
168                 $logMessage .= @json_encode($record);
169
170                 return $logMessage;
171         }
172
173         /**
174          * Simple interpolation of PSR-3 compliant replacements ( variables between '{' and '}' )
175          * @see https://www.php-fig.org/psr/psr-3/#12-message
176          *
177          * @param string $message
178          * @param array  $context
179          *
180          * @return string the interpolated message
181          */
182         private function psrInterpolate($message, array $context = array())
183         {
184                 $replace = [];
185                 foreach ($context as $key => $value) {
186                         // check that the value can be casted to string
187                         if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
188                                 $replace['{' . $key . '}'] = $value;
189                         } elseif (is_array($value)) {
190                                 $replace['{' . $key . '}'] = @json_encode($value);
191                         }
192                 }
193
194                 return strtr($message, $replace);
195         }
196
197         /**
198          * Adds a new entry to the syslog
199          *
200          * @param int    $level
201          * @param string $message
202          * @param array  $context
203          *
204          * @throws InternalServerErrorException if the syslog isn't available
205          */
206         private function addEntry($level, $message, $context = [])
207         {
208                 if ($level >= $this->logLevel) {
209                         return;
210                 }
211
212                 $formattedLog = $this->formatLog($level, $message, $context);
213                 $this->write($level, $formattedLog);
214         }
215
216         /**
217          * {@inheritdoc}
218          * @throws InternalServerErrorException if the syslog isn't available
219          */
220         public function emergency($message, array $context = array())
221         {
222                 $this->addEntry(LOG_EMERG, $message, $context);
223         }
224
225         /**
226          * {@inheritdoc}
227          * @throws InternalServerErrorException if the syslog isn't available
228          */
229         public function alert($message, array $context = array())
230         {
231                 $this->addEntry(LOG_ALERT, $message, $context);
232         }
233
234         /**
235          * {@inheritdoc}
236          * @throws InternalServerErrorException if the syslog isn't available
237          */
238         public function critical($message, array $context = array())
239         {
240                 $this->addEntry(LOG_CRIT, $message, $context);
241         }
242
243         /**
244          * {@inheritdoc}
245          * @throws InternalServerErrorException if the syslog isn't available
246          */
247         public function error($message, array $context = array())
248         {
249                 $this->addEntry(LOG_ERR, $message, $context);
250         }
251
252         /**
253          * {@inheritdoc}
254          * @throws InternalServerErrorException if the syslog isn't available
255          */
256         public function warning($message, array $context = array())
257         {
258                 $this->addEntry(LOG_WARNING, $message, $context);
259         }
260
261         /**
262          * {@inheritdoc}
263          * @throws InternalServerErrorException if the syslog isn't available
264          */
265         public function notice($message, array $context = array())
266         {
267                 $this->addEntry(LOG_NOTICE, $message, $context);
268         }
269
270         /**
271          * {@inheritdoc}
272          * @throws InternalServerErrorException if the syslog isn't available
273          */
274         public function info($message, array $context = array())
275         {
276                 $this->addEntry(LOG_INFO, $message, $context);
277         }
278
279         /**
280          * {@inheritdoc}
281          * @throws InternalServerErrorException if the syslog isn't available
282          */
283         public function debug($message, array $context = array())
284         {
285                 $this->addEntry(LOG_DEBUG, $message, $context);
286         }
287
288         /**
289          * {@inheritdoc}
290          * @throws InternalServerErrorException if the syslog isn't available
291          */
292         public function log($level, $message, array $context = array())
293         {
294                 $logLevel = $this->mapLevelToPriority($level);
295                 $this->addEntry($logLevel, $message, $context);
296         }
297 }