]> git.mxchange.org Git - friendica.git/blob - src/Core/Logger/Type/StreamLogger.php
Restructure Logger to new paradigm
[friendica.git] / src / Core / Logger / Type / StreamLogger.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, 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\Exception\LoggerArgumentException;
25 use Friendica\Core\Logger\Exception\LoggerException;
26 use Friendica\Util\DateTimeFormat;
27 use Friendica\Util\FileSystem;
28 use Friendica\Util\Introspection;
29 use Psr\Log\LogLevel;
30
31 /**
32  * A Logger instance for logging into a stream (file, stdout, stderr)
33  */
34 class StreamLogger extends AbstractLogger
35 {
36         /**
37          * The minimum loglevel at which this logger will be triggered
38          * @var string
39          */
40         private $logLevel;
41
42         /**
43          * The file URL of the stream (if needed)
44          * @var string
45          */
46         private $url;
47
48         /**
49          * The stream, where the current logger is writing into
50          * @var resource
51          */
52         private $stream;
53
54         /**
55          * The current process ID
56          * @var int
57          */
58         private $pid;
59
60         /**
61          * @var FileSystem
62          */
63         private $fileSystem;
64
65         /**
66          * Translates LogLevel log levels to integer values
67          * @var array
68          */
69         private $levelToInt = [
70                 LogLevel::EMERGENCY => 0,
71                 LogLevel::ALERT     => 1,
72                 LogLevel::CRITICAL  => 2,
73                 LogLevel::ERROR     => 3,
74                 LogLevel::WARNING   => 4,
75                 LogLevel::NOTICE    => 5,
76                 LogLevel::INFO      => 6,
77                 LogLevel::DEBUG     => 7,
78         ];
79
80         /**
81          * {@inheritdoc}
82          * @param string|resource $stream The stream to write with this logger (either a file or a stream, i.e. stdout)
83          * @param string          $level  The minimum loglevel at which this logger will be triggered
84          *
85          * @throws LoggerArgumentException
86          */
87         public function __construct($channel, $stream, Introspection $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG)
88         {
89                 $this->fileSystem = $fileSystem;
90
91                 parent::__construct($channel, $introspection);
92
93                 if (is_resource($stream)) {
94                         $this->stream = $stream;
95                 } elseif (is_string($stream)) {
96                         $this->url = $stream;
97                 } else {
98                         throw new LoggerArgumentException('A stream must either be a resource or a string.');
99                 }
100
101                 $this->pid = getmypid();
102                 if (array_key_exists($level, $this->levelToInt)) {
103                         $this->logLevel = $this->levelToInt[$level];
104                 } else {
105                         throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level));
106                 }
107
108                 $this->checkStream();
109         }
110
111         public function close()
112         {
113                 if ($this->url && is_resource($this->stream)) {
114                         fclose($this->stream);
115                 }
116
117                 $this->stream = null;
118         }
119
120         /**
121          * Adds a new entry to the log
122          *
123          * @param mixed  $level
124          * @param string $message
125          * @param array  $context
126          *
127          * @return void
128          *
129          * @throws LoggerException
130          * @throws LoggerArgumentException
131          */
132         protected function addEntry($level, string $message, array $context = [])
133         {
134                 if (!array_key_exists($level, $this->levelToInt)) {
135                         throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level));
136                 }
137
138                 $logLevel = $this->levelToInt[$level];
139
140                 if ($logLevel > $this->logLevel) {
141                         return;
142                 }
143
144                 $this->checkStream();
145
146                 $formattedLog = $this->formatLog($level, $message, $context);
147                 fwrite($this->stream, $formattedLog);
148         }
149
150         /**
151          * Formats a log record for the syslog output
152          *
153          * @param mixed  $level   The loglevel/priority
154          * @param string $message The message
155          * @param array  $context The context of this call
156          *
157          * @return string the formatted syslog output
158          *
159          * @throws LoggerException
160          */
161         private function formatLog($level, string $message, array $context = []): string
162         {
163                 $record = $this->introspection->getRecord();
164                 $record = array_merge($record, ['uid' => $this->logUid, 'process_id' => $this->pid]);
165
166                 try {
167                         $logMessage = DateTimeFormat::utcNow(DateTimeFormat::ATOM) . ' ';
168                 } catch (\Exception $exception) {
169                         throw new LoggerException('Cannot get current datetime.', $exception);
170                 }
171                 $logMessage .= $this->channel . ' ';
172                 $logMessage .= '[' . strtoupper($level) . ']: ';
173                 $logMessage .= $this->psrInterpolate($message, $context) . ' ';
174                 $logMessage .= $this->jsonEncodeArray($context) . ' - ';
175                 $logMessage .= $this->jsonEncodeArray($record);
176                 $logMessage .= PHP_EOL;
177
178                 return $logMessage;
179         }
180
181         /**
182          * Checks the current stream
183          *
184          * @throws LoggerException
185          * @throws LoggerArgumentException
186          */
187         private function checkStream()
188         {
189                 if (is_resource($this->stream)) {
190                         return;
191                 }
192
193                 if (empty($this->url)) {
194                         throw new LoggerArgumentException('Missing stream URL.');
195                 }
196
197                 try {
198                         $this->stream = $this->fileSystem->createStream($this->url);
199                 } catch (\UnexpectedValueException $exception) {
200                         throw new LoggerException('Cannot create stream.', $exception);
201                 }
202         }
203 }