]> git.mxchange.org Git - friendica.git/blob - src/Core/Logger/Factory/Logger.php
Adapt Logger\Introspection
[friendica.git] / src / Core / Logger / Factory / Logger.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2022, 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\Factory;
23
24 use Friendica\Core\Config\Capability\IManageConfigValues;
25 use Friendica\Core;
26 use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections;
27 use Friendica\Core\Logger\Exception\LogLevelException;
28 use Friendica\Database\Database;
29 use Friendica\Network\HTTPException\InternalServerErrorException;
30 use Friendica\Util\FileSystem;
31 use Friendica\Core\Logger\Type\ProfilerLogger;
32 use Friendica\Core\Logger\Type\StreamLogger;
33 use Friendica\Core\Logger\Type\SyslogLogger;
34 use Friendica\Util\Profiler;
35 use Psr\Log\LoggerInterface;
36 use Psr\Log\LogLevel;
37 use Psr\Log\NullLogger;
38
39 /**
40  * A logger factory
41  */
42 class Logger
43 {
44         const DEV_CHANNEL = 'dev';
45
46         /** @var string The log-channel (app, worker, ...) */
47         private $channel;
48
49         public function __construct(string $channel, bool $includeAddon = true)
50         {
51                 $this->channel = $channel;
52
53                 /// @fixme clean solution = Making Addon & Hook dynamic and load them inside the constructor, so there's no custom load logic necessary anymore
54                 if ($includeAddon) {
55                         Core\Addon::loadAddons();
56                         Core\Hook::loadHooks();
57                 }
58         }
59
60         /**
61          * Creates a new PSR-3 compliant logger instances
62          *
63          * @param Database            $database   The Friendica Database instance
64          * @param IManageConfigValues $config     The config
65          * @param Profiler            $profiler   The profiler of the app
66          * @param FileSystem          $fileSystem FileSystem utils
67          * @param string|null         $minLevel   (optional) Override minimum Loglevel to log
68          *
69          * @return LoggerInterface The PSR-3 compliant logger instance
70          */
71         public function create(Database $database, IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem, IHaveCallIntrospections $introspection, ?string $minLevel = null): LoggerInterface
72         {
73                 if (empty($config->get('system', 'debugging', false))) {
74                         $logger = new NullLogger();
75                         $database->setLogger($logger);
76                         return $logger;
77                 }
78
79                 $minLevel      = $minLevel ?? $config->get('system', 'loglevel');
80                 $loglevel      = self::mapLegacyConfigDebugLevel((string)$minLevel);
81
82                 $name = $config->get('system', 'logger_config', 'stream');
83
84                 switch ($name) {
85                         case 'syslog':
86                                 try {
87                                         $logger = new SyslogLogger($this->channel, $introspection, $loglevel, $config->get('system', 'syslog_flags', SyslogLogger::DEFAULT_FLAGS), $config->get('system', 'syslog_facility', SyslogLogger::DEFAULT_FACILITY));
88                                 } catch (LogLevelException $exception) {
89                                         // If there's a wrong config value for loglevel, try again with standard
90                                         $logger = $this->create($database, $config, $profiler, $fileSystem,  $introspection, LogLevel::NOTICE);
91                                         $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
92                                 } catch (\Throwable $e) {
93                                         // No logger ...
94                                         $logger = new NullLogger();
95                                 }
96                                 break;
97
98                         case 'stream':
99                         default:
100                                 $data = [
101                                         'name'          => $name,
102                                         'channel'       => $this->channel,
103                                         'introspection' => $introspection,
104                                         'loglevel'      => $loglevel,
105                                         'logger'        => null,
106                                 ];
107                                 try {
108                                         Core\Hook::callAll('logger_instance', $data);
109                                 } catch (InternalServerErrorException $exception) {
110                                         $data['logger'] = null;
111                                 }
112
113                                 if (($data['logger'] ?? null) instanceof LoggerInterface) {
114                                         $logger = $data['logger'];
115                                 }
116
117                                 if (empty($logger)) {
118                                         $stream = $config->get('system', 'logfile');
119                                         // just add a stream in case it's either writable or not file
120                                         if (!is_file($stream) || is_writable($stream)) {
121                                                 try {
122                                                         $logger = new StreamLogger($this->channel, $stream, $introspection, $fileSystem, $loglevel);
123                                                 } catch (LogLevelException $exception) {
124                                                         // If there's a wrong config value for loglevel, try again with standard
125                                                         $logger = $this->create($database, $config, $profiler, $fileSystem, $introspection, LogLevel::NOTICE);
126                                                         $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
127                                                 } catch (\Throwable $t) {
128                                                         // No logger ...
129                                                         $logger = new NullLogger();
130                                                 }
131                                         } else {
132                                                 try {
133                                                         $logger = new SyslogLogger($this->channel, $introspection, $loglevel);
134                                                 } catch (LogLevelException $exception) {
135                                                         // If there's a wrong config value for loglevel, try again with standard
136                                                         $logger = $this->create($database, $config, $profiler, $fileSystem, $introspection, LogLevel::NOTICE);
137                                                         $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
138                                                 } catch (\Throwable $e) {
139                                                         // No logger ...
140                                                         $logger = new NullLogger();
141                                                 }
142                                         }
143                                 }
144                                 break;
145                 }
146
147                 $profiling = $config->get('system', 'profiling', false);
148
149                 // In case profiling is enabled, wrap the ProfilerLogger around the current logger
150                 if (isset($profiling) && $profiling !== false) {
151                         $logger = new ProfilerLogger($logger, $profiler);
152                 }
153
154                 $database->setLogger($logger);
155                 return $logger;
156         }
157
158         /**
159          * Creates a new PSR-3 compliant develop logger
160          *
161          * If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
162          * you'll use this logger instance for the duration of your work.
163          *
164          * It should never get filled during normal usage of Friendica
165          *
166          * @param IManageConfigValues $config     The config
167          * @param Profiler            $profiler   The profiler of the app
168          * @param FileSystem          $fileSystem FileSystem utils
169          *
170          * @return LoggerInterface The PSR-3 compliant logger instance
171          * @throws \Exception
172          */
173         public static function createDev(IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem, IHaveCallIntrospections $introspection)
174         {
175                 $debugging   = $config->get('system', 'debugging');
176                 $stream      = $config->get('system', 'dlogfile');
177                 $developerIp = $config->get('system', 'dlogip');
178
179                 if ((!isset($developerIp) || !$debugging) &&
180                         (!is_file($stream) || is_writable($stream))) {
181                         return new NullLogger();
182                 }
183
184                 $name = $config->get('system', 'logger_config', 'stream');
185
186                 switch ($name) {
187
188                         case 'syslog':
189                                 $logger = new SyslogLogger(self::DEV_CHANNEL, $introspection, LogLevel::DEBUG);
190                                 break;
191
192                         case 'stream':
193                         default:
194                                 $data = [
195                                         'name'          => $name,
196                                         'channel'       => self::DEV_CHANNEL,
197                                         'introspection' => $introspection,
198                                         'loglevel'      => LogLevel::DEBUG,
199                                         'logger'        => null,
200                                 ];
201                                 try {
202                                         Core\Hook::callAll('logger_instance', $data);
203                                 } catch (InternalServerErrorException $exception) {
204                                         $data['logger'] = null;
205                                 }
206
207                                 if (($data['logger'] ?? null) instanceof LoggerInterface) {
208                                         return $data['logger'];
209                                 }
210
211                                 $logger = new StreamLogger(self::DEV_CHANNEL, $stream, $introspection, $fileSystem, LogLevel::DEBUG);
212                                 break;
213                 }
214
215                 $profiling = $config->get('system', 'profiling', false);
216
217                 // In case profiling is enabled, wrap the ProfilerLogger around the current logger
218                 if (isset($profiling) && $profiling !== false) {
219                         $logger = new ProfilerLogger($logger, $profiler);
220                 }
221
222                 return $logger;
223         }
224
225         /**
226          * Mapping a legacy level to the PSR-3 compliant levels
227          *
228          * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
229          *
230          * @param string $level the level to be mapped
231          *
232          * @return string the PSR-3 compliant level
233          */
234         private static function mapLegacyConfigDebugLevel(string $level): string
235         {
236                 switch ($level) {
237                         // legacy WARNING
238                         case "0":
239                                 return LogLevel::ERROR;
240                         // legacy INFO
241                         case "1":
242                                 return LogLevel::WARNING;
243                         // legacy TRACE
244                         case "2":
245                                 return LogLevel::NOTICE;
246                         // legacy DEBUG
247                         case "3":
248                                 return LogLevel::INFO;
249                         // legacy DATA
250                         case "4":
251                         // legacy ALL
252                         case "5":
253                                 return LogLevel::DEBUG;
254                         // default if nothing set
255                         default:
256                                 return $level;
257                 }
258         }
259 }