3 * @copyright Copyright (C) 2010-2021, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Core\Logger\Factory;
24 use Friendica\Core\Config\Capability\IManageConfigValues;
25 use Friendica\Core\Logger\Exception\LoggerException;
27 use Friendica\Core\Logger\Exception\LogLevelException;
28 use Friendica\Database\Database;
29 use Friendica\Util\FileSystem;
30 use Friendica\Core\Logger\Util\Introspection;
31 use Friendica\Core\Logger\Type\Monolog\DevelopHandler;
32 use Friendica\Core\Logger\Type\Monolog\IntrospectionProcessor;
33 use Friendica\Core\Logger\Type\ProfilerLogger;
34 use Friendica\Core\Logger\Type\StreamLogger;
35 use Friendica\Core\Logger\Type\SyslogLogger;
36 use Friendica\Util\Profiler;
38 use Psr\Log\LoggerInterface;
40 use Psr\Log\NullLogger;
47 const DEV_CHANNEL = 'dev';
50 * A list of classes, which shouldn't get logged
54 private static $ignoreClassList = [
57 'Friendica\\Core\\Logger\\Type',
60 /** @var string The log-channel (app, worker, ...) */
63 public function __construct(string $channel)
65 $this->channel = $channel;
69 * Creates a new PSR-3 compliant logger instances
71 * @param Database $database The Friendica Database instance
72 * @param IManageConfigValues $config The config
73 * @param Profiler $profiler The profiler of the app
74 * @param FileSystem $fileSystem FileSystem utils
75 * @param string|null $minLevel (optional) Override minimum Loglevel to log
77 * @return LoggerInterface The PSR-3 compliant logger instance
79 public function create(Database $database, IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem, ?string $minLevel = null): LoggerInterface
81 if (empty($config->get('system', 'debugging', false))) {
82 $logger = new NullLogger();
83 $database->setLogger($logger);
87 $introspection = new Introspection(self::$ignoreClassList);
88 $minLevel = $minLevel ?? $config->get('system', 'loglevel');
89 $loglevel = self::mapLegacyConfigDebugLevel((string)$minLevel);
91 switch ($config->get('system', 'logger_config', 'stream')) {
93 $loggerTimeZone = new \DateTimeZone('UTC');
94 Monolog\Logger::setTimezone($loggerTimeZone);
96 $logger = new Monolog\Logger($this->channel);
97 $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
98 $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
99 $logger->pushProcessor(new Monolog\Processor\UidProcessor());
100 $logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
102 $stream = $config->get('system', 'logfile');
104 // just add a stream in case it's either writable or not file
105 if (!is_file($stream) || is_writable($stream)) {
107 static::addStreamHandler($logger, $stream, $loglevel);
108 } catch (\Throwable $e) {
111 $logger = new SyslogLogger($this->channel, $introspection, $loglevel);
112 } catch (\Throwable $e) {
114 $logger = new NullLogger();
122 $logger = new SyslogLogger($this->channel, $introspection, $loglevel, $config->get('system', 'syslog_flags', SyslogLogger::DEFAULT_FLAGS), $config->get('system', 'syslog_facility', SyslogLogger::DEFAULT_FACILITY));
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, LogLevel::NOTICE);
126 $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
127 } catch (\Throwable $e) {
129 $logger = new NullLogger();
135 $stream = $config->get('system', 'logfile');
136 // just add a stream in case it's either writable or not file
137 if (!is_file($stream) || is_writable($stream)) {
139 $logger = new StreamLogger($this->channel, $stream, $introspection, $fileSystem, $loglevel);
140 } catch (LogLevelException $exception) {
141 // If there's a wrong config value for loglevel, try again with standard
142 $logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE);
143 $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
144 } catch (\Throwable $t) {
146 $logger = new NullLogger();
150 $logger = new SyslogLogger($this->channel, $introspection, $loglevel);
151 } catch (LogLevelException $exception) {
152 // If there's a wrong config value for loglevel, try again with standard
153 $logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE);
154 $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
155 } catch (\Throwable $e) {
157 $logger = new NullLogger();
163 $profiling = $config->get('system', 'profiling', false);
165 // In case profiling is enabled, wrap the ProfilerLogger around the current logger
166 if (isset($profiling) && $profiling !== false) {
167 $logger = new ProfilerLogger($logger, $profiler);
170 $database->setLogger($logger);
175 * Creates a new PSR-3 compliant develop logger
177 * If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
178 * you'll use this logger instance for the duration of your work.
180 * It should never get filled during normal usage of Friendica
182 * @param IManageConfigValues $config The config
183 * @param Profiler $profiler The profiler of the app
184 * @param FileSystem $fileSystem FileSystem utils
186 * @return LoggerInterface The PSR-3 compliant logger instance
189 public static function createDev(IManageConfigValues $config, Profiler $profiler, FileSystem $fileSystem)
191 $debugging = $config->get('system', 'debugging');
192 $stream = $config->get('system', 'dlogfile');
193 $developerIp = $config->get('system', 'dlogip');
195 if ((!isset($developerIp) || !$debugging) &&
196 (!is_file($stream) || is_writable($stream))) {
197 return new NullLogger();
200 $loggerTimeZone = new \DateTimeZone('UTC');
201 Monolog\Logger::setTimezone($loggerTimeZone);
203 $introspection = new Introspection(self::$ignoreClassList);
205 switch ($config->get('system', 'logger_config', 'stream')) {
208 $loggerTimeZone = new \DateTimeZone('UTC');
209 Monolog\Logger::setTimezone($loggerTimeZone);
211 $logger = new Monolog\Logger(self::DEV_CHANNEL);
212 $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
213 $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
214 $logger->pushProcessor(new Monolog\Processor\UidProcessor());
215 $logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
217 $logger->pushHandler(new DevelopHandler($developerIp));
219 static::addStreamHandler($logger, $stream, LogLevel::DEBUG);
223 $logger = new SyslogLogger(self::DEV_CHANNEL, $introspection, LogLevel::DEBUG);
228 $logger = new StreamLogger(self::DEV_CHANNEL, $stream, $introspection, $fileSystem, LogLevel::DEBUG);
232 $profiling = $config->get('system', 'profiling', false);
234 // In case profiling is enabled, wrap the ProfilerLogger around the current logger
235 if (isset($profiling) && $profiling !== false) {
236 $logger = new ProfilerLogger($logger, $profiler);
243 * Mapping a legacy level to the PSR-3 compliant levels
245 * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
247 * @param string $level the level to be mapped
249 * @return string the PSR-3 compliant level
251 private static function mapLegacyConfigDebugLevel(string $level): string
256 return LogLevel::ERROR;
259 return LogLevel::WARNING;
262 return LogLevel::NOTICE;
265 return LogLevel::INFO;
270 return LogLevel::DEBUG;
271 // default if nothing set
278 * Adding a handler to a given logger instance
280 * @param LoggerInterface $logger The logger instance
281 * @param mixed $stream The stream which handles the logger output
282 * @param string $level The level, for which this handler at least should handle logging
286 * @throws LoggerException
288 public static function addStreamHandler(LoggerInterface $logger, $stream, string $level = LogLevel::NOTICE)
290 if ($logger instanceof Monolog\Logger) {
291 $loglevel = Monolog\Logger::toMonologLevel($level);
293 // fallback to notice if an invalid loglevel is set
294 if (!is_int($loglevel)) {
295 $loglevel = LogLevel::NOTICE;
299 $fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
301 $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
302 $fileHandler->setFormatter($formatter);
304 $logger->pushHandler($fileHandler);
305 } catch (\Exception $exception) {
306 throw new LoggerException('Cannot create Monolog Logger.', $exception);