. * * @author Daniil Gentili * @copyright 2016-2020 Daniil Gentili * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 * * @link https://docs.madelineproto.xyz MadelineProto documentation */ namespace danog\MadelineProto; use Amp\ByteStream\ResourceOutputStream; use Amp\Failure; use function Amp\ByteStream\getStderr; use function Amp\ByteStream\getStdout; /** * Logger class. */ class Logger { const FOREGROUND = ['default' => 39, 'black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'light_gray' => 37, 'dark_gray' => 90, 'light_red' => 91, 'light_green' => 92, 'light_yellow' => 93, 'light_blue' => 94, 'light_magenta' => 95, 'light_cyan' => 96, 'white' => 97]; const BACKGROUND = ['default' => 49, 'black' => 40, 'red' => 41, 'magenta' => 45, 'yellow' => 43, 'green' => 42, 'blue' => 44, 'cyan' => 46, 'light_gray' => 47, 'dark_gray' => 100, 'light_red' => 101, 'light_green' => 102, 'light_yellow' => 103, 'light_blue' => 104, 'light_magenta' => 105, 'light_cyan' => 106, 'white' => 107]; const SET = ['bold' => 1, 'dim' => 2, 'underlined' => 3, 'blink' => 4, 'reverse' => 5, 'hidden' => 6]; const RESET = ['all' => 0, 'bold' => 21, 'dim' => 22, 'underlined' => 24, 'blink' => 25, 'reverse' => 26, 'hidden' => 28]; /** * Logging mode. * * @var integer */ public $mode = 0; /** * Optional logger parameter. * * @var mixed */ public $optional = null; /** * Logger prefix. * * @var string */ public $prefix = ''; /** * Logging level. * * @var integer */ public $level = self::NOTICE; /** * Logging colors. * * @var array */ public $colors = []; /** * Newline. * * @var string */ public $newline = "\n"; /** * Logfile. * * @var ResourceOutputStream */ public $stdout; /** * Default logger instance. * * @var self */ public static $default; /** * Whether the AGPL notice was printed. * * @var boolean */ public static $printed = false; const ULTRA_VERBOSE = 5; const VERBOSE = 4; const NOTICE = 3; const WARNING = 2; const ERROR = 1; const FATAL_ERROR = 0; const NO_LOGGER = 0; const DEFAULT_LOGGER = 1; const FILE_LOGGER = 2; const ECHO_LOGGER = 3; const CALLABLE_LOGGER = 4; /** * Construct global static logger from MadelineProto settings. * * @param array $settings Settings array * * @return void */ public static function constructorFromSettings(array $settings) { if (!self::$default) { // The getLogger function will automatically init the static logger, but we'll do it again anyway self::$default = self::getLoggerFromSettings(MTProto::parseSettings($settings)); } } /** * Get logger from MadelineProto settings. * * @param array $settings Settings array * @param string $prefix Optional prefix for log messages * * @return self */ public static function getLoggerFromSettings(array $settings, string $prefix = ''): self { if (!isset($settings['logger']['logger_param']) && isset($settings['logger']['param'])) { $settings['logger']['logger_param'] = $settings['logger']['param']; } if (PHP_SAPI !== 'cli' && isset($settings['logger']['logger_param']) && $settings['logger']['logger_param'] === 'MadelineProto.log') { $settings['logger']['logger_param'] = Magic::$script_cwd.'/MadelineProto.log'; } $logger = new self($settings['logger']['logger'], $settings['logger']['logger_param'] ?? '', $prefix, $settings['logger']['logger_level'] ?? Logger::VERBOSE, $settings['logger']['max_size'] ?? 100 * 1024 * 1024); if (!self::$default) { self::$default = $logger; } if (PHP_SAPI !== 'cli') { try { \error_reporting(E_ALL); \ini_set('log_errors', 1); \ini_set('error_log', $settings['logger']['logger'] === self::FILE_LOGGER ? $settings['logger']['logger_param'] : Magic::$script_cwd.'/MadelineProto.log'); \error_log('Enabled PHP logging'); } catch (\danog\MadelineProto\Exception $e) { $logger->logger('Could not enable PHP logging'); } } return $logger; } /** * Construct global logger. * * @param int $mode One of the logger constants * @param mixed $optional Optional parameter for logger * @param string $prefix Prefix for log messages * @param int $level Default logging level * @param int $max_size Maximum size for logfile * * @return void */ public static function constructor(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 100 * 1024 * 1024) { self::$default = new self($mode, $optional, $prefix, $level, $max_size); } /** * Construct global logger. * * @param int $mode One of the logger constants * @param mixed $optional Optional parameter for logger * @param string $prefix Prefix for log messages * @param int $level Default logging level * @param int $max_size Maximum size for logfile * * @return void */ public function __construct(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 100 * 1024 * 1024) { if ($mode === null) { throw new Exception(\danog\MadelineProto\Lang::$current_lang['no_mode_specified']); } $this->mode = $mode; $this->optional = $mode == 2 ? Tools::absolute($optional) : $optional; $this->prefix = $prefix === '' ? '' : ', '.$prefix; $this->level = $level; if ($this->mode === 2 && !\file_exists(\pathinfo($this->optional, PATHINFO_DIRNAME))) { $this->optional = Magic::$script_cwd.'/MadelineProto.log'; } if ($this->mode === 2 && !\preg_match('/\\.log$/', $this->optional)) { $this->optional .= '.log'; } if ($mode === 2 && $max_size !== -1 && \file_exists($this->optional) && \filesize($this->optional) > $max_size) { \unlink($this->optional); } $this->colors[self::ULTRA_VERBOSE] = \implode(';', [self::FOREGROUND['light_gray'], self::SET['dim']]); $this->colors[self::VERBOSE] = \implode(';', [self::FOREGROUND['green'], self::SET['bold']]); $this->colors[self::NOTICE] = \implode(';', [self::FOREGROUND['yellow'], self::SET['bold']]); $this->colors[self::WARNING] = \implode(';', [self::FOREGROUND['white'], self::SET['dim'], self::BACKGROUND['red']]); $this->colors[self::ERROR] = \implode(';', [self::FOREGROUND['white'], self::SET['bold'], self::BACKGROUND['red']]); $this->colors[self::FATAL_ERROR] = \implode(';', [self::FOREGROUND['red'], self::SET['bold'], self::BACKGROUND['light_gray']]); $this->newline = PHP_EOL; if ($this->mode === 3) { $this->stdout = getStdout(); if (PHP_SAPI !== 'cli') { $this->newline = '
'.$this->newline; } } elseif ($this->mode === 2) { $this->stdout = new ResourceOutputStream(\fopen($this->optional, 'a+')); } elseif ($this->mode === 1) { $result = @\ini_get('error_log'); if ($result === 'syslog') { $this->stdout = getStderr(); } elseif ($result) { $this->stdout = new ResourceOutputStream(\fopen($result, 'a+')); } else { $this->stdout = getStderr(); } } } /** * Log a message. * * @param mixed $param Message * @param int $level Logging level * * @return void */ public static function log($param, int $level = self::NOTICE) { if (!\is_null(self::$default)) { self::$default->logger($param, $level, \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php')); } else { echo $param.PHP_EOL; } } /** * Log a message. * * @param mixed $param Message to log * @param int $level Logging level * @param string $file File that originated the message * * @return void */ public function logger($param, int $level = self::NOTICE, string $file = ''): void { if ($level > $this->level || $this->mode === 0) { return; } if (!self::$printed) { self::$printed = true; $this->colors[self::NOTICE] = \implode(';', [self::FOREGROUND['light_gray'], self::SET['bold'], self::BACKGROUND['blue']]); $this->logger('MadelineProto'); $this->logger('Copyright (C) 2016-2020 Daniil Gentili'); $this->logger('Licensed under AGPLv3'); $this->logger('https://github.com/danog/MadelineProto'); $this->colors[self::NOTICE] = \implode(';', [self::FOREGROUND['yellow'], self::SET['bold']]); } if ($this->mode === 4) { \call_user_func_array($this->optional, [$param, $level]); return; } $prefix = $this->prefix; if ($param instanceof \Throwable) { $param = (string) $param; } elseif (!\is_string($param)) { $param = \json_encode($param, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } if (empty($file)) { $file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php'); } $param = \str_pad($file.$prefix.': ', 16 + \strlen($prefix))."\t".$param; switch ($this->mode) { case 1: if ($this->stdout->write($param.$this->newline) instanceof Failure) { \error_log($param); } break; default: $param = Magic::$isatty ? "\33[".$this->colors[$level].'m'.$param."\33[0m".$this->newline : $param.$this->newline; if ($this->stdout->write($param) instanceof Failure) { switch ($this->mode) { case 3: echo $param; break; case 2: \file_put_contents($this->optional, $param, FILE_APPEND); break; } } break; } } }