diff options
Diffstat (limited to 'src')
101 files changed, 1979 insertions, 1398 deletions
diff --git a/src/Monolog/DateTimeImmutable.php b/src/Monolog/DateTimeImmutable.php new file mode 100644 index 0000000..4e9f598 --- /dev/null +++ b/src/Monolog/DateTimeImmutable.php @@ -0,0 +1,64 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + private $useMicroseconds; + + public function __construct($useMicroseconds, \DateTimeZone $timezone = null) + { + static $needsMicrosecondsHack = PHP_VERSION_ID < 70100; + + $this->useMicroseconds = $useMicroseconds; + $date = 'now'; + + if ($needsMicrosecondsHack && $useMicroseconds) { + $timestamp = microtime(true); + + // apply offset of the timezone as microtime() is always UTC + if ($timezone && $timezone->getName() !== 'UTC') { + $timestamp += (new \DateTime('now', $timezone))->getOffset(); + } + + // Circumvent DateTimeImmutable::createFromFormat() which always returns \DateTimeImmutable instead of `static` + // @link https://bugs.php.net/bug.php?id=60302 + // + // So we create a DateTime but then format it so we + // can re-create one using the right class + $dt = self::createFromFormat('U.u', sprintf('%.6F', $timestamp)); + $date = $dt->format('Y-m-d H:i:s.u'); + } + + parent::__construct($date, $timezone); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/src/Monolog/ErrorHandler.php b/src/Monolog/ErrorHandler.php index 7bfcd83..d0b1aa6 100644 --- a/src/Monolog/ErrorHandler.php +++ b/src/Monolog/ErrorHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -13,7 +13,6 @@ namespace Monolog; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; -use Monolog\Handler\AbstractHandler; /** * Monolog error handler @@ -29,7 +28,7 @@ class ErrorHandler private $logger; private $previousExceptionHandler; - private $uncaughtExceptionLevel; + private $uncaughtExceptionLevelMap; private $previousErrorHandler; private $errorLevelMap; @@ -38,7 +37,7 @@ class ErrorHandler private $hasFatalErrorHandler; private $fatalLevel; private $reservedMemory; - private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]; public function __construct(LoggerInterface $logger) { @@ -51,12 +50,12 @@ class ErrorHandler * By default it will handle errors, exceptions and fatal errors * * @param LoggerInterface $logger - * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling - * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling - * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling * @return ErrorHandler */ - public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self { //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 class_exists('\\Psr\\Log\\LogLevel', true); @@ -65,8 +64,8 @@ class ErrorHandler if ($errorLevelMap !== false) { $handler->registerErrorHandler($errorLevelMap); } - if ($exceptionLevel !== false) { - $handler->registerExceptionHandler($exceptionLevel); + if ($exceptionLevelMap !== false) { + $handler->registerExceptionHandler($exceptionLevelMap); } if ($fatalLevel !== false) { $handler->registerFatalHandler($fatalLevel); @@ -75,38 +74,57 @@ class ErrorHandler return $handler; } - public function registerExceptionHandler($level = null, $callPrevious = true) + public function registerExceptionHandler($levelMap = [], $callPrevious = true): self { - $prev = set_exception_handler(array($this, 'handleException')); - $this->uncaughtExceptionLevel = $level; + $prev = set_exception_handler([$this, 'handleException']); + $this->uncaughtExceptionLevelMap = $levelMap; + foreach ($this->defaultExceptionLevelMap() as $class => $level) { + if (!isset($this->uncaughtExceptionLevelMap[$class])) { + $this->uncaughtExceptionLevelMap[$class] = $level; + } + } if ($callPrevious && $prev) { $this->previousExceptionHandler = $prev; } + + return $this; } - public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + public function registerErrorHandler(array $levelMap = [], $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true): self { - $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $prev = set_error_handler([$this, 'handleError'], $errorTypes); $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); if ($callPrevious) { $this->previousErrorHandler = $prev ?: true; } $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + + return $this; } - public function registerFatalHandler($level = null, $reservedMemorySize = 20) + public function registerFatalHandler($level = null, $reservedMemorySize = 20): self { - register_shutdown_function(array($this, 'handleFatalError')); + register_shutdown_function([$this, 'handleFatalError']); $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); $this->fatalLevel = $level; $this->hasFatalErrorHandler = true; + + return $this; + } + + protected function defaultExceptionLevelMap(): array + { + return [ + 'ParseError' => LogLevel::CRITICAL, + 'Throwable' => LogLevel::ERROR, + ]; } - protected function defaultErrorLevelMap() + protected function defaultErrorLevelMap(): array { - return array( + return [ E_ERROR => LogLevel::CRITICAL, E_WARNING => LogLevel::WARNING, E_PARSE => LogLevel::ALERT, @@ -122,7 +140,7 @@ class ErrorHandler E_RECOVERABLE_ERROR => LogLevel::ERROR, E_DEPRECATED => LogLevel::NOTICE, E_USER_DEPRECATED => LogLevel::NOTICE, - ); + ]; } /** @@ -130,10 +148,18 @@ class ErrorHandler */ public function handleException($e) { + $level = LogLevel::ERROR; + foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { + if ($e instanceof $class) { + $level = $candidate; + break; + } + } + $this->logger->log( - $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + $level, sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), - array('exception' => $e) + ['exception' => $e] ); if ($this->previousExceptionHandler) { @@ -146,7 +172,7 @@ class ErrorHandler /** * @private */ - public function handleError($code, $message, $file = '', $line = 0, $context = array()) + public function handleError($code, $message, $file = '', $line = 0, $context = []) { if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { return; @@ -154,8 +180,8 @@ class ErrorHandler // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { - $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; - $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); } if ($this->previousErrorHandler === true) { @@ -170,27 +196,25 @@ class ErrorHandler */ public function handleFatalError() { - $this->reservedMemory = null; + $this->reservedMemory = ''; $lastError = error_get_last(); if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { $this->logger->log( $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], - array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']) + ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']] ); if ($this->logger instanceof Logger) { foreach ($this->logger->getHandlers() as $handler) { - if ($handler instanceof AbstractHandler) { - $handler->close(); - } + $handler->close(); } } } } - private static function codeToString($code) + private static function codeToString($code): string { switch ($code) { case E_ERROR: diff --git a/src/Monolog/Formatter/ChromePHPFormatter.php b/src/Monolog/Formatter/ChromePHPFormatter.php index 9beda1e..2b4d649 100644 --- a/src/Monolog/Formatter/ChromePHPFormatter.php +++ b/src/Monolog/Formatter/ChromePHPFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -23,7 +23,7 @@ class ChromePHPFormatter implements FormatterInterface /** * Translates Monolog log levels to Wildfire levels. */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => 'log', Logger::INFO => 'info', Logger::NOTICE => 'info', @@ -32,7 +32,7 @@ class ChromePHPFormatter implements FormatterInterface Logger::CRITICAL => 'error', Logger::ALERT => 'error', Logger::EMERGENCY => 'error', - ); + ]; /** * {@inheritdoc} @@ -46,7 +46,7 @@ class ChromePHPFormatter implements FormatterInterface unset($record['extra']['file'], $record['extra']['line']); } - $message = array('message' => $record['message']); + $message = ['message' => $record['message']]; if ($record['context']) { $message['context'] = $record['context']; } @@ -57,17 +57,20 @@ class ChromePHPFormatter implements FormatterInterface $message = reset($message); } - return array( + return [ $record['channel'], $message, $backtrace, $this->logLevels[$record['level']], - ); + ]; } + /** + * {@inheritdoc} + */ public function formatBatch(array $records) { - $formatted = array(); + $formatted = []; foreach ($records as $record) { $formatted[] = $this->format($record); diff --git a/src/Monolog/Formatter/ElasticaFormatter.php b/src/Monolog/Formatter/ElasticaFormatter.php index 4c556cf..a6354f5 100644 --- a/src/Monolog/Formatter/ElasticaFormatter.php +++ b/src/Monolog/Formatter/ElasticaFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -34,7 +34,7 @@ class ElasticaFormatter extends NormalizerFormatter * @param string $index Elastic Search index name * @param string $type Elastic Search document type */ - public function __construct($index, $type) + public function __construct(string $index, string $type) { // elasticsearch requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\TH:i:s.uP'); @@ -53,31 +53,20 @@ class ElasticaFormatter extends NormalizerFormatter return $this->getDocument($record); } - /** - * Getter index - * @return string - */ - public function getIndex() + public function getIndex(): string { return $this->index; } - /** - * Getter type - * @return string - */ - public function getType() + public function getType(): string { return $this->type; } /** * Convert a log message into an Elastica Document - * - * @param array $record Log message - * @return Document */ - protected function getDocument($record) + protected function getDocument(array $record): Document { $document = new Document(); $document->setData($record); diff --git a/src/Monolog/Formatter/FlowdockFormatter.php b/src/Monolog/Formatter/FlowdockFormatter.php index 5094af3..301b74b 100644 --- a/src/Monolog/Formatter/FlowdockFormatter.php +++ b/src/Monolog/Formatter/FlowdockFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -28,11 +28,7 @@ class FlowdockFormatter implements FormatterInterface */ private $sourceEmail; - /** - * @param string $source - * @param string $sourceEmail - */ - public function __construct($source, $sourceEmail) + public function __construct(string $source, string $sourceEmail) { $this->source = $source; $this->sourceEmail = $sourceEmail; @@ -41,13 +37,13 @@ class FlowdockFormatter implements FormatterInterface /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): array { - $tags = array( + $tags = [ '#logs', '#' . strtolower($record['level_name']), '#' . $record['channel'], - ); + ]; foreach ($record['extra'] as $value) { $tags[] = '#' . $value; @@ -60,14 +56,14 @@ class FlowdockFormatter implements FormatterInterface $this->getShortMessage($record['message']) ); - $record['flowdock'] = array( + $record['flowdock'] = [ 'source' => $this->source, 'from_address' => $this->sourceEmail, 'subject' => $subject, 'content' => $record['message'], 'tags' => $tags, 'project' => $this->source, - ); + ]; return $record; } @@ -75,9 +71,9 @@ class FlowdockFormatter implements FormatterInterface /** * {@inheritdoc} */ - public function formatBatch(array $records) + public function formatBatch(array $records): array { - $formatted = array(); + $formatted = []; foreach ($records as $record) { $formatted[] = $this->format($record); @@ -86,12 +82,7 @@ class FlowdockFormatter implements FormatterInterface return $formatted; } - /** - * @param string $message - * - * @return string - */ - public function getShortMessage($message) + public function getShortMessage(string $message): string { static $hasMbString; diff --git a/src/Monolog/Formatter/FluentdFormatter.php b/src/Monolog/Formatter/FluentdFormatter.php index 02632bb..a84f826 100644 --- a/src/Monolog/Formatter/FluentdFormatter.php +++ b/src/Monolog/Formatter/FluentdFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -39,41 +39,41 @@ class FluentdFormatter implements FormatterInterface */ protected $levelTag = false; - public function __construct($levelTag = false) + public function __construct(bool $levelTag = false) { if (!function_exists('json_encode')) { throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); } - $this->levelTag = (bool) $levelTag; + $this->levelTag = $levelTag; } - public function isUsingLevelsInTag() + public function isUsingLevelsInTag(): bool { return $this->levelTag; } - public function format(array $record) + public function format(array $record): string { $tag = $record['channel']; if ($this->levelTag) { $tag .= '.' . strtolower($record['level_name']); } - $message = array( + $message = [ 'message' => $record['message'], 'extra' => $record['extra'], - ); + ]; if (!$this->levelTag) { $message['level'] = $record['level']; $message['level_name'] = $record['level_name']; } - return json_encode(array($tag, $record['datetime']->getTimestamp(), $message)); + return json_encode([$tag, $record['datetime']->getTimestamp(), $message]); } - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { diff --git a/src/Monolog/Formatter/FormatterInterface.php b/src/Monolog/Formatter/FormatterInterface.php index b5de751..7442134 100644 --- a/src/Monolog/Formatter/FormatterInterface.php +++ b/src/Monolog/Formatter/FormatterInterface.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. diff --git a/src/Monolog/Formatter/GelfMessageFormatter.php b/src/Monolog/Formatter/GelfMessageFormatter.php index 2c1b0e8..4e95a6e 100644 --- a/src/Monolog/Formatter/GelfMessageFormatter.php +++ b/src/Monolog/Formatter/GelfMessageFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -47,7 +47,7 @@ class GelfMessageFormatter extends NormalizerFormatter /** * Translates Monolog log levels to Graylog2 log priorities. */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => 7, Logger::INFO => 6, Logger::NOTICE => 5, @@ -56,9 +56,9 @@ class GelfMessageFormatter extends NormalizerFormatter Logger::CRITICAL => 2, Logger::ALERT => 1, Logger::EMERGENCY => 0, - ); + ]; - public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null) + public function __construct(string $systemName = null, string $extraPrefix = null, string $contextPrefix = 'ctxt_', int $maxLength = null) { parent::__construct('U.u'); @@ -72,9 +72,14 @@ class GelfMessageFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): Message { - $record = parent::format($record); + if (isset($record['context'])) { + $record['context'] = parent::format($record['context']); + } + if (isset($record['extra'])) { + $record['extra'] = parent::format($record['extra']); + } if (!isset($record['datetime'], $record['message'], $record['level'])) { throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); @@ -87,7 +92,7 @@ class GelfMessageFormatter extends NormalizerFormatter ->setHost($this->systemName) ->setLevel($this->logLevels[$record['level']]); - // message length + system name length + 200 for padding / metadata + // message length + system name length + 200 for padding / metadata $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); if ($len > $this->maxLength) { diff --git a/src/Monolog/Formatter/HtmlFormatter.php b/src/Monolog/Formatter/HtmlFormatter.php index 3eec95f..a343b06 100644 --- a/src/Monolog/Formatter/HtmlFormatter.php +++ b/src/Monolog/Formatter/HtmlFormatter.php @@ -1,4 +1,5 @@ -<?php +<?php declare(strict_types=1); + /* * This file is part of the Monolog package. * @@ -24,7 +25,7 @@ class HtmlFormatter extends NormalizerFormatter /** * Translates Monolog log levels to html color priorities. */ - protected $logLevels = array( + protected $logLevels = [ Logger::DEBUG => '#cccccc', Logger::INFO => '#468847', Logger::NOTICE => '#3a87ad', @@ -33,12 +34,12 @@ class HtmlFormatter extends NormalizerFormatter Logger::CRITICAL => '#FF7708', Logger::ALERT => '#C12A19', Logger::EMERGENCY => '#000000', - ); + ]; /** * @param string $dateFormat The format of the timestamp: one supported by DateTime::format */ - public function __construct($dateFormat = null) + public function __construct(string $dateFormat = null) { parent::__construct($dateFormat); } @@ -51,7 +52,7 @@ class HtmlFormatter extends NormalizerFormatter * @param bool $escapeTd false if td content must not be html escaped * @return string */ - protected function addRow($th, $td = ' ', $escapeTd = true) + protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string { $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); if ($escapeTd) { @@ -68,7 +69,7 @@ class HtmlFormatter extends NormalizerFormatter * @param int $level Error level * @return string */ - protected function addTitle($title, $level) + protected function addTitle(string $title, int $level) { $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); @@ -81,13 +82,13 @@ class HtmlFormatter extends NormalizerFormatter * @param array $record A record to format * @return mixed The formatted record */ - public function format(array $record) + public function format(array $record): string { $output = $this->addTitle($record['level_name'], $record['level']); $output .= '<table cellspacing="1" width="100%" class="monolog-output">'; $output .= $this->addRow('Message', (string) $record['message']); - $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Time', $this->formatDate($record['datetime'])); $output .= $this->addRow('Channel', $record['channel']); if ($record['context']) { $embeddedTable = '<table cellspacing="1" width="100%">'; @@ -115,7 +116,7 @@ class HtmlFormatter extends NormalizerFormatter * @param array $records A set of records to format * @return mixed The formatted set of records */ - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { @@ -125,17 +126,14 @@ class HtmlFormatter extends NormalizerFormatter return $message; } - protected function convertToString($data) + protected function convertToString($data): string { if (null === $data || is_scalar($data)) { return (string) $data; } $data = $this->normalize($data); - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - } - return str_replace('\\/', '/', json_encode($data)); + return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } } diff --git a/src/Monolog/Formatter/JsonFormatter.php b/src/Monolog/Formatter/JsonFormatter.php index 0782f14..8e2f2fd 100644 --- a/src/Monolog/Formatter/JsonFormatter.php +++ b/src/Monolog/Formatter/JsonFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,7 +11,6 @@ namespace Monolog\Formatter; -use Exception; use Throwable; /** @@ -34,11 +33,7 @@ class JsonFormatter extends NormalizerFormatter */ protected $includeStacktraces = false; - /** - * @param int $batchMode - * @param bool $appendNewline - */ - public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true) + public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true) { $this->batchMode = $batchMode; $this->appendNewline = $appendNewline; @@ -50,20 +45,16 @@ class JsonFormatter extends NormalizerFormatter * formatted as a JSON-encoded array. However, for * compatibility with some API endpoints, alternative styles * are available. - * - * @return int */ - public function getBatchMode() + public function getBatchMode(): int { return $this->batchMode; } /** * True if newlines are appended to every formatted record - * - * @return bool */ - public function isAppendingNewlines() + public function isAppendingNewlines(): bool { return $this->appendNewline; } @@ -71,7 +62,7 @@ class JsonFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): string { return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); } @@ -79,7 +70,7 @@ class JsonFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function formatBatch(array $records) + public function formatBatch(array $records): string { switch ($this->batchMode) { case static::BATCH_MODE_NEWLINES: @@ -91,21 +82,15 @@ class JsonFormatter extends NormalizerFormatter } } - /** - * @param bool $include - */ - public function includeStacktraces($include = true) + public function includeStacktraces(bool $include = true) { $this->includeStacktraces = $include; } /** * Return a JSON-encoded array of records. - * - * @param array $records - * @return string */ - protected function formatBatchJson(array $records) + protected function formatBatchJson(array $records): string { return $this->toJson($this->normalize($records), true); } @@ -113,11 +98,8 @@ class JsonFormatter extends NormalizerFormatter /** * Use new lines to separate records instead of a * JSON-encoded array. - * - * @param array $records - * @return string */ - protected function formatBatchNewlines(array $records) + protected function formatBatchNewlines(array $records): string { $instance = $this; @@ -138,10 +120,14 @@ class JsonFormatter extends NormalizerFormatter * * @return mixed */ - protected function normalize($data) + protected function normalize($data, int $depth = 0) { + if ($depth > 9) { + return 'Over 9 levels deep, aborting normalization'; + } + if (is_array($data) || $data instanceof \Traversable) { - $normalized = array(); + $normalized = []; $count = 1; foreach ($data as $key => $value) { @@ -149,14 +135,14 @@ class JsonFormatter extends NormalizerFormatter $normalized['...'] = 'Over 1000 items, aborting normalization'; break; } - $normalized[$key] = $this->normalize($value); + $normalized[$key] = $this->normalize($value, $depth + 1); } return $normalized; } - if ($data instanceof Exception || $data instanceof Throwable) { - return $this->normalizeException($data); + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); } return $data; @@ -165,24 +151,15 @@ class JsonFormatter extends NormalizerFormatter /** * Normalizes given exception with or without its own stack trace based on * `includeStacktraces` property. - * - * @param Exception|Throwable $e - * - * @return array */ - protected function normalizeException($e) + protected function normalizeException(Throwable $e, int $depth = 0): array { - // TODO 2.0 only check for Throwable - if (!$e instanceof Exception && !$e instanceof Throwable) { - throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); - } - - $data = array( + $data = [ 'class' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile().':'.$e->getLine(), - ); + ]; if ($this->includeStacktraces) { $trace = $e->getTrace(); @@ -200,7 +177,7 @@ class JsonFormatter extends NormalizerFormatter } if ($previous = $e->getPrevious()) { - $data['previous'] = $this->normalizeException($previous); + $data['previous'] = $this->normalizeException($previous, $depth + 1); } return $data; diff --git a/src/Monolog/Formatter/LineFormatter.php b/src/Monolog/Formatter/LineFormatter.php index d3e209e..9d4ef1d 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -34,7 +34,7 @@ class LineFormatter extends NormalizerFormatter * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries * @param bool $ignoreEmptyContextAndExtra */ - public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + public function __construct(string $format = null, string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false) { $this->format = $format ?: static::SIMPLE_FORMAT; $this->allowInlineLineBreaks = $allowInlineLineBreaks; @@ -42,7 +42,7 @@ class LineFormatter extends NormalizerFormatter parent::__construct($dateFormat); } - public function includeStacktraces($include = true) + public function includeStacktraces(bool $include = true) { $this->includeStacktraces = $include; if ($this->includeStacktraces) { @@ -50,12 +50,12 @@ class LineFormatter extends NormalizerFormatter } } - public function allowInlineLineBreaks($allow = true) + public function allowInlineLineBreaks(bool $allow = true) { $this->allowInlineLineBreaks = $allow; } - public function ignoreEmptyContextAndExtra($ignore = true) + public function ignoreEmptyContextAndExtra(bool $ignore = true) { $this->ignoreEmptyContextAndExtra = $ignore; } @@ -63,7 +63,7 @@ class LineFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): string { $vars = parent::format($record); @@ -76,7 +76,6 @@ class LineFormatter extends NormalizerFormatter } } - foreach ($vars['context'] as $var => $val) { if (false !== strpos($output, '%context.'.$var.'%')) { $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); @@ -110,7 +109,7 @@ class LineFormatter extends NormalizerFormatter return $output; } - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { @@ -120,18 +119,13 @@ class LineFormatter extends NormalizerFormatter return $message; } - public function stringify($value) + public function stringify($value): string { return $this->replaceNewlines($this->convertToString($value)); } - protected function normalizeException($e) + protected function normalizeException(\Throwable $e, int $depth = 0): string { - // TODO 2.0 only check for Throwable - if (!$e instanceof \Exception && !$e instanceof \Throwable) { - throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); - } - $previousText = ''; if ($previous = $e->getPrevious()) { do { @@ -147,7 +141,7 @@ class LineFormatter extends NormalizerFormatter return $str; } - protected function convertToString($data) + protected function convertToString($data): string { if (null === $data || is_bool($data)) { return var_export($data, true); @@ -157,14 +151,10 @@ class LineFormatter extends NormalizerFormatter return (string) $data; } - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return $this->toJson($data, true); - } - - return str_replace('\\/', '/', @json_encode($data)); + return (string) $this->toJson($data, true); } - protected function replaceNewlines($str) + protected function replaceNewlines(string $str): string { if ($this->allowInlineLineBreaks) { if (0 === strpos($str, '{')) { @@ -174,6 +164,6 @@ class LineFormatter extends NormalizerFormatter return $str; } - return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + return str_replace(["\r\n", "\r", "\n"], ' ', $str); } } diff --git a/src/Monolog/Formatter/LogglyFormatter.php b/src/Monolog/Formatter/LogglyFormatter.php index 401859b..29841aa 100644 --- a/src/Monolog/Formatter/LogglyFormatter.php +++ b/src/Monolog/Formatter/LogglyFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -21,10 +21,8 @@ class LogglyFormatter extends JsonFormatter /** * Overrides the default batch mode to new lines for compatibility with the * Loggly bulk API. - * - * @param int $batchMode */ - public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false) + public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false) { parent::__construct($batchMode, $appendNewline); } @@ -35,11 +33,11 @@ class LogglyFormatter extends JsonFormatter * @see https://www.loggly.com/docs/automated-parsing/#json * @see \Monolog\Formatter\JsonFormatter::format() */ - public function format(array $record) + public function format(array $record): string { - if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTimeInterface)) { $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); - // TODO 2.0 unset the 'datetime' parameter, retained for BC + unset($record["datetime"]); } return parent::format($record); diff --git a/src/Monolog/Formatter/LogmaticFormatter.php b/src/Monolog/Formatter/LogmaticFormatter.php new file mode 100644 index 0000000..7a75e00 --- /dev/null +++ b/src/Monolog/Formatter/LogmaticFormatter.php @@ -0,0 +1,72 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Logmatic. + * + * @author Julien Breux <julien.breux@gmail.com> + */ +class LogmaticFormatter extends JsonFormatter +{ + const MARKERS = ["sourcecode", "php"]; + + /** + * @param string + */ + protected $hostname = ''; + + /** + * @param string + */ + protected $appname = ''; + + /** + * Set hostname + * + * @param string $hostname + */ + public function setHostname(string $hostname) + { + $this->hostname = $hostname; + } + + /** + * Set appname + * + * @param string $appname + */ + public function setAppname(string $appname) + { + $this->appname = $appname; + } + + /** + * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. + * + * @see http://doc.logmatic.io/docs/basics-to-send-data + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record): string + { + if (!empty($this->hostname)) { + $record["hostname"] = $this->hostname; + } + if (!empty($this->appname)) { + $record["appname"] = $this->appname; + } + + $record["@marker"] = self::MARKERS; + + return parent::format($record); + } +} diff --git a/src/Monolog/Formatter/LogstashFormatter.php b/src/Monolog/Formatter/LogstashFormatter.php index 8f83bec..3cf31dd 100644 --- a/src/Monolog/Formatter/LogstashFormatter.php +++ b/src/Monolog/Formatter/LogstashFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -15,15 +15,12 @@ namespace Monolog\Formatter; * Serializes a log message to Logstash Event Format * * @see http://logstash.net/ - * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * @see https://github.com/elastic/logstash/blob/master/logstash-core-event/lib/logstash/event.rb * * @author Tim Mower <timothy.mower@gmail.com> */ class LogstashFormatter extends NormalizerFormatter { - const V0 = 0; - const V1 = 1; - /** * @var string the name of the system for the Logstash log message, used to fill the @source field */ @@ -45,18 +42,12 @@ class LogstashFormatter extends NormalizerFormatter protected $contextPrefix; /** - * @var int logstash format version to use - */ - protected $version; - - /** * @param string $applicationName the application that sends the data, used as the "type" field of logstash * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine * @param string $extraPrefix prefix for extra keys inside logstash "fields" * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ - * @param int $version the logstash format version to use, defaults to 0 */ - public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + public function __construct(string $applicationName, string $systemName = null, string $extraPrefix = null, string $contextPrefix = 'ctxt_') { // logstash requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\TH:i:s.uP'); @@ -65,78 +56,23 @@ class LogstashFormatter extends NormalizerFormatter $this->applicationName = $applicationName; $this->extraPrefix = $extraPrefix; $this->contextPrefix = $contextPrefix; - $this->version = $version; } /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): string { $record = parent::format($record); - if ($this->version === self::V1) { - $message = $this->formatV1($record); - } else { - $message = $this->formatV0($record); - } - - return $this->toJson($message) . "\n"; - } - - protected function formatV0(array $record) - { if (empty($record['datetime'])) { $record['datetime'] = gmdate('c'); } - $message = array( - '@timestamp' => $record['datetime'], - '@source' => $this->systemName, - '@fields' => array(), - ); - if (isset($record['message'])) { - $message['@message'] = $record['message']; - } - if (isset($record['channel'])) { - $message['@tags'] = array($record['channel']); - $message['@fields']['channel'] = $record['channel']; - } - if (isset($record['level'])) { - $message['@fields']['level'] = $record['level']; - } - if ($this->applicationName) { - $message['@type'] = $this->applicationName; - } - if (isset($record['extra']['server'])) { - $message['@source_host'] = $record['extra']['server']; - } - if (isset($record['extra']['url'])) { - $message['@source_path'] = $record['extra']['url']; - } - if (!empty($record['extra'])) { - foreach ($record['extra'] as $key => $val) { - $message['@fields'][$this->extraPrefix . $key] = $val; - } - } - if (!empty($record['context'])) { - foreach ($record['context'] as $key => $val) { - $message['@fields'][$this->contextPrefix . $key] = $val; - } - } - - return $message; - } - - protected function formatV1(array $record) - { - if (empty($record['datetime'])) { - $record['datetime'] = gmdate('c'); - } - $message = array( + $message = [ '@timestamp' => $record['datetime'], '@version' => 1, 'host' => $this->systemName, - ); + ]; if (isset($record['message'])) { $message['message'] = $record['message']; } @@ -147,20 +83,19 @@ class LogstashFormatter extends NormalizerFormatter if (isset($record['level_name'])) { $message['level'] = $record['level_name']; } + if (isset($record['level'])) { + $message['monolog_level'] = $record['level']; + } if ($this->applicationName) { $message['type'] = $this->applicationName; } if (!empty($record['extra'])) { - foreach ($record['extra'] as $key => $val) { - $message[$this->extraPrefix . $key] = $val; - } + $message[$this->extraPrefix.'extra'] = $record['extra']; } if (!empty($record['context'])) { - foreach ($record['context'] as $key => $val) { - $message[$this->contextPrefix . $key] = $val; - } + $message[$this->contextPrefix.'context'] = $record['context']; } - return $message; + return $this->toJson($message) . "\n"; } } diff --git a/src/Monolog/Formatter/MongoDBFormatter.php b/src/Monolog/Formatter/MongoDBFormatter.php index eb067bb..8c40e3e 100644 --- a/src/Monolog/Formatter/MongoDBFormatter.php +++ b/src/Monolog/Formatter/MongoDBFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,6 +11,8 @@ namespace Monolog\Formatter; +use MongoDB\BSON\UTCDateTime; + /** * Formats a record for use with the MongoDBHandler. * @@ -20,21 +22,24 @@ class MongoDBFormatter implements FormatterInterface { private $exceptionTraceAsString; private $maxNestingLevel; + private $isLegacyMongoExt; /** * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings */ - public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true) + public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) { $this->maxNestingLevel = max($maxNestingLevel, 0); - $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + $this->exceptionTraceAsString = $exceptionTraceAsString; + + $this->isLegacyMongoExt = version_compare(phpversion('mongodb'), '1.1.9', '<='); } /** * {@inheritDoc} */ - public function format(array $record) + public function format(array $record): array { return $this->formatArray($record); } @@ -42,7 +47,7 @@ class MongoDBFormatter implements FormatterInterface /** * {@inheritDoc} */ - public function formatBatch(array $records) + public function formatBatch(array $records): array { foreach ($records as $key => $record) { $records[$key] = $this->format($record); @@ -51,13 +56,16 @@ class MongoDBFormatter implements FormatterInterface return $records; } - protected function formatArray(array $record, $nestingLevel = 0) + /** + * @return array|string Array except when max nesting level is reached then a string "[...]" + */ + protected function formatArray(array $record, int $nestingLevel = 0) { if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { foreach ($record as $name => $value) { - if ($value instanceof \DateTime) { + if ($value instanceof \DateTimeInterface) { $record[$name] = $this->formatDate($value, $nestingLevel + 1); - } elseif ($value instanceof \Exception) { + } elseif ($value instanceof \Throwable) { $record[$name] = $this->formatException($value, $nestingLevel + 1); } elseif (is_array($value)) { $record[$name] = $this->formatArray($value, $nestingLevel + 1); @@ -72,7 +80,7 @@ class MongoDBFormatter implements FormatterInterface return $record; } - protected function formatObject($value, $nestingLevel) + protected function formatObject($value, int $nestingLevel) { $objectVars = get_object_vars($value); $objectVars['class'] = get_class($value); @@ -80,14 +88,14 @@ class MongoDBFormatter implements FormatterInterface return $this->formatArray($objectVars, $nestingLevel); } - protected function formatException(\Exception $exception, $nestingLevel) + protected function formatException(\Throwable $exception, int $nestingLevel) { - $formattedException = array( + $formattedException = [ 'class' => get_class($exception), 'message' => $exception->getMessage(), 'code' => $exception->getCode(), 'file' => $exception->getFile() . ':' . $exception->getLine(), - ); + ]; if ($this->exceptionTraceAsString === true) { $formattedException['trace'] = $exception->getTraceAsString(); @@ -98,8 +106,35 @@ class MongoDBFormatter implements FormatterInterface return $this->formatArray($formattedException, $nestingLevel); } - protected function formatDate(\DateTime $value, $nestingLevel) + protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime + { + if ($this->isLegacyMongoExt) { + return $this->legacyGetMongoDbDateTime($value); + } + + return $this->getMongoDbDateTime($value); + } + + private function getMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + return new UTCDateTime((int) (string) floor($value->format('U.u') * 1000)); + } + + /** + * This is needed to support MongoDB Driver v1.19 and below + * + * See https://github.com/mongodb/mongo-php-driver/issues/426 + * + * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted + */ + private function legacyGetMongoDbDateTime(\DateTimeInterface $value): UTCDateTime { - return new \MongoDate($value->getTimestamp()); + $milliseconds = floor($value->format('U.u') * 1000); + + $milliseconds = (PHP_INT_SIZE == 8) //64-bit OS? + ? (int) $milliseconds + : (string) $milliseconds; + + return new UTCDateTime($milliseconds); } } diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index d441488..84f644c 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,7 +11,8 @@ namespace Monolog\Formatter; -use Exception; +use Throwable; +use Monolog\DateTimeImmutable; /** * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets @@ -20,16 +21,16 @@ use Exception; */ class NormalizerFormatter implements FormatterInterface { - const SIMPLE_DATE = "Y-m-d H:i:s"; + const SIMPLE_DATE = "Y-m-d\TH:i:sP"; protected $dateFormat; /** * @param string $dateFormat The format of the timestamp: one supported by DateTime::format */ - public function __construct($dateFormat = null) + public function __construct(string $dateFormat = null) { - $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; if (!function_exists('json_encode')) { throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); } @@ -55,8 +56,16 @@ class NormalizerFormatter implements FormatterInterface return $records; } - protected function normalize($data) + /** + * @param mixed $data + * @return int|bool|string|null|array + */ + protected function normalize($data, int $depth = 0) { + if ($depth > 9) { + return 'Over 9 levels deep, aborting normalization'; + } + if (null === $data || is_scalar($data)) { if (is_float($data)) { if (is_infinite($data)) { @@ -71,7 +80,7 @@ class NormalizerFormatter implements FormatterInterface } if (is_array($data)) { - $normalized = array(); + $normalized = []; $count = 1; foreach ($data as $key => $value) { @@ -79,53 +88,56 @@ class NormalizerFormatter implements FormatterInterface $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; break; } - $normalized[$key] = $this->normalize($value); + $normalized[$key] = $this->normalize($value, $depth + 1); } return $normalized; } - if ($data instanceof \DateTime) { - return $data->format($this->dateFormat); + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); } if (is_object($data)) { - // TODO 2.0 only check for Throwable - if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { - return $this->normalizeException($data); + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); } - // non-serializable objects that implement __toString stringified - if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + if ($data instanceof \JsonSerializable) { + $value = $data->jsonSerialize(); + } elseif (method_exists($data, '__toString')) { $value = $data->__toString(); } else { - // the rest is json-serialized in some way - $value = $this->toJson($data, true); + // the rest is normalized by json encoding and decoding it + $encoded = $this->toJson($data, true); + if ($encoded === false) { + $value = 'JSON_ERROR'; + } else { + $value = json_decode($encoded, true); + } } - return sprintf("[object] (%s: %s)", get_class($data), $value); + return [get_class($data) => $value]; } if (is_resource($data)) { - return sprintf('[resource] (%s)', get_resource_type($data)); + return sprintf('[resource(%s)]', get_resource_type($data)); } return '[unknown('.gettype($data).')]'; } - protected function normalizeException($e) + /** + * @return array + */ + protected function normalizeException(Throwable $e, int $depth = 0) { - // TODO 2.0 only check for Throwable - if (!$e instanceof Exception && !$e instanceof \Throwable) { - throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); - } - - $data = array( + $data = [ 'class' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile().':'.$e->getLine(), - ); + ]; if ($e instanceof \SoapFault) { if (isset($e->faultcode)) { @@ -150,12 +162,12 @@ class NormalizerFormatter implements FormatterInterface $data['trace'][] = $frame['function']; } else { // We should again normalize the frames, because it might contain invalid items - $data['trace'][] = $this->toJson($this->normalize($frame), true); + $data['trace'][] = $this->toJson($this->normalize($frame, $depth + 1), true); } } if ($previous = $e->getPrevious()) { - $data['previous'] = $this->normalizeException($previous); + $data['previous'] = $this->normalizeException($previous, $depth + 1); } return $data; @@ -165,11 +177,10 @@ class NormalizerFormatter implements FormatterInterface * Return the JSON representation of a value * * @param mixed $data - * @param bool $ignoreErrors * @throws \RuntimeException if encoding fails and errors are not ignored - * @return string + * @return string|bool */ - protected function toJson($data, $ignoreErrors = false) + protected function toJson($data, bool $ignoreErrors = false) { // suppress json_encode errors since it's twitchy with some inputs if ($ignoreErrors) { @@ -186,16 +197,12 @@ class NormalizerFormatter implements FormatterInterface } /** - * @param mixed $data - * @return string JSON encoded data or null on failure + * @param mixed $data + * @return string|bool JSON encoded data or false on failure */ private function jsonEncode($data) { - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - } - - return json_encode($data); + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION); } /** @@ -203,7 +210,7 @@ class NormalizerFormatter implements FormatterInterface * * If the failure is due to invalid string encoding, try to clean the * input and encode again. If the second encoding attempt fails, the - * inital error is not encoding related or the input can't be cleaned then + * initial error is not encoding related or the input can't be cleaned then * raise a descriptive exception. * * @param int $code return code of json_last_error function @@ -211,7 +218,7 @@ class NormalizerFormatter implements FormatterInterface * @throws \RuntimeException if failure can't be corrected * @return string JSON encoded data after error correction */ - private function handleJsonError($code, $data) + private function handleJsonError(int $code, $data): string { if ($code !== JSON_ERROR_UTF8) { $this->throwEncodeError($code, $data); @@ -220,7 +227,7 @@ class NormalizerFormatter implements FormatterInterface if (is_string($data)) { $this->detectAndCleanUtf8($data); } elseif (is_array($data)) { - array_walk_recursive($data, array($this, 'detectAndCleanUtf8')); + array_walk_recursive($data, [$this, 'detectAndCleanUtf8']); } else { $this->throwEncodeError($code, $data); } @@ -241,7 +248,7 @@ class NormalizerFormatter implements FormatterInterface * @param mixed $data data that was meant to be encoded * @throws \RuntimeException */ - private function throwEncodeError($code, $data) + private function throwEncodeError(int $code, $data) { switch ($code) { case JSON_ERROR_DEPTH: @@ -284,14 +291,27 @@ class NormalizerFormatter implements FormatterInterface if (is_string($data) && !preg_match('//u', $data)) { $data = preg_replace_callback( '/[\x80-\xFF]+/', - function ($m) { return utf8_encode($m[0]); }, + function ($m) { + return utf8_encode($m[0]); + }, $data ); $data = str_replace( - array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), - array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], $data ); } } + + protected function formatDate(\DateTimeInterface $date) + { + // in case the date format isn't custom then we defer to the custom DateTimeImmutable + // formatting logic, which will pick the right format based on whether useMicroseconds is on + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { + return (string) $date; + } + + return $date->format($this->dateFormat); + } } diff --git a/src/Monolog/Formatter/ScalarFormatter.php b/src/Monolog/Formatter/ScalarFormatter.php index 5d345d5..8d560e7 100644 --- a/src/Monolog/Formatter/ScalarFormatter.php +++ b/src/Monolog/Formatter/ScalarFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -22,7 +22,7 @@ class ScalarFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): array { foreach ($record as $key => $value) { $record[$key] = $this->normalizeValue($value); diff --git a/src/Monolog/Formatter/WildfireFormatter.php b/src/Monolog/Formatter/WildfireFormatter.php index 654710a..c8a3bb4 100644 --- a/src/Monolog/Formatter/WildfireFormatter.php +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -27,7 +27,7 @@ class WildfireFormatter extends NormalizerFormatter /** * Translates Monolog log levels to Wildfire levels. */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => 'LOG', Logger::INFO => 'INFO', Logger::NOTICE => 'INFO', @@ -36,12 +36,12 @@ class WildfireFormatter extends NormalizerFormatter Logger::CRITICAL => 'ERROR', Logger::ALERT => 'ERROR', Logger::EMERGENCY => 'ERROR', - ); + ]; /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): string { // Retrieve the line and file if set and remove them from the formatted extra $file = $line = ''; @@ -55,7 +55,7 @@ class WildfireFormatter extends NormalizerFormatter } $record = $this->normalize($record); - $message = array('message' => $record['message']); + $message = ['message' => $record['message']]; $handleError = false; if ($record['context']) { $message['context'] = $record['context']; @@ -79,15 +79,15 @@ class WildfireFormatter extends NormalizerFormatter } // Create JSON object describing the appearance of the message in the console - $json = $this->toJson(array( - array( + $json = $this->toJson([ + [ 'Type' => $type, 'File' => $file, 'Line' => $line, 'Label' => $label, - ), + ], $message, - ), $handleError); + ], $handleError); // The message itself is a serialization of the above JSON object + it's length return sprintf( @@ -97,17 +97,23 @@ class WildfireFormatter extends NormalizerFormatter ); } + /** + * {@inheritdoc} + */ public function formatBatch(array $records) { throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); } - protected function normalize($data) + /** + * {@inheritdoc} + */ + protected function normalize($data, int $depth = 0) { - if (is_object($data) && !$data instanceof \DateTime) { + if (is_object($data) && !$data instanceof \DateTimeInterface) { return $data; } - return parent::normalize($data); + return parent::normalize($data, $depth); } } diff --git a/src/Monolog/Handler/AbstractHandler.php b/src/Monolog/Handler/AbstractHandler.php index 758a425..ea79ec9 100644 --- a/src/Monolog/Handler/AbstractHandler.php +++ b/src/Monolog/Handler/AbstractHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,26 +12,18 @@ namespace Monolog\Handler; use Monolog\Logger; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; /** - * Base Handler class providing the Handler structure + * Base Handler class providing basic level/bubble support * * @author Jordi Boggiano <j.boggiano@seld.be> */ -abstract class AbstractHandler implements HandlerInterface +abstract class AbstractHandler extends Handler { protected $level = Logger::DEBUG; protected $bubble = true; /** - * @var FormatterInterface - */ - protected $formatter; - protected $processors = array(); - - /** * @param int $level The minimum logging level at which this handler will be triggered * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not */ @@ -44,84 +36,18 @@ abstract class AbstractHandler implements HandlerInterface /** * {@inheritdoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $record['level'] >= $this->level; } /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - foreach ($records as $record) { - $this->handle($record); - } - } - - /** - * Closes the handler. - * - * This will be called automatically when the object is destroyed - */ - public function close() - { - } - - /** - * {@inheritdoc} - */ - public function pushProcessor($callback) - { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } - array_unshift($this->processors, $callback); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function popProcessor() - { - if (!$this->processors) { - throw new \LogicException('You tried to pop from an empty processor stack.'); - } - - return array_shift($this->processors); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(FormatterInterface $formatter) - { - $this->formatter = $formatter; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - if (!$this->formatter) { - $this->formatter = $this->getDefaultFormatter(); - } - - return $this->formatter; - } - - /** * Sets minimum logging level at which this handler will be triggered. * * @param int|string $level Level or level name * @return self */ - public function setLevel($level) + public function setLevel($level): self { $this->level = Logger::toMonologLevel($level); @@ -133,7 +59,7 @@ abstract class AbstractHandler implements HandlerInterface * * @return int */ - public function getLevel() + public function getLevel(): int { return $this->level; } @@ -145,7 +71,7 @@ abstract class AbstractHandler implements HandlerInterface * false means that bubbling is not permitted. * @return self */ - public function setBubble($bubble) + public function setBubble(bool $bubble): self { $this->bubble = $bubble; @@ -158,29 +84,8 @@ abstract class AbstractHandler implements HandlerInterface * @return Boolean true means that this handler allows bubbling. * false means that bubbling is not permitted. */ - public function getBubble() + public function getBubble(): bool { return $this->bubble; } - - public function __destruct() - { - try { - $this->close(); - } catch (\Exception $e) { - // do nothing - } catch (\Throwable $e) { - // do nothing - } - } - - /** - * Gets the default formatter. - * - * @return FormatterInterface - */ - protected function getDefaultFormatter() - { - return new LineFormatter(); - } } diff --git a/src/Monolog/Handler/AbstractProcessingHandler.php b/src/Monolog/Handler/AbstractProcessingHandler.php index 6f18f72..654e671 100644 --- a/src/Monolog/Handler/AbstractProcessingHandler.php +++ b/src/Monolog/Handler/AbstractProcessingHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -19,18 +19,23 @@ namespace Monolog\Handler; * @author Jordi Boggiano <j.boggiano@seld.be> * @author Christophe Coevoet <stof@notk.org> */ -abstract class AbstractProcessingHandler extends AbstractHandler +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } - $record = $this->processRecord($record); + if ($this->processors) { + $record = $this->processRecord($record); + } $record['formatted'] = $this->getFormatter()->format($record); @@ -46,21 +51,4 @@ abstract class AbstractProcessingHandler extends AbstractHandler * @return void */ abstract protected function write(array $record); - - /** - * Processes a record. - * - * @param array $record - * @return array - */ - protected function processRecord(array $record) - { - if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - } - - return $record; - } } diff --git a/src/Monolog/Handler/AbstractSyslogHandler.php b/src/Monolog/Handler/AbstractSyslogHandler.php index e2b2832..d6fc41e 100644 --- a/src/Monolog/Handler/AbstractSyslogHandler.php +++ b/src/Monolog/Handler/AbstractSyslogHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; use Monolog\Formatter\LineFormatter; /** @@ -24,7 +25,7 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler /** * Translates Monolog log levels to syslog log priorities. */ - protected $logLevels = array( + protected $logLevels = [ Logger::DEBUG => LOG_DEBUG, Logger::INFO => LOG_INFO, Logger::NOTICE => LOG_NOTICE, @@ -33,12 +34,12 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler Logger::CRITICAL => LOG_CRIT, Logger::ALERT => LOG_ALERT, Logger::EMERGENCY => LOG_EMERG, - ); + ]; /** * List of valid log facility names. */ - protected $facilities = array( + protected $facilities = [ 'auth' => LOG_AUTH, 'authpriv' => LOG_AUTHPRIV, 'cron' => LOG_CRON, @@ -50,7 +51,7 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler 'syslog' => LOG_SYSLOG, 'user' => LOG_USER, 'uucp' => LOG_UUCP, - ); + ]; /** * @param mixed $facility @@ -82,7 +83,7 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler } // convert textual description of facility to syslog constant - if (array_key_exists(strtolower($facility), $this->facilities)) { + if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) { $facility = $this->facilities[strtolower($facility)]; } elseif (!in_array($facility, array_values($this->facilities), true)) { throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); @@ -94,7 +95,7 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler /** * {@inheritdoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); } diff --git a/src/Monolog/Handler/AmqpHandler.php b/src/Monolog/Handler/AmqpHandler.php index e5a46bc..6e39a11 100644 --- a/src/Monolog/Handler/AmqpHandler.php +++ b/src/Monolog/Handler/AmqpHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; use Monolog\Formatter\JsonFormatter; use PhpAmqpLib\Message\AMQPMessage; use PhpAmqpLib\Channel\AMQPChannel; @@ -31,18 +32,18 @@ class AmqpHandler extends AbstractProcessingHandler /** * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use - * @param string $exchangeName + * @param string $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only * @param int $level * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + public function __construct($exchange, $exchangeName = null, $level = Logger::DEBUG, $bubble = true) { - if ($exchange instanceof AMQPExchange) { - $exchange->setName($exchangeName); - } elseif ($exchange instanceof AMQPChannel) { + if ($exchange instanceof AMQPChannel) { $this->exchangeName = $exchangeName; - } else { + } elseif (!$exchange instanceof AMQPExchange) { throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } elseif ($exchangeName) { + @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); } $this->exchange = $exchange; @@ -62,10 +63,10 @@ class AmqpHandler extends AbstractProcessingHandler $data, $routingKey, 0, - array( + [ 'delivery_mode' => 2, 'content_type' => 'application/json', - ) + ] ); } else { $this->exchange->basic_publish( @@ -113,12 +114,7 @@ class AmqpHandler extends AbstractProcessingHandler */ protected function getRoutingKey(array $record) { - $routingKey = sprintf( - '%s.%s', - // TODO 2.0 remove substr call - substr($record['level_name'], 0, 4), - $record['channel'] - ); + $routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']); return strtolower($routingKey); } @@ -131,17 +127,17 @@ class AmqpHandler extends AbstractProcessingHandler { return new AMQPMessage( (string) $data, - array( + [ 'delivery_mode' => 2, 'content_type' => 'application/json', - ) + ] ); } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); } diff --git a/src/Monolog/Handler/BrowserConsoleHandler.php b/src/Monolog/Handler/BrowserConsoleHandler.php index b3a21bd..4879d24 100644 --- a/src/Monolog/Handler/BrowserConsoleHandler.php +++ b/src/Monolog/Handler/BrowserConsoleHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; /** * Handler sending logs to browser's javascript console with no browser extension required @@ -21,7 +22,7 @@ use Monolog\Formatter\LineFormatter; class BrowserConsoleHandler extends AbstractProcessingHandler { protected static $initialized = false; - protected static $records = array(); + protected static $records = []; /** * {@inheritDoc} @@ -32,7 +33,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler * * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); } @@ -78,7 +79,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler */ public static function reset() { - self::$records = array(); + self::$records = []; } /** @@ -87,7 +88,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler protected function registerShutdownFunction() { if (PHP_SAPI !== 'cli') { - register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); } } @@ -132,7 +133,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler private static function generateScript() { - $script = array(); + $script = []; foreach (self::$records as $record) { $context = self::dump('Context', $record['context']); $extra = self::dump('Extra', $record['extra']); @@ -141,10 +142,10 @@ class BrowserConsoleHandler extends AbstractProcessingHandler $script[] = self::call_array('log', self::handleStyles($record['formatted'])); } else { $script = array_merge($script, - array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))), + [self::call_array('groupCollapsed', self::handleStyles($record['formatted']))], $context, $extra, - array(self::call('groupEnd')) + [self::call('groupEnd')] ); } } @@ -154,7 +155,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler private static function handleStyles($formatted) { - $args = array(self::quote('font-weight: normal')); + $args = [self::quote('font-weight: normal')]; $format = '%c' . $formatted; preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); @@ -173,8 +174,8 @@ class BrowserConsoleHandler extends AbstractProcessingHandler private static function handleCustomStyles($style, $string) { - static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); - static $labels = array(); + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { if (trim($m[1]) === 'autolabel') { @@ -193,7 +194,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler private static function dump($title, array $dict) { - $script = array(); + $script = []; $dict = array_filter($dict); if (empty($dict)) { return $script; diff --git a/src/Monolog/Handler/BufferHandler.php b/src/Monolog/Handler/BufferHandler.php index 72f8953..5ce6c39 100644 --- a/src/Monolog/Handler/BufferHandler.php +++ b/src/Monolog/Handler/BufferHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -21,13 +21,15 @@ use Monolog\Logger; * * @author Christophe Coevoet <stof@notk.org> */ -class BufferHandler extends AbstractHandler +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + protected $handler; protected $bufferSize = 0; protected $bufferLimit; protected $flushOnOverflow; - protected $buffer = array(); + protected $buffer = []; protected $initialized = false; /** @@ -48,7 +50,7 @@ class BufferHandler extends AbstractHandler /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($record['level'] < $this->level) { return false; @@ -56,7 +58,7 @@ class BufferHandler extends AbstractHandler if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors - register_shutdown_function(array($this, 'close')); + register_shutdown_function([$this, 'close']); $this->initialized = true; } @@ -70,9 +72,7 @@ class BufferHandler extends AbstractHandler } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } $this->buffer[] = $record; @@ -112,6 +112,6 @@ class BufferHandler extends AbstractHandler public function clear() { $this->bufferSize = 0; - $this->buffer = array(); + $this->buffer = []; } } diff --git a/src/Monolog/Handler/ChromePHPHandler.php b/src/Monolog/Handler/ChromePHPHandler.php index 785cb0c..4c6be69 100644 --- a/src/Monolog/Handler/ChromePHPHandler.php +++ b/src/Monolog/Handler/ChromePHPHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; /** @@ -32,7 +33,7 @@ class ChromePHPHandler extends AbstractProcessingHandler * Header name */ const HEADER_NAME = 'X-ChromeLogger-Data'; - + /** * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) */ @@ -49,11 +50,11 @@ class ChromePHPHandler extends AbstractProcessingHandler */ protected static $overflowed = false; - protected static $json = array( + protected static $json = [ 'version' => self::VERSION, - 'columns' => array('label', 'log', 'backtrace', 'type'), - 'rows' => array(), - ); + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; protected static $sendHeaders = true; @@ -74,7 +75,7 @@ class ChromePHPHandler extends AbstractProcessingHandler */ public function handleBatch(array $records) { - $messages = array(); + $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { @@ -93,7 +94,7 @@ class ChromePHPHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ChromePHPFormatter(); } @@ -131,7 +132,7 @@ class ChromePHPHandler extends AbstractProcessingHandler return; } - self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; } $json = @json_encode(self::$json); @@ -139,15 +140,15 @@ class ChromePHPHandler extends AbstractProcessingHandler if (strlen($data) > 240 * 1024) { self::$overflowed = true; - $record = array( + $record = [ 'message' => 'Incomplete logs, chrome header size limit reached', - 'context' => array(), + 'context' => [], 'level' => Logger::WARNING, 'level_name' => Logger::getLevelName(Logger::WARNING), 'channel' => 'monolog', - 'datetime' => new \DateTime(), - 'extra' => array(), - ); + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ]; self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); $json = @json_encode(self::$json); $data = base64_encode(utf8_encode($json)); @@ -173,39 +174,13 @@ class ChromePHPHandler extends AbstractProcessingHandler /** * Verifies if the headers are accepted by the current user agent - * - * @return Boolean */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (empty($_SERVER['HTTP_USER_AGENT'])) { return false; } - return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); - } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; } } diff --git a/src/Monolog/Handler/CouchDBHandler.php b/src/Monolog/Handler/CouchDBHandler.php index cc98697..e0603f3 100644 --- a/src/Monolog/Handler/CouchDBHandler.php +++ b/src/Monolog/Handler/CouchDBHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,6 +11,7 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; use Monolog\Formatter\JsonFormatter; use Monolog\Logger; @@ -23,15 +24,15 @@ class CouchDBHandler extends AbstractProcessingHandler { private $options; - public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + public function __construct(array $options = [], $level = Logger::DEBUG, $bubble = true) { - $this->options = array_merge(array( + $this->options = array_merge([ 'host' => 'localhost', 'port' => 5984, 'dbname' => 'logger', 'username' => null, 'password' => null, - ), $options); + ], $options); parent::__construct($level, $bubble); } @@ -47,17 +48,17 @@ class CouchDBHandler extends AbstractProcessingHandler } $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; - $context = stream_context_create(array( - 'http' => array( + $context = stream_context_create([ + 'http' => [ 'method' => 'POST', 'content' => $record['formatted'], 'ignore_errors' => true, 'max_redirects' => 0, 'header' => 'Content-type: application/json', - ), - )); + ], + ]); - if (false === @file_get_contents($url, null, $context)) { + if (false === @file_get_contents($url, false, $context)) { throw new \RuntimeException(sprintf('Could not connect to %s', $url)); } } @@ -65,7 +66,7 @@ class CouchDBHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); } diff --git a/src/Monolog/Handler/CubeHandler.php b/src/Monolog/Handler/CubeHandler.php index 96b3ca0..ab6e007 100644 --- a/src/Monolog/Handler/CubeHandler.php +++ b/src/Monolog/Handler/CubeHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -26,7 +26,7 @@ class CubeHandler extends AbstractProcessingHandler private $scheme; private $host; private $port; - private $acceptedSchemes = array('http', 'udp'); + private $acceptedSchemes = ['http', 'udp']; /** * Create a Cube handler @@ -105,7 +105,7 @@ class CubeHandler extends AbstractProcessingHandler { $date = $record['datetime']; - $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; unset($record['datetime']); if (isset($record['context']['type'])) { @@ -141,10 +141,10 @@ class CubeHandler extends AbstractProcessingHandler } curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); - curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length: ' . strlen('['.$data.']'), - )); + ]); Curl\Util::execute($this->httpConnection, 5, false); } diff --git a/src/Monolog/Handler/Curl/Util.php b/src/Monolog/Handler/Curl/Util.php index 48d30b3..b0bec3d 100644 --- a/src/Monolog/Handler/Curl/Util.php +++ b/src/Monolog/Handler/Curl/Util.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -13,7 +13,7 @@ namespace Monolog\Handler\Curl; class Util { - private static $retriableErrorCodes = array( + private static $retriableErrorCodes = [ CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_HTTP_NOT_FOUND, @@ -21,7 +21,7 @@ class Util CURLE_OPERATION_TIMEOUTED, CURLE_HTTP_POST_ERROR, CURLE_SSL_CONNECT_ERROR, - ); + ]; /** * Executes a CURL request with optional retries and exception on failure diff --git a/src/Monolog/Handler/DeduplicationHandler.php b/src/Monolog/Handler/DeduplicationHandler.php index 7778c22..235b3b8 100644 --- a/src/Monolog/Handler/DeduplicationHandler.php +++ b/src/Monolog/Handler/DeduplicationHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -81,7 +81,6 @@ class DeduplicationHandler extends BufferHandler foreach ($this->buffer as $record) { if ($record['level'] >= $this->deduplicationLevel) { - $passthru = $passthru || !$this->isDuplicate($record); if ($passthru) { $this->appendRecord($record); @@ -139,13 +138,13 @@ class DeduplicationHandler extends BufferHandler $handle = fopen($this->deduplicationStore, 'rw+'); flock($handle, LOCK_EX); - $validLogs = array(); + $validLogs = []; $timestampValidity = time() - $this->time; while (!feof($handle)) { $log = fgets($handle); - if (substr($log, 0, 10) >= $timestampValidity) { + if ($log && substr($log, 0, 10) >= $timestampValidity) { $validLogs[] = $log; } } diff --git a/src/Monolog/Handler/DoctrineCouchDBHandler.php b/src/Monolog/Handler/DoctrineCouchDBHandler.php index b91ffec..13a08ee 100644 --- a/src/Monolog/Handler/DoctrineCouchDBHandler.php +++ b/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -13,6 +13,7 @@ namespace Monolog\Handler; use Monolog\Logger; use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; use Doctrine\CouchDB\CouchDBClient; /** @@ -38,7 +39,7 @@ class DoctrineCouchDBHandler extends AbstractProcessingHandler $this->client->postDocument($record['formatted']); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter; } diff --git a/src/Monolog/Handler/DynamoDbHandler.php b/src/Monolog/Handler/DynamoDbHandler.php index 237b71f..a6991fa 100644 --- a/src/Monolog/Handler/DynamoDbHandler.php +++ b/src/Monolog/Handler/DynamoDbHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -13,6 +13,7 @@ namespace Monolog\Handler; use Aws\Sdk; use Aws\DynamoDb\DynamoDbClient; +use Monolog\Formatter\FormatterInterface; use Aws\DynamoDb\Marshaler; use Monolog\Formatter\ScalarFormatter; use Monolog\Logger; @@ -80,10 +81,10 @@ class DynamoDbHandler extends AbstractProcessingHandler $formatted = $this->client->formatAttributes($filtered); } - $this->client->putItem(array( + $this->client->putItem([ 'TableName' => $this->table, 'Item' => $formatted, - )); + ]); } /** @@ -100,7 +101,7 @@ class DynamoDbHandler extends AbstractProcessingHandler /** * {@inheritdoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ScalarFormatter(self::DATE_FORMAT); } diff --git a/src/Monolog/Handler/ElasticSearchHandler.php b/src/Monolog/Handler/ElasticSearchHandler.php index 8196740..fbb999f 100644 --- a/src/Monolog/Handler/ElasticSearchHandler.php +++ b/src/Monolog/Handler/ElasticSearchHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -43,7 +43,7 @@ class ElasticSearchHandler extends AbstractProcessingHandler /** * @var array Handler config options */ - protected $options = array(); + protected $options = []; /** * @param Client $client Elastica Client object @@ -51,16 +51,16 @@ class ElasticSearchHandler extends AbstractProcessingHandler * @param int $level The minimum logging level at which this handler will be triggered * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, $bubble = true) { parent::__construct($level, $bubble); $this->client = $client; $this->options = array_merge( - array( + [ 'index' => 'monolog', // Elastic index name 'type' => 'record', // Elastic document type 'ignore_error' => false, // Suppress Elastica exceptions - ), + ], $options ); } @@ -70,13 +70,13 @@ class ElasticSearchHandler extends AbstractProcessingHandler */ protected function write(array $record) { - $this->bulkSend(array($record['formatted'])); + $this->bulkSend([$record['formatted']]); } /** * {@inheritdoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { if ($formatter instanceof ElasticaFormatter) { return parent::setFormatter($formatter); @@ -96,7 +96,7 @@ class ElasticSearchHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ElasticaFormatter($this->options['index'], $this->options['type']); } diff --git a/src/Monolog/Handler/ErrorLogHandler.php b/src/Monolog/Handler/ErrorLogHandler.php index 1447a58..2c80139 100644 --- a/src/Monolog/Handler/ErrorLogHandler.php +++ b/src/Monolog/Handler/ErrorLogHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; /** @@ -51,16 +52,16 @@ class ErrorLogHandler extends AbstractProcessingHandler */ public static function getAvailableTypes() { - return array( + return [ self::OPERATING_SYSTEM, self::SAPI, - ); + ]; } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); } @@ -70,13 +71,14 @@ class ErrorLogHandler extends AbstractProcessingHandler */ protected function write(array $record) { - if ($this->expandNewlines) { - $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); - foreach ($lines as $line) { - error_log($line, $this->messageType); - } - } else { + if (!$this->expandNewlines) { error_log((string) $record['formatted'], $this->messageType); + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + foreach ($lines as $line) { + error_log($line, $this->messageType); } } } diff --git a/src/Monolog/Handler/FilterHandler.php b/src/Monolog/Handler/FilterHandler.php index 2a0f7fd..da0634a 100644 --- a/src/Monolog/Handler/FilterHandler.php +++ b/src/Monolog/Handler/FilterHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -21,8 +21,10 @@ use Monolog\Logger; * @author Hennadiy Verkh * @author Jordi Boggiano <j.boggiano@seld.be> */ -class FilterHandler extends AbstractHandler +class FilterHandler extends Handler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + /** * Handler or factory callable($record, $this) * @@ -64,7 +66,7 @@ class FilterHandler extends AbstractHandler /** * @return array */ - public function getAcceptedLevels() + public function getAcceptedLevels(): array { return array_flip($this->acceptedLevels); } @@ -90,7 +92,7 @@ class FilterHandler extends AbstractHandler /** * {@inheritdoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return isset($this->acceptedLevels[$record['level']]); } @@ -98,7 +100,7 @@ class FilterHandler extends AbstractHandler /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; @@ -113,9 +115,7 @@ class FilterHandler extends AbstractHandler } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } $this->handler->handle($record); @@ -128,7 +128,7 @@ class FilterHandler extends AbstractHandler */ public function handleBatch(array $records) { - $filtered = array(); + $filtered = []; foreach ($records as $record) { if ($this->isHandling($record)) { $filtered[] = $record; diff --git a/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php index c3e42ef..f9cab61 100644 --- a/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php +++ b/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. diff --git a/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php index 2a2a64d..63a14cb 100644 --- a/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php +++ b/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -42,7 +42,7 @@ class ChannelLevelActivationStrategy implements ActivationStrategyInterface * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any * @param array $channelToActionLevel An array that maps channel names to action levels. */ - public function __construct($defaultActionLevel, $channelToActionLevel = array()) + public function __construct($defaultActionLevel, $channelToActionLevel = []) { $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); diff --git a/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php index 6e63085..d0ebd84 100644 --- a/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php +++ b/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. diff --git a/src/Monolog/Handler/FingersCrossedHandler.php b/src/Monolog/Handler/FingersCrossedHandler.php index d1dcaac..f0151d1 100644 --- a/src/Monolog/Handler/FingersCrossedHandler.php +++ b/src/Monolog/Handler/FingersCrossedHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -27,13 +27,15 @@ use Monolog\Logger; * * @author Jordi Boggiano <j.boggiano@seld.be> */ -class FingersCrossedHandler extends AbstractHandler +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + protected $handler; protected $activationStrategy; protected $buffering = true; protected $bufferSize; - protected $buffer = array(); + protected $buffer = []; protected $stopBuffering; protected $passthruLevel; @@ -74,7 +76,7 @@ class FingersCrossedHandler extends AbstractHandler /** * {@inheritdoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return true; } @@ -96,18 +98,16 @@ class FingersCrossedHandler extends AbstractHandler } } $this->handler->handleBatch($this->buffer); - $this->buffer = array(); + $this->buffer = []; } /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } if ($this->buffering) { @@ -137,7 +137,7 @@ class FingersCrossedHandler extends AbstractHandler }); if (count($this->buffer) > 0) { $this->handler->handleBatch($this->buffer); - $this->buffer = array(); + $this->buffer = []; } } } @@ -157,7 +157,7 @@ class FingersCrossedHandler extends AbstractHandler */ public function clear() { - $this->buffer = array(); + $this->buffer = []; $this->reset(); } } diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php index fee4795..ac7230c 100644 --- a/src/Monolog/Handler/FirePHPHandler.php +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Formatter\WildfireFormatter; +use Monolog\Formatter\FormatterInterface; /** * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. @@ -60,26 +61,24 @@ class FirePHPHandler extends AbstractProcessingHandler * @param string $message Log message * @return array Complete header string ready for the client as key and message as value */ - protected function createHeader(array $meta, $message) + protected function createHeader(array $meta, string $message): array { $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); - return array($header => $message); + return [$header => $message]; } /** * Creates message header from record * * @see createHeader() - * @param array $record - * @return string */ - protected function createRecordHeader(array $record) + protected function createRecordHeader(array $record): array { // Wildfire is extensible to support multiple protocols & plugins in a single request, // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. return $this->createHeader( - array(1, 1, 1, self::$messageIndex++), + [1, 1, 1, self::$messageIndex++], $record['formatted'] ); } @@ -87,7 +86,7 @@ class FirePHPHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new WildfireFormatter(); } @@ -103,9 +102,9 @@ class FirePHPHandler extends AbstractProcessingHandler { // Initial payload consists of required headers for Wildfire return array_merge( - $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), - $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), - $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + $this->createHeader(['Protocol', 1], self::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], self::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], self::PLUGIN_URI) ); } @@ -168,28 +167,4 @@ class FirePHPHandler extends AbstractProcessingHandler return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; - } } diff --git a/src/Monolog/Handler/FleepHookHandler.php b/src/Monolog/Handler/FleepHookHandler.php index c43c013..748100b 100644 --- a/src/Monolog/Handler/FleepHookHandler.php +++ b/src/Monolog/Handler/FleepHookHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,6 +11,7 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; use Monolog\Formatter\LineFormatter; use Monolog\Logger; @@ -63,7 +64,7 @@ class FleepHookHandler extends SocketHandler * * @return LineFormatter */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(null, null, true, true); } @@ -117,9 +118,9 @@ class FleepHookHandler extends SocketHandler */ private function buildContent($record) { - $dataArray = array( + $dataArray = [ 'message' => $record['formatted'], - ); + ]; return http_build_query($dataArray); } diff --git a/src/Monolog/Handler/FlowdockHandler.php b/src/Monolog/Handler/FlowdockHandler.php index dd9a361..67d1291 100644 --- a/src/Monolog/Handler/FlowdockHandler.php +++ b/src/Monolog/Handler/FlowdockHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -53,7 +53,7 @@ class FlowdockHandler extends SocketHandler /** * {@inheritdoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { if (!$formatter instanceof FlowdockFormatter) { throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); @@ -65,9 +65,9 @@ class FlowdockHandler extends SocketHandler /** * Gets the default formatter. * - * @return FormatterInterface + * @suppress PhanTypeMissingReturn */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); } diff --git a/src/Monolog/Handler/FormattableHandlerInterface.php b/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 0000000..fc1693c --- /dev/null +++ b/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,37 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/src/Monolog/Handler/FormattableHandlerTrait.php b/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 0000000..2b7e56f --- /dev/null +++ b/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,60 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +trait FormattableHandlerTrait +{ + /** + * @var FormatterInterface + */ + protected $formatter; + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/src/Monolog/Handler/GelfHandler.php b/src/Monolog/Handler/GelfHandler.php index d3847d8..b5c3a8c 100644 --- a/src/Monolog/Handler/GelfHandler.php +++ b/src/Monolog/Handler/GelfHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,12 +11,10 @@ namespace Monolog\Handler; -use Gelf\IMessagePublisher; use Gelf\PublisherInterface; -use Gelf\Publisher; -use InvalidArgumentException; use Monolog\Logger; use Monolog\Formatter\GelfMessageFormatter; +use Monolog\Formatter\FormatterInterface; /** * Handler to send messages to a Graylog2 (http://www.graylog2.org) server @@ -27,23 +25,19 @@ use Monolog\Formatter\GelfMessageFormatter; class GelfHandler extends AbstractProcessingHandler { /** - * @var Publisher the publisher object that sends the message to the server + * @var PublisherInterface|null the publisher object that sends the message to the server */ protected $publisher; /** - * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param PublisherInterface $publisher a publisher object + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct($publisher, $level = Logger::DEBUG, $bubble = true) + public function __construct(PublisherInterface $publisher, $level = Logger::DEBUG, $bubble = true) { parent::__construct($level, $bubble); - if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) { - throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance'); - } - $this->publisher = $publisher; } @@ -66,7 +60,7 @@ class GelfHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new GelfMessageFormatter(); } diff --git a/src/Monolog/Handler/GroupHandler.php b/src/Monolog/Handler/GroupHandler.php index 663f5a9..ef1485f 100644 --- a/src/Monolog/Handler/GroupHandler.php +++ b/src/Monolog/Handler/GroupHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -18,8 +18,10 @@ use Monolog\Formatter\FormatterInterface; * * @author Lenar Lõhmus <lenar@city.ee> */ -class GroupHandler extends AbstractHandler +class GroupHandler extends Handler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + protected $handlers; /** @@ -41,7 +43,7 @@ class GroupHandler extends AbstractHandler /** * {@inheritdoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -55,12 +57,10 @@ class GroupHandler extends AbstractHandler /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { @@ -76,7 +76,7 @@ class GroupHandler extends AbstractHandler public function handleBatch(array $records) { if ($this->processors) { - $processed = array(); + $processed = []; foreach ($records as $record) { foreach ($this->processors as $processor) { $processed[] = call_user_func($processor, $record); diff --git a/src/Monolog/Handler/Handler.php b/src/Monolog/Handler/Handler.php new file mode 100644 index 0000000..347e7b7 --- /dev/null +++ b/src/Monolog/Handler/Handler.php @@ -0,0 +1,53 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + return array_keys(get_object_vars($this)); + } +} diff --git a/src/Monolog/Handler/HandlerInterface.php b/src/Monolog/Handler/HandlerInterface.php index d920c4b..472fd31 100644 --- a/src/Monolog/Handler/HandlerInterface.php +++ b/src/Monolog/Handler/HandlerInterface.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,8 +11,6 @@ namespace Monolog\Handler; -use Monolog\Formatter\FormatterInterface; - /** * Interface that all Monolog Handlers must implement * @@ -33,7 +31,7 @@ interface HandlerInterface * * @return Boolean */ - public function isHandling(array $record); + public function isHandling(array $record): bool; /** * Handles a record. @@ -49,7 +47,7 @@ interface HandlerInterface * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. * false means the record was either not processed or that this handler allows bubbling. */ - public function handle(array $record); + public function handle(array $record): bool; /** * Handles a set of records at once. @@ -59,32 +57,12 @@ interface HandlerInterface public function handleBatch(array $records); /** - * Adds a processor in the stack. - * - * @param callable $callback - * @return self - */ - public function pushProcessor($callback); - - /** - * Removes the processor on top of the stack and returns it. - * - * @return callable - */ - public function popProcessor(); - - /** - * Sets the formatter. + * Closes the handler. * - * @param FormatterInterface $formatter - * @return self - */ - public function setFormatter(FormatterInterface $formatter); - - /** - * Gets the formatter. + * This will be called automatically when the object is destroyed if you extend Monolog\Handler\Handler * - * @return FormatterInterface + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. */ - public function getFormatter(); + public function close(); } diff --git a/src/Monolog/Handler/HandlerWrapper.php b/src/Monolog/Handler/HandlerWrapper.php index e540d80..28fe57d 100644 --- a/src/Monolog/Handler/HandlerWrapper.php +++ b/src/Monolog/Handler/HandlerWrapper.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -30,7 +30,7 @@ use Monolog\Formatter\FormatterInterface; * * @author Alexey Karapetov <alexey@karapetov.com> */ -class HandlerWrapper implements HandlerInterface +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface { /** * @var HandlerInterface @@ -49,7 +49,7 @@ class HandlerWrapper implements HandlerInterface /** * {@inheritdoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->handler->isHandling($record); } @@ -57,7 +57,7 @@ class HandlerWrapper implements HandlerInterface /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { return $this->handler->handle($record); } @@ -73,36 +73,58 @@ class HandlerWrapper implements HandlerInterface /** * {@inheritdoc} */ - public function pushProcessor($callback) + public function close() { - $this->handler->pushProcessor($callback); + return $this->handler->close(); + } - return $this; + /** + * {@inheritdoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** * {@inheritdoc} */ - public function popProcessor() + public function popProcessor(): callable { - return $this->handler->popProcessor(); + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** * {@inheritdoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->handler->setFormatter($formatter); + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + } - return $this; + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } /** * {@inheritdoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->handler->getFormatter(); + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter($formatter); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } } diff --git a/src/Monolog/Handler/HipChatHandler.php b/src/Monolog/Handler/HipChatHandler.php index 73049f3..98441f3 100644 --- a/src/Monolog/Handler/HipChatHandler.php +++ b/src/Monolog/Handler/HipChatHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -16,12 +16,13 @@ use Monolog\Logger; /** * Sends notifications through the hipchat api to a hipchat room * + * This handler only supports the API v2 + * * Notes: * API token - HipChat API token * Room - HipChat Room Id or name, where messages are sent * Name - Name used to send the message (from) * notify - Should the message trigger a notification in the clients - * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) * * @author Rafael Dohms <rafael@doh.ms> * @see https://www.hipchat.com/docs/api @@ -29,16 +30,6 @@ use Monolog\Logger; class HipChatHandler extends SocketHandler { /** - * Use API version 1 - */ - const API_V1 = 'v1'; - - /** - * Use API version v2 - */ - const API_V2 = 'v2'; - - /** * The maximum allowed length for the name used in the "from" field. */ const MAXIMUM_NAME_LENGTH = 15; @@ -79,28 +70,18 @@ class HipChatHandler extends SocketHandler private $host; /** - * @var string + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. */ - private $version; - - /** - * @param string $token HipChat API Token - * @param string $room The room that should be alerted of the message (Id or Name) - * @param string $name Name used in the "from" field. - * @param bool $notify Trigger a notification in clients or not - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $useSSL Whether to connect via SSL. - * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) - * @param string $host The HipChat server hostname. - * @param string $version The HipChat API version (default HipChatHandler::API_V1) - */ - public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com') { - if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { - throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); - } - $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; parent::__construct($connectionString, $level, $bubble); @@ -110,7 +91,6 @@ class HipChatHandler extends SocketHandler $this->room = $room; $this->format = $format; $this->host = $host; - $this->version = $version; } /** @@ -134,14 +114,12 @@ class HipChatHandler extends SocketHandler */ private function buildContent($record) { - $dataArray = array( - 'notify' => $this->version == self::API_V1 ? - ($this->notify ? 1 : 0) : - ($this->notify ? 'true' : 'false'), + $dataArray = [ + 'notify' => $this->notify ? 'true' : 'false', 'message' => $record['formatted'], 'message_format' => $this->format, 'color' => $this->getAlertColor($record['level']), - ); + ]; if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { if (function_exists('mb_substr')) { @@ -151,14 +129,9 @@ class HipChatHandler extends SocketHandler } } - // if we are using the legacy API then we need to send some additional information - if ($this->version == self::API_V1) { - $dataArray['room_id'] = $this->room; - } - // append the sender name if it is set // always append it if we use the v1 api (it is required in v1) - if ($this->version == self::API_V1 || $this->name !== null) { + if ($this->name !== null) { $dataArray['from'] = (string) $this->name; } @@ -173,13 +146,9 @@ class HipChatHandler extends SocketHandler */ private function buildHeader($content) { - if ($this->version == self::API_V1) { - $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; - } else { - // needed for rooms with special (spaces, etc) characters in the name - $room = rawurlencode($this->room); - $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; - } + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; $header .= "Host: {$this->host}\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; @@ -259,9 +228,9 @@ class HipChatHandler extends SocketHandler private function combineRecords($records) { $batchRecord = null; - $batchRecords = array(); - $messages = array(); - $formattedMessages = array(); + $batchRecords = []; + $messages = []; + $formattedMessages = []; $level = 0; $levelName = null; $datetime = null; @@ -283,12 +252,12 @@ class HipChatHandler extends SocketHandler $formattedMessages[] = $this->getFormatter()->format($record); $formattedMessageStr = implode('', $formattedMessages); - $batchRecord = array( + $batchRecord = [ 'message' => $messageStr, 'formatted' => $formattedMessageStr, - 'context' => array(), - 'extra' => array(), - ); + 'context' => [], + 'extra' => [], + ]; if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { // Pop the last message and implode the remaining messages @@ -298,8 +267,8 @@ class HipChatHandler extends SocketHandler $batchRecord['formatted'] = implode('', $formattedMessages); $batchRecords[] = $batchRecord; - $messages = array($lastMessage); - $formattedMessages = array($lastFormattedMessage); + $messages = [$lastMessage]; + $formattedMessages = [$lastFormattedMessage]; $batchRecord = null; } @@ -313,11 +282,11 @@ class HipChatHandler extends SocketHandler foreach ($batchRecords as &$batchRecord) { $batchRecord = array_merge( $batchRecord, - array( + [ 'level' => $level, 'level_name' => $levelName, 'datetime' => $datetime, - ) + ] ); } diff --git a/src/Monolog/Handler/IFTTTHandler.php b/src/Monolog/Handler/IFTTTHandler.php index d60a3c8..46792ee 100644 --- a/src/Monolog/Handler/IFTTTHandler.php +++ b/src/Monolog/Handler/IFTTTHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -48,11 +48,11 @@ class IFTTTHandler extends AbstractProcessingHandler */ public function write(array $record) { - $postData = array( + $postData = [ "value1" => $record["channel"], "value2" => $record["level_name"], "value3" => $record["message"], - ); + ]; $postString = json_encode($postData); $ch = curl_init(); @@ -60,9 +60,9 @@ class IFTTTHandler extends AbstractProcessingHandler curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( + curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", - )); + ]); Curl\Util::execute($ch); } diff --git a/src/Monolog/Handler/LogEntriesHandler.php b/src/Monolog/Handler/LogEntriesHandler.php index 494c605..c74b9d2 100644 --- a/src/Monolog/Handler/LogEntriesHandler.php +++ b/src/Monolog/Handler/LogEntriesHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. diff --git a/src/Monolog/Handler/LogglyHandler.php b/src/Monolog/Handler/LogglyHandler.php index bcd62e1..544e7c7 100644 --- a/src/Monolog/Handler/LogglyHandler.php +++ b/src/Monolog/Handler/LogglyHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; use Monolog\Formatter\LogglyFormatter; /** @@ -29,7 +30,7 @@ class LogglyHandler extends AbstractProcessingHandler protected $token; - protected $tag = array(); + protected $tag = []; public function __construct($token, $level = Logger::DEBUG, $bubble = true) { @@ -44,14 +45,14 @@ class LogglyHandler extends AbstractProcessingHandler public function setTag($tag) { - $tag = !empty($tag) ? $tag : array(); - $this->tag = is_array($tag) ? $tag : array($tag); + $tag = !empty($tag) ? $tag : []; + $this->tag = is_array($tag) ? $tag : [$tag]; } public function addTag($tag) { if (!empty($tag)) { - $tag = is_array($tag) ? $tag : array($tag); + $tag = is_array($tag) ? $tag : [$tag]; $this->tag = array_unique(array_merge($this->tag, $tag)); } } @@ -78,7 +79,7 @@ class LogglyHandler extends AbstractProcessingHandler { $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); - $headers = array('Content-Type: application/json'); + $headers = ['Content-Type: application/json']; if (!empty($this->tag)) { $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); @@ -95,7 +96,7 @@ class LogglyHandler extends AbstractProcessingHandler Curl\Util::execute($ch); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LogglyFormatter(); } diff --git a/src/Monolog/Handler/LogmaticHandler.php b/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 0000000..9747220 --- /dev/null +++ b/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,88 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; + +/** + * @author Julien Breux <julien.breux@gmail.com> + */ +class LogmaticHandler extends SocketHandler +{ + /** + * @var string + */ + private $logToken; + + /** + * @var string + */ + private $hostname; + + /** + * @var string + */ + private $appname; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appname Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param int|string $level The minimum logging level to trigger this handler. + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct(string $token, string $hostname = '', string $appname = '', bool $useSSL = true, $level = Logger::DEBUG, bool $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct($endpoint, $level, $bubble); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appname = $appname; + } + + /** + * {@inheritdoc} + */ + protected function generateDataStream($record): string + { + return $this->logToken . ' ' . $record['formatted']; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if (!empty($this->hostname)) { + $formatter->setHostname($this->hostname); + } + if (!empty($this->appname)) { + $formatter->setAppname($this->appname); + } + + return $formatter; + } +} diff --git a/src/Monolog/Handler/MailHandler.php b/src/Monolog/Handler/MailHandler.php index 9e23283..634fbc1 100644 --- a/src/Monolog/Handler/MailHandler.php +++ b/src/Monolog/Handler/MailHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,6 +11,9 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\HtmlFormatter; + /** * Base class for all mail handlers * @@ -23,7 +26,7 @@ abstract class MailHandler extends AbstractProcessingHandler */ public function handleBatch(array $records) { - $messages = array(); + $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { @@ -43,14 +46,14 @@ abstract class MailHandler extends AbstractProcessingHandler * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content */ - abstract protected function send($content, array $records); + abstract protected function send(string $content, array $records); /** * {@inheritdoc} */ protected function write(array $record) { - $this->send((string) $record['formatted'], array($record)); + $this->send((string) $record['formatted'], [$record]); } protected function getHighestRecord(array $records) @@ -64,4 +67,19 @@ abstract class MailHandler extends AbstractProcessingHandler return $highestRecord; } + + protected function isHtmlBody($body) + { + return substr($body, 0, 1) === '<'; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } } diff --git a/src/Monolog/Handler/MandrillHandler.php b/src/Monolog/Handler/MandrillHandler.php index ab95924..066c649 100644 --- a/src/Monolog/Handler/MandrillHandler.php +++ b/src/Monolog/Handler/MandrillHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -46,10 +46,15 @@ class MandrillHandler extends MailHandler /** * {@inheritdoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records) { + $mime = null; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + $message = clone $this->message; - $message->setBody($content); + $message->setBody($content, $mime); $message->setDate(time()); $ch = curl_init(); @@ -57,11 +62,11 @@ class MandrillHandler extends MailHandler curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'key' => $this->apiKey, 'raw_message' => (string) $message, 'async' => false, - ))); + ])); Curl\Util::execute($ch); } diff --git a/src/Monolog/Handler/MissingExtensionException.php b/src/Monolog/Handler/MissingExtensionException.php index 4724a7e..1554b34 100644 --- a/src/Monolog/Handler/MissingExtensionException.php +++ b/src/Monolog/Handler/MissingExtensionException.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. diff --git a/src/Monolog/Handler/MongoDBHandler.php b/src/Monolog/Handler/MongoDBHandler.php index 56fe755..f302d31 100644 --- a/src/Monolog/Handler/MongoDBHandler.php +++ b/src/Monolog/Handler/MongoDBHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,49 +11,75 @@ namespace Monolog\Handler; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use MongoDB\Client; use Monolog\Logger; -use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\MongoDBFormatter; /** * Logs to a MongoDB database. * - * usage example: + * Usage example: * - * $log = new Logger('application'); - * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log = new \Monolog\Logger('application'); + * $client = new \MongoDB\Client('mongodb://localhost:27017'); + * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); * $log->pushHandler($mongodb); * - * @author Thomas Tourlourat <thomas@tourlourat.com> + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. */ class MongoDBHandler extends AbstractProcessingHandler { - protected $mongoCollection; + private $collection; + private $manager; + private $namespace; - public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($mongodb, $database, $collection, $level = Logger::DEBUG, $bubble = true) { - if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { - throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { + throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required'); } - $this->mongoCollection = $mongo->selectCollection($database, $collection); + if ($mongodb instanceof Client) { + $this->collection = $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } parent::__construct($level, $bubble); } protected function write(array $record) { - if ($this->mongoCollection instanceof \MongoDB\Collection) { - $this->mongoCollection->insertOne($record["formatted"]); - } else { - $this->mongoCollection->save($record["formatted"]); + if (isset($this->collection)) { + $this->collection->insertOne($record['formatted']); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record["formatted"]); + $this->manager->executeBulkWrite($this->namespace, $bulk); } } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new NormalizerFormatter(); + return new MongoDBFormatter; } } diff --git a/src/Monolog/Handler/NativeMailerHandler.php b/src/Monolog/Handler/NativeMailerHandler.php index d7807fd..b597761 100644 --- a/src/Monolog/Handler/NativeMailerHandler.php +++ b/src/Monolog/Handler/NativeMailerHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -38,13 +38,13 @@ class NativeMailerHandler extends MailHandler * Optional headers for the message * @var array */ - protected $headers = array(); + protected $headers = []; /** * Optional parameters for the message * @var array */ - protected $parameters = array(); + protected $parameters = []; /** * The wordwrap length for the message @@ -56,7 +56,7 @@ class NativeMailerHandler extends MailHandler * The Content-type for the message * @var string */ - protected $contentType = 'text/plain'; + protected $contentType; /** * The encoding for the message @@ -75,7 +75,7 @@ class NativeMailerHandler extends MailHandler public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) { parent::__construct($level, $bubble); - $this->to = is_array($to) ? $to : array($to); + $this->to = (array) $to; $this->subject = $subject; $this->addHeader(sprintf('From: %s', $from)); $this->maxColumnWidth = $maxColumnWidth; @@ -115,12 +115,17 @@ class NativeMailerHandler extends MailHandler /** * {@inheritdoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records) { - $content = wordwrap($content, $this->maxColumnWidth); + $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); - $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; - if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { $headers .= 'MIME-Version: 1.0' . "\r\n"; } diff --git a/src/Monolog/Handler/NewRelicHandler.php b/src/Monolog/Handler/NewRelicHandler.php index 6718e9e..fb03778 100644 --- a/src/Monolog/Handler/NewRelicHandler.php +++ b/src/Monolog/Handler/NewRelicHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -13,6 +13,7 @@ namespace Monolog\Handler; use Monolog\Logger; use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; /** * Class to record a log on a NewRelic application. @@ -195,7 +196,7 @@ class NewRelicHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } diff --git a/src/Monolog/Handler/NoopHandler.php b/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 0000000..8ee2b4c --- /dev/null +++ b/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,40 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers <roelharbers@gmail.com> + */ +class NoopHandler extends Handler +{ + /** + * {@inheritdoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record): bool + { + return false; + } +} diff --git a/src/Monolog/Handler/NullHandler.php b/src/Monolog/Handler/NullHandler.php index 4b84588..93678e8 100644 --- a/src/Monolog/Handler/NullHandler.php +++ b/src/Monolog/Handler/NullHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -21,20 +21,30 @@ use Monolog\Logger; * * @author Jordi Boggiano <j.boggiano@seld.be> */ -class NullHandler extends AbstractHandler +class NullHandler extends Handler { + private $level; + /** * @param int $level The minimum logging level at which this handler will be triggered */ - public function __construct($level = Logger::DEBUG) + public function __construct(int $level = Logger::DEBUG) + { + $this->level = $level; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record): bool { - parent::__construct($level, false); + return $record['level'] >= $this->level; } /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($record['level'] < $this->level) { return false; diff --git a/src/Monolog/Handler/PHPConsoleHandler.php b/src/Monolog/Handler/PHPConsoleHandler.php index 1f2076a..c2b7b3e 100644 --- a/src/Monolog/Handler/PHPConsoleHandler.php +++ b/src/Monolog/Handler/PHPConsoleHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,11 +11,11 @@ namespace Monolog\Handler; -use Exception; use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; use PhpConsole\Connector; -use PhpConsole\Handler; +use PhpConsole\Handler as VendorPhpConsoleHandler; use PhpConsole\Helper; /** @@ -32,17 +32,17 @@ use PhpConsole\Helper; * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); * \Monolog\ErrorHandler::register($logger); * echo $undefinedVar; - * $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); * PC::debug($_SERVER); // PHP Console debugger for any type of vars * * @author Sergey Barbushin https://www.linkedin.com/in/barbushin */ class PHPConsoleHandler extends AbstractProcessingHandler { - private $options = array( + private $options = [ 'enabled' => true, // bool Is PHP Console server enabled - 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... - 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled 'useOwnErrorsHandler' => false, // bool Enable errors handling 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths @@ -51,7 +51,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'headersLimit' => null, // int|null Set headers size limit for your web-server 'password' => null, // string|null Protect PHP Console connection by password 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed - 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level @@ -60,22 +60,22 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) - ); + ]; /** @var Connector */ private $connector; /** - * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details - * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) - * @param int $level - * @param bool $bubble - * @throws Exception + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @param int $level + * @param bool $bubble + * @throws \RuntimeException */ - public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + public function __construct(array $options = [], Connector $connector = null, $level = Logger::DEBUG, $bubble = true) { if (!class_exists('PhpConsole\Connector')) { - throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); } parent::__construct($level, $bubble); $this->options = $this->initOptions($options); @@ -86,7 +86,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler { $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); if ($wrongOptions) { - throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); } return array_replace($this->options, $options); @@ -107,7 +107,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler if ($this->options['enabled'] && $connector->isActiveClient()) { if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { - $handler = Handler::getInstance(); + $handler = VendorPhpConsoleHandler::getInstance(); $handler->setHandleErrors($this->options['useOwnErrorsHandler']); $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); $handler->start(); @@ -157,7 +157,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler return $this->options; } - public function handle(array $record) + public function handle(array $record): bool { if ($this->options['enabled'] && $this->connector->isActiveClient()) { return parent::handle($record); @@ -176,7 +176,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler { if ($record['level'] < Logger::NOTICE) { $this->handleDebugRecord($record); - } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { $this->handleExceptionRecord($record); } else { $this->handleErrorRecord($record); @@ -203,10 +203,10 @@ class PHPConsoleHandler extends AbstractProcessingHandler $context = $record['context']; $this->connector->getErrorsDispatcher()->dispatchError( - isset($context['code']) ? $context['code'] : null, - isset($context['message']) ? $context['message'] : $record['message'], - isset($context['file']) ? $context['file'] : null, - isset($context['line']) ? $context['line'] : null, + $context['code'] ?? null, + $context['message'] ?? $record['message'], + $context['file'] ?? null, + $context['line'] ?? null, $this->options['classesPartialsTraceIgnore'] ); } @@ -235,7 +235,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('%message%'); } diff --git a/src/Monolog/Handler/ProcessHandler.php b/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 0000000..8cf6087 --- /dev/null +++ b/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,202 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + * <pre> + * $log = new Logger('myLogger'); + * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php')); + * </pre> + * + * @author Kolja Zuelsdorf <koljaz@web.de> + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + /** + * @var string + */ + private $command; + + /** + * @var string + */ + private $cwd; + + /** + * @var array + */ + private $pipes = []; + + /** + * @var array + */ + const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|int $level The minimum logging level at which this handler will be triggered. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not. + * @param string $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @throws \UnexpectedValueException + * @return void + */ + protected function write(array $record) + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record['formatted']); + + $errors = $this->readProcessErrors(); + if (empty($errors) === false) { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + * + * @return void + */ + private function ensureProcessIsStarted() + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + * + * @return void + */ + private function startProcess() + { + $this->process = proc_open($this->command, self::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + * @return void + */ + private function handleStartupErrors() + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || empty($errors) === false) { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors() + { + return stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + * @param $string + * @return void + */ + protected function writeProcessInput($string) + { + fwrite($this->pipes[0], (string) $string); + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/src/Monolog/Handler/ProcessableHandlerInterface.php b/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 0000000..9556f98 --- /dev/null +++ b/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,36 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return HandlerInterface self + */ + public function pushProcessor(callable $callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @throws \LogicException In case the processor stack is empty + * @return callable + */ + public function popProcessor(): callable; +} diff --git a/src/Monolog/Handler/ProcessableHandlerTrait.php b/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 0000000..bba940e --- /dev/null +++ b/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,62 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + */ + protected $processors = []; + + /** + * {@inheritdoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } +} diff --git a/src/Monolog/Handler/PsrHandler.php b/src/Monolog/Handler/PsrHandler.php index 1ae8584..415b45f 100644 --- a/src/Monolog/Handler/PsrHandler.php +++ b/src/Monolog/Handler/PsrHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -43,7 +43,7 @@ class PsrHandler extends AbstractHandler /** * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; diff --git a/src/Monolog/Handler/PushoverHandler.php b/src/Monolog/Handler/PushoverHandler.php index bba7200..b53ce6c 100644 --- a/src/Monolog/Handler/PushoverHandler.php +++ b/src/Monolog/Handler/PushoverHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -37,7 +37,7 @@ class PushoverHandler extends SocketHandler * @see https://pushover.net/api * @var array */ - private $parameterNames = array( + private $parameterNames = [ 'token' => true, 'user' => true, 'message' => true, @@ -51,18 +51,18 @@ class PushoverHandler extends SocketHandler 'retry' => true, 'expire' => true, 'callback' => true, - ); + ]; /** * Sounds the api supports by default * @see https://pushover.net/api#sounds * @var array */ - private $sounds = array( + private $sounds = [ 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', 'persistent', 'echo', 'updown', 'none', - ); + ]; /** * @param string $token Pushover api token @@ -110,13 +110,13 @@ class PushoverHandler extends SocketHandler $timestamp = $record['datetime']->getTimestamp(); - $dataArray = array( + $dataArray = [ 'token' => $this->token, 'user' => $this->user, 'message' => $message, 'title' => $this->title, 'timestamp' => $timestamp, - ); + ]; if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { $dataArray['priority'] = 2; diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php index 53a8b39..1c8e718 100644 --- a/src/Monolog/Handler/RavenHandler.php +++ b/src/Monolog/Handler/RavenHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -27,7 +27,7 @@ class RavenHandler extends AbstractProcessingHandler /** * Translates Monolog log levels to Raven log levels. */ - private $logLevels = array( + private $logLevels = [ Logger::DEBUG => Raven_Client::DEBUG, Logger::INFO => Raven_Client::INFO, Logger::NOTICE => Raven_Client::INFO, @@ -36,7 +36,7 @@ class RavenHandler extends AbstractProcessingHandler Logger::CRITICAL => Raven_Client::FATAL, Logger::ALERT => Raven_Client::FATAL, Logger::EMERGENCY => Raven_Client::FATAL, - ); + ]; /** * @var string should represent the current version of the calling @@ -92,7 +92,7 @@ class RavenHandler extends AbstractProcessingHandler }); // the other ones are added as a context item - $logs = array(); + $logs = []; foreach ($records as $r) { $logs[] = $this->processRecord($r); } @@ -133,10 +133,11 @@ class RavenHandler extends AbstractProcessingHandler */ protected function write(array $record) { + /** @var bool|null|array This is false, unless set below to null or an array of data, when we read the current user context */ $previousUserContext = false; - $options = array(); + $options = []; $options['level'] = $this->logLevels[$record['level']]; - $options['tags'] = array(); + $options['tags'] = []; if (!empty($record['extra']['tags'])) { $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); unset($record['extra']['tags']); @@ -156,7 +157,7 @@ class RavenHandler extends AbstractProcessingHandler $options['logger'] = $record['channel']; } foreach ($this->getExtraParameters() as $key) { - foreach (array('extra', 'context') as $source) { + foreach (['extra', 'context'] as $source) { if (!empty($record[$source][$key])) { $options[$key] = $record[$source][$key]; unset($record[$source][$key]); @@ -179,14 +180,15 @@ class RavenHandler extends AbstractProcessingHandler $options['release'] = $this->release; } - if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { $options['extra']['message'] = $record['formatted']; $this->ravenClient->captureException($record['context']['exception'], $options); } else { - $this->ravenClient->captureMessage($record['formatted'], array(), $options); + $this->ravenClient->captureMessage($record['formatted'], [], $options); } - if ($previousUserContext !== false) { + // restore the user context if it was modified + if (!is_bool($previousUserContext)) { $this->ravenClient->user_context($previousUserContext); } } @@ -194,7 +196,7 @@ class RavenHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('[%channel%] %message%'); } @@ -216,11 +218,11 @@ class RavenHandler extends AbstractProcessingHandler */ protected function getExtraParameters() { - return array('checksum', 'release', 'event_id'); + return ['checksum', 'release', 'event_id']; } /** - * @param string $value + * @param string $value * @return self */ public function setRelease($value) diff --git a/src/Monolog/Handler/RedisHandler.php b/src/Monolog/Handler/RedisHandler.php index 590f996..e7ddb44 100644 --- a/src/Monolog/Handler/RedisHandler.php +++ b/src/Monolog/Handler/RedisHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,6 +12,7 @@ namespace Monolog\Handler; use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; /** @@ -36,9 +37,9 @@ class RedisHandler extends AbstractProcessingHandler * @param string $key The key name to push records to * @param int $level The minimum logging level at which this handler will be triggered * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param int $capSize Number of entries to limit list size to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited */ - public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0) { if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { throw new \InvalidArgumentException('Predis\Client or Redis instance required'); @@ -90,7 +91,7 @@ class RedisHandler extends AbstractProcessingHandler /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(); } diff --git a/src/Monolog/Handler/RollbarHandler.php b/src/Monolog/Handler/RollbarHandler.php index 6c8a3e3..5b6678e 100644 --- a/src/Monolog/Handler/RollbarHandler.php +++ b/src/Monolog/Handler/RollbarHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -12,7 +12,7 @@ namespace Monolog\Handler; use RollbarNotifier; -use Exception; +use Throwable; use Monolog\Logger; /** @@ -40,7 +40,7 @@ class RollbarHandler extends AbstractProcessingHandler */ protected $rollbarNotifier; - protected $levelMap = array( + protected $levelMap = [ Logger::DEBUG => 'debug', Logger::INFO => 'info', Logger::NOTICE => 'info', @@ -49,7 +49,7 @@ class RollbarHandler extends AbstractProcessingHandler Logger::CRITICAL => 'critical', Logger::ALERT => 'critical', Logger::EMERGENCY => 'critical', - ); + ]; /** * Records whether any log records have been added since the last flush of the rollbar notifier @@ -84,19 +84,19 @@ class RollbarHandler extends AbstractProcessingHandler } $context = $record['context']; - $payload = array(); + $payload = []; if (isset($context['payload'])) { $payload = $context['payload']; unset($context['payload']); } - $context = array_merge($context, $record['extra'], array( + $context = array_merge($context, $record['extra'], [ 'level' => $this->levelMap[$record['level']], 'monolog_level' => $record['level_name'], 'channel' => $record['channel'], 'datetime' => $record['datetime']->format('U'), - )); + ]); - if (isset($context['exception']) && $context['exception'] instanceof Exception) { + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { $payload['level'] = $context['level']; $exception = $context['exception']; unset($context['exception']); diff --git a/src/Monolog/Handler/RotatingFileHandler.php b/src/Monolog/Handler/RotatingFileHandler.php index 3b60b3d..a4a5ba4 100644 --- a/src/Monolog/Handler/RotatingFileHandler.php +++ b/src/Monolog/Handler/RotatingFileHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,6 +11,7 @@ namespace Monolog\Handler; +use InvalidArgumentException; use Monolog\Logger; /** @@ -47,7 +48,7 @@ class RotatingFileHandler extends StreamHandler { $this->filename = $filename; $this->maxFiles = (int) $maxFiles; - $this->nextRotation = new \DateTime('tomorrow'); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); $this->filenameFormat = '{filename}-{date}'; $this->dateFormat = 'Y-m-d'; @@ -69,18 +70,16 @@ class RotatingFileHandler extends StreamHandler public function setFilenameFormat($filenameFormat, $dateFormat) { if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { - trigger_error( + throw new InvalidArgumentException( 'Invalid date format - format must be one of '. 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. - 'date formats using slashes, underscores and/or dots instead of dashes.', - E_USER_DEPRECATED + 'date formats using slashes, underscores and/or dots instead of dashes.' ); } if (substr_count($filenameFormat, '{date}') === 0) { - trigger_error( - 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', - E_USER_DEPRECATED + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' ); } $this->filenameFormat = $filenameFormat; @@ -114,7 +113,7 @@ class RotatingFileHandler extends StreamHandler { // update filename $this->url = $this->getTimedFilename(); - $this->nextRotation = new \DateTime('tomorrow'); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); // skip GC of old logs if files are unlimited if (0 === $this->maxFiles) { @@ -136,7 +135,8 @@ class RotatingFileHandler extends StreamHandler if (is_writable($file)) { // suppress errors here as unlink() might fail if two processes // are cleaning up/rotating at the same time - set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + }); unlink($file); restore_error_handler(); } @@ -149,8 +149,8 @@ class RotatingFileHandler extends StreamHandler { $fileInfo = pathinfo($this->filename); $timedFilename = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], date($this->dateFormat)), + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat ); @@ -165,8 +165,8 @@ class RotatingFileHandler extends StreamHandler { $fileInfo = pathinfo($this->filename); $glob = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], '*'), + ['{filename}', '{date}'], + [$fileInfo['filename'], '*'], $fileInfo['dirname'] . '/' . $this->filenameFormat ); if (!empty($fileInfo['extension'])) { diff --git a/src/Monolog/Handler/SamplingHandler.php b/src/Monolog/Handler/SamplingHandler.php index 9509ae3..8a25cbb 100644 --- a/src/Monolog/Handler/SamplingHandler.php +++ b/src/Monolog/Handler/SamplingHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -25,8 +25,10 @@ namespace Monolog\Handler; * @author Bryan Davis <bd808@wikimedia.org> * @author Kunal Mehta <legoktm@gmail.com> */ -class SamplingHandler extends AbstractHandler +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + /** * @var callable|HandlerInterface $handler */ @@ -52,12 +54,12 @@ class SamplingHandler extends AbstractHandler } } - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->handler->isHandling($record); } - public function handle(array $record) + public function handle(array $record): bool { if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { // The same logic as in FingersCrossedHandler @@ -69,9 +71,7 @@ class SamplingHandler extends AbstractHandler } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } $this->handler->handle($record); diff --git a/src/Monolog/Handler/SendGridHandler.php b/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 0000000..7d82d94 --- /dev/null +++ b/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,100 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli <ricardo.fontanelli@hotmail.com> + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @var string + */ + protected $apiUser; + + /** + * The SendGrid API Key + * @var string + */ + protected $apiKey; + + /** + * The email addresses to which the message will be sent + * @var string + */ + protected $from; + + /** + * The email addresses to which the message will be sent + * @var array + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|array $to The recipients of the email + * @param string $subject The subject of the mail + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, int $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * {@inheritdoc} + */ + protected function send(string $content, array $records) + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 38bc838..d9e6a4c 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -89,7 +89,7 @@ class SlackRecord { $this->channel = $channel; $this->username = $username; - $this->userIcon = trim($userIcon, ':'); + $this->userIcon = $userIcon !== null ? trim($userIcon, ':') : null; $this->useAttachment = $useAttachment; $this->useShortAttachment = $useShortAttachment; $this->includeContextAndExtra = $includeContextAndExtra; @@ -127,7 +127,7 @@ class SlackRecord 'color' => $this->getAttachmentColor($record['level']), 'fields' => array(), 'mrkdwn_in' => array('fields'), - 'ts' => $record['datetime']->getTimestamp() + 'ts' => $record['datetime']->getTimestamp(), ); if ($this->useShortAttachment) { @@ -137,7 +137,6 @@ class SlackRecord $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); } - if ($this->includeContextAndExtra) { foreach (array('extra', 'context') as $key) { if (empty($record[$key])) { @@ -229,7 +228,7 @@ class SlackRecord /** * Generates attachment field * - * @param string $title + * @param string $title * @param string|array $value\ * * @return array @@ -243,7 +242,7 @@ class SlackRecord return array( 'title' => $title, 'value' => $value, - 'short' => false + 'short' => false, ); } diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index 3ac4d83..a99872f 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -197,7 +197,7 @@ class SlackHandler extends SocketHandler return $this->slackRecord->stringify($fields); } - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); @@ -205,7 +205,7 @@ class SlackHandler extends SocketHandler return $this; } - public function getFormatter() + public function getFormatter(): FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); diff --git a/src/Monolog/Handler/SlackWebhookHandler.php b/src/Monolog/Handler/SlackWebhookHandler.php index 9a1bbb4..a2ea52a 100644 --- a/src/Monolog/Handler/SlackWebhookHandler.php +++ b/src/Monolog/Handler/SlackWebhookHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -36,16 +36,16 @@ class SlackWebhookHandler extends AbstractProcessingHandler private $slackRecord; /** - * @param string $webhookUrl Slack Webhook URL - * @param string|null $channel Slack channel (encoded ID or name) - * @param string|null $username Name of a bot - * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) - * @param string|null $iconEmoji The emoji name to use (or null) - * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style - * @param bool $includeContextAndExtra Whether the attachment should include context and extra data - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] */ public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = array()) { @@ -97,7 +97,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler Curl\Util::execute($ch); } - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); @@ -105,7 +105,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler return $this; } - public function getFormatter() + public function getFormatter(): FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); diff --git a/src/Monolog/Handler/SlackbotHandler.php b/src/Monolog/Handler/SlackbotHandler.php index baead52..dd2db59 100644 --- a/src/Monolog/Handler/SlackbotHandler.php +++ b/src/Monolog/Handler/SlackbotHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -40,11 +40,11 @@ class SlackbotHandler extends AbstractProcessingHandler private $channel; /** - * @param string $slackTeam Slack team slug - * @param string $token Slackbot token - * @param string $channel Slack channel (encoded ID or name) - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) { diff --git a/src/Monolog/Handler/SocketHandler.php b/src/Monolog/Handler/SocketHandler.php index 7a61bf4..e2161ff 100644 --- a/src/Monolog/Handler/SocketHandler.php +++ b/src/Monolog/Handler/SocketHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -23,8 +23,11 @@ class SocketHandler extends AbstractProcessingHandler { private $connectionString; private $connectionTimeout; + /** @var resource|null */ private $resource; + /** @var float */ private $timeout = 0; + /** @var float */ private $writingTimeout = 10; private $lastSentBytes = null; private $persistent = false; @@ -149,20 +152,16 @@ class SocketHandler extends AbstractProcessingHandler /** * Get current connection timeout setting - * - * @return float */ - public function getConnectionTimeout() + public function getConnectionTimeout(): float { return $this->connectionTimeout; } /** * Get current in-transfer timeout - * - * @return float */ - public function getTimeout() + public function getTimeout(): float { return $this->timeout; } @@ -172,7 +171,7 @@ class SocketHandler extends AbstractProcessingHandler * * @return float */ - public function getWritingTimeout() + public function getWritingTimeout(): float { return $this->writingTimeout; } @@ -216,7 +215,7 @@ class SocketHandler extends AbstractProcessingHandler $seconds = floor($this->timeout); $microseconds = round(($this->timeout - $seconds) * 1e6); - return stream_set_timeout($this->resource, $seconds, $microseconds); + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); } /** diff --git a/src/Monolog/Handler/SqsHandler.php b/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 0000000..01c70dc --- /dev/null +++ b/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,53 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Logger; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker <git@amvc.nl> + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** @var SqsClient */ + private $client; + /** @var string */ + private $queueUrl; + + public function __construct(SqsClient $sqsClient, $queueUrl, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * Writes the record down to the log of the implementing handler. + * + * @param array $record + */ + protected function write(array $record) + { + if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string'); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $record['formatted'], + ]); + } +} diff --git a/src/Monolog/Handler/StreamHandler.php b/src/Monolog/Handler/StreamHandler.php index 09a1573..5abbc59 100644 --- a/src/Monolog/Handler/StreamHandler.php +++ b/src/Monolog/Handler/StreamHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -22,8 +22,10 @@ use Monolog\Logger; */ class StreamHandler extends AbstractProcessingHandler { + /** @var resource|null */ protected $stream; protected $url; + /** @var string|null */ private $errorMessage; protected $filePermission; protected $useLocking; @@ -96,7 +98,7 @@ class StreamHandler extends AbstractProcessingHandler } $this->createDir(); $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); + set_error_handler([$this, 'customErrorHandler']); $this->stream = fopen($this->url, 'a'); if ($this->filePermission !== null) { @chmod($this->url, $this->filePermission); @@ -123,7 +125,7 @@ class StreamHandler extends AbstractProcessingHandler /** * Write to stream * @param resource $stream - * @param array $record + * @param array $record */ protected function streamWrite($stream, array $record) { @@ -164,7 +166,7 @@ class StreamHandler extends AbstractProcessingHandler $dir = $this->getDirFromStream($this->url); if (null !== $dir && !is_dir($dir)) { $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); + set_error_handler([$this, 'customErrorHandler']); $status = mkdir($dir, 0777, true); restore_error_handler(); if (false === $status) { diff --git a/src/Monolog/Handler/SwiftMailerHandler.php b/src/Monolog/Handler/SwiftMailerHandler.php index 72f44a5..3359ac8 100644 --- a/src/Monolog/Handler/SwiftMailerHandler.php +++ b/src/Monolog/Handler/SwiftMailerHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -13,6 +13,7 @@ namespace Monolog\Handler; use Monolog\Logger; use Monolog\Formatter\LineFormatter; +use Swift_Message; use Swift; /** @@ -26,12 +27,12 @@ class SwiftMailerHandler extends MailHandler private $messageTemplate; /** - * @param \Swift_Mailer $mailer The mailer to use - * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + * @param int|string $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, bool $bubble = true) { parent::__construct($level, $bubble); @@ -42,7 +43,7 @@ class SwiftMailerHandler extends MailHandler /** * {@inheritdoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records) { $this->mailer->send($this->buildMessage($content, $records)); } @@ -50,21 +51,21 @@ class SwiftMailerHandler extends MailHandler /** * Creates instance of Swift_Message to be sent * - * @param string $content formatted email body to be sent - * @param array $records Log records that formed the content - * @return \Swift_Message + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return Swift_Message */ - protected function buildMessage($content, array $records) + protected function buildMessage(string $content, array $records): Swift_Message { $message = null; - if ($this->messageTemplate instanceof \Swift_Message) { + if ($this->messageTemplate instanceof Swift_Message) { $message = clone $this->messageTemplate; $message->generateId(); } elseif (is_callable($this->messageTemplate)) { $message = call_user_func($this->messageTemplate, $content, $records); } - if (!$message instanceof \Swift_Message) { + if (!$message instanceof Swift_Message) { throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); } @@ -73,7 +74,12 @@ class SwiftMailerHandler extends MailHandler $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); } - $message->setBody($content); + $mime = null; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message->setBody($content, $mime); if (version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { @@ -82,18 +88,4 @@ class SwiftMailerHandler extends MailHandler return $message; } - - /** - * BC getter, to be removed in 2.0 - */ - public function __get($name) - { - if ($name === 'message') { - trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); - - return $this->buildMessage(null, array()); - } - - throw new \InvalidArgumentException('Invalid property '.$name); - } } diff --git a/src/Monolog/Handler/SyslogHandler.php b/src/Monolog/Handler/SyslogHandler.php index 376bc3b..863fc56 100644 --- a/src/Monolog/Handler/SyslogHandler.php +++ b/src/Monolog/Handler/SyslogHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. diff --git a/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/src/Monolog/Handler/SyslogUdp/UdpSocket.php index 3bff085..f1801b3 100644 --- a/src/Monolog/Handler/SyslogUdp/UdpSocket.php +++ b/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -17,6 +17,8 @@ class UdpSocket protected $ip; protected $port; + + /** @var resource|null */ protected $socket; public function __construct($ip, $port = 514) @@ -42,7 +44,7 @@ class UdpSocket protected function send($chunk) { if (!is_resource($this->socket)) { - throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); } socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); } diff --git a/src/Monolog/Handler/SyslogUdpHandler.php b/src/Monolog/Handler/SyslogUdpHandler.php index 4718711..2258f56 100644 --- a/src/Monolog/Handler/SyslogUdpHandler.php +++ b/src/Monolog/Handler/SyslogUdpHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -57,19 +57,19 @@ class SyslogUdpHandler extends AbstractSyslogHandler $this->socket->close(); } - private function splitMessageIntoLines($message) + private function splitMessageIntoLines($message): array { if (is_array($message)) { $message = implode("\n", $message); } - return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + return preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); } /** * Make common syslog header (see rfc5424) */ - protected function makeCommonSyslogHeader($severity) + protected function makeCommonSyslogHeader($severity): string { $priority = $severity + $this->facility; @@ -96,7 +96,7 @@ class SyslogUdpHandler extends AbstractSyslogHandler /** * Inject your own socket, mainly used for testing */ - public function setSocket($socket) + public function setSocket(UdpSocket $socket) { $this->socket = $socket; } diff --git a/src/Monolog/Handler/TestHandler.php b/src/Monolog/Handler/TestHandler.php index e39cfc6..3df7b6d 100644 --- a/src/Monolog/Handler/TestHandler.php +++ b/src/Monolog/Handler/TestHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -65,8 +65,8 @@ namespace Monolog\Handler; */ class TestHandler extends AbstractProcessingHandler { - protected $records = array(); - protected $recordsByLevel = array(); + protected $records = []; + protected $recordsByLevel = []; public function getRecords() { @@ -75,8 +75,8 @@ class TestHandler extends AbstractProcessingHandler public function clear() { - $this->records = array(); - $this->recordsByLevel = array(); + $this->records = []; + $this->recordsByLevel = []; } public function hasRecords($level) @@ -109,12 +109,8 @@ class TestHandler extends AbstractProcessingHandler }, $level); } - public function hasRecordThatPasses($predicate, $level) + public function hasRecordThatPasses(callable $predicate, $level) { - if (!is_callable($predicate)) { - throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); - } - if (!isset($this->recordsByLevel[$level])) { return false; } @@ -145,7 +141,7 @@ class TestHandler extends AbstractProcessingHandler if (method_exists($this, $genericMethod)) { $args[] = $level; - return call_user_func_array(array($this, $genericMethod), $args); + return call_user_func_array([$this, $genericMethod], $args); } } diff --git a/src/Monolog/Handler/WhatFailureGroupHandler.php b/src/Monolog/Handler/WhatFailureGroupHandler.php index 2732ba3..42c2336 100644 --- a/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -22,7 +22,7 @@ class WhatFailureGroupHandler extends GroupHandler /** * {@inheritdoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { foreach ($this->processors as $processor) { @@ -33,8 +33,6 @@ class WhatFailureGroupHandler extends GroupHandler foreach ($this->handlers as $handler) { try { $handler->handle($record); - } catch (\Exception $e) { - // What failure? } catch (\Throwable $e) { // What failure? } @@ -51,8 +49,6 @@ class WhatFailureGroupHandler extends GroupHandler foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); - } catch (\Exception $e) { - // What failure? } catch (\Throwable $e) { // What failure? } diff --git a/src/Monolog/Handler/ZendMonitorHandler.php b/src/Monolog/Handler/ZendMonitorHandler.php index f22cf21..c0bceb4 100644 --- a/src/Monolog/Handler/ZendMonitorHandler.php +++ b/src/Monolog/Handler/ZendMonitorHandler.php @@ -1,4 +1,5 @@ -<?php +<?php declare(strict_types=1); + /* * This file is part of the Monolog package. * @@ -10,6 +11,7 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; use Monolog\Formatter\NormalizerFormatter; use Monolog\Logger; @@ -25,7 +27,7 @@ class ZendMonitorHandler extends AbstractProcessingHandler * * @var array */ - protected $levelMap = array( + protected $levelMap = [ Logger::DEBUG => 1, Logger::INFO => 2, Logger::NOTICE => 3, @@ -34,7 +36,7 @@ class ZendMonitorHandler extends AbstractProcessingHandler Logger::CRITICAL => 6, Logger::ALERT => 7, Logger::EMERGENCY => 0, - ); + ]; /** * Construct @@ -78,7 +80,7 @@ class ZendMonitorHandler extends AbstractProcessingHandler /** * {@inheritdoc} */ - public function getDefaultFormatter() + public function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } diff --git a/src/Monolog/Logger.php b/src/Monolog/Logger.php index 49d00af..88ecf0d 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -11,8 +11,8 @@ namespace Monolog; +use DateTimeZone; use Monolog\Handler\HandlerInterface; -use Monolog\Handler\StreamHandler; use Psr\Log\LoggerInterface; use Psr\Log\InvalidArgumentException; @@ -84,14 +84,16 @@ class Logger implements LoggerInterface * * @var int */ - const API = 1; + const API = 2; /** * Logging levels from syslog protocol defined in RFC 5424 * - * @var array $levels Logging levels + * This is a static variable and not a constant to serve as an extension point for custom levels + * + * @var string[] $levels Logging levels with the levels as key */ - protected static $levels = array( + protected static $levels = [ self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', @@ -100,12 +102,7 @@ class Logger implements LoggerInterface self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY', - ); - - /** - * @var \DateTimeZone - */ - protected static $timezone; + ]; /** * @var string @@ -134,31 +131,33 @@ class Logger implements LoggerInterface protected $microsecondTimestamps = true; /** - * @param string $name The logging channel + * @var DateTimeZone + */ + protected $timezone; + + /** + * @param string $name The logging channel, a simple descriptive name that is attached to all log records * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. * @param callable[] $processors Optional array of processors + * @param DateTimeZone $timezone Optional timezone, if not provided date_default_timezone_get() will be used */ - public function __construct($name, array $handlers = array(), array $processors = array()) + public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone $timezone = null) { $this->name = $name; $this->handlers = $handlers; $this->processors = $processors; + $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC'); } - /** - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } /** * Return a new cloned instance with the name changed - * - * @return static */ - public function withName($name) + public function withName(string $name): self { $new = clone $this; $new->name = $name; @@ -168,11 +167,8 @@ class Logger implements LoggerInterface /** * Pushes a handler on to the stack. - * - * @param HandlerInterface $handler - * @return $this */ - public function pushHandler(HandlerInterface $handler) + public function pushHandler(HandlerInterface $handler): self { array_unshift($this->handlers, $handler); @@ -182,9 +178,9 @@ class Logger implements LoggerInterface /** * Pops a handler from the stack * - * @return HandlerInterface + * @throws \LogicException If empty handler stack */ - public function popHandler() + public function popHandler(): HandlerInterface { if (!$this->handlers) { throw new \LogicException('You tried to pop from an empty handler stack.'); @@ -198,12 +194,11 @@ class Logger implements LoggerInterface * * If a map is passed, keys will be ignored. * - * @param HandlerInterface[] $handlers - * @return $this + * @param HandlerInterface[] $handlers */ - public function setHandlers(array $handlers) + public function setHandlers(array $handlers): self { - $this->handlers = array(); + $this->handlers = []; foreach (array_reverse($handlers) as $handler) { $this->pushHandler($handler); } @@ -214,22 +209,16 @@ class Logger implements LoggerInterface /** * @return HandlerInterface[] */ - public function getHandlers() + public function getHandlers(): array { return $this->handlers; } /** * Adds a processor on to the stack. - * - * @param callable $callback - * @return $this */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): self { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } array_unshift($this->processors, $callback); return $this; @@ -238,9 +227,10 @@ class Logger implements LoggerInterface /** * Removes the processor on top of the stack and returns it. * + * @throws \LogicException If empty processor stack * @return callable */ - public function popProcessor() + public function popProcessor(): callable { if (!$this->processors) { throw new \LogicException('You tried to pop from an empty processor stack.'); @@ -252,7 +242,7 @@ class Logger implements LoggerInterface /** * @return callable[] */ - public function getProcessors() + public function getProcessors(): array { return $this->processors; } @@ -261,18 +251,22 @@ class Logger implements LoggerInterface * Control the use of microsecond resolution timestamps in the 'datetime' * member of new records. * - * Generating microsecond resolution timestamps by calling + * On PHP7.0, generating microsecond resolution timestamps by calling * microtime(true), formatting the result via sprintf() and then parsing * the resulting string via \DateTime::createFromFormat() can incur * a measurable runtime overhead vs simple usage of DateTime to capture * a second resolution timestamp in systems which generate a large number * of log events. * + * On PHP7.1 however microseconds are always included by the engine, so + * this setting can be left alone unless you really want to suppress + * microseconds in the output. + * * @param bool $micro True to use microtime() to create timestamps */ - public function useMicrosecondTimestamps($micro) + public function useMicrosecondTimestamps(bool $micro) { - $this->microsecondTimestamps = (bool) $micro; + $this->microsecondTimestamps = $micro; } /** @@ -283,56 +277,43 @@ class Logger implements LoggerInterface * @param array $context The log context * @return Boolean Whether the record has been processed */ - public function addRecord($level, $message, array $context = array()) + public function addRecord(int $level, string $message, array $context = []): bool { - if (!$this->handlers) { - $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); - } - - $levelName = static::getLevelName($level); - // check if any handler will handle this message so we can return early and save cycles $handlerKey = null; - reset($this->handlers); - while ($handler = current($this->handlers)) { - if ($handler->isHandling(array('level' => $level))) { - $handlerKey = key($this->handlers); + foreach ($this->handlers as $key => $handler) { + if ($handler->isHandling(['level' => $level])) { + $handlerKey = $key; break; } - - next($this->handlers); } if (null === $handlerKey) { return false; } - if (!static::$timezone) { - static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); - } - - // php7.1+ always has microseconds enabled, so we do not need this hack - if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { - $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); - } else { - $ts = new \DateTime(null, static::$timezone); - } - $ts->setTimezone(static::$timezone); + $levelName = static::getLevelName($level); - $record = array( - 'message' => (string) $message, + $record = [ + 'message' => $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, - 'datetime' => $ts, - 'extra' => array(), - ); + 'datetime' => new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + 'extra' => [], + ]; foreach ($this->processors as $processor) { $record = call_user_func($processor, $record); } + // advance the array pointer to the first handler that will handle this record + reset($this->handlers); + while ($handlerKey !== key($this->handlers)) { + next($this->handlers); + } + while ($handler = current($this->handlers)) { if (true === $handler->handle($record)) { break; @@ -345,107 +326,11 @@ class Logger implements LoggerInterface } /** - * Adds a log record at the DEBUG level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addDebug($message, array $context = array()) - { - return $this->addRecord(static::DEBUG, $message, $context); - } - - /** - * Adds a log record at the INFO level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addInfo($message, array $context = array()) - { - return $this->addRecord(static::INFO, $message, $context); - } - - /** - * Adds a log record at the NOTICE level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addNotice($message, array $context = array()) - { - return $this->addRecord(static::NOTICE, $message, $context); - } - - /** - * Adds a log record at the WARNING level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addWarning($message, array $context = array()) - { - return $this->addRecord(static::WARNING, $message, $context); - } - - /** - * Adds a log record at the ERROR level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addError($message, array $context = array()) - { - return $this->addRecord(static::ERROR, $message, $context); - } - - /** - * Adds a log record at the CRITICAL level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addCritical($message, array $context = array()) - { - return $this->addRecord(static::CRITICAL, $message, $context); - } - - /** - * Adds a log record at the ALERT level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addAlert($message, array $context = array()) - { - return $this->addRecord(static::ALERT, $message, $context); - } - - /** - * Adds a log record at the EMERGENCY level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addEmergency($message, array $context = array()) - { - return $this->addRecord(static::EMERGENCY, $message, $context); - } - - /** * Gets all supported logging levels. * * @return array Assoc array with human-readable level names => level codes. */ - public static function getLevels() + public static function getLevels(): array { return array_flip(static::$levels); } @@ -453,10 +338,11 @@ class Logger implements LoggerInterface /** * Gets the name of the logging level. * - * @param int $level + * @param int $level + * @throws \Psr\Log\InvalidArgumentException If level is not defined * @return string */ - public static function getLevelName($level) + public static function getLevelName(int $level): string { if (!isset(static::$levels[$level])) { throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); @@ -469,12 +355,17 @@ class Logger implements LoggerInterface * Converts PSR-3 levels to Monolog ones if necessary * * @param string|int Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined * @return int */ - public static function toMonologLevel($level) + public static function toMonologLevel($level): int { - if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { - return constant(__CLASS__.'::'.strtoupper($level)); + if (is_string($level)) { + if (defined(__CLASS__.'::'.strtoupper($level))) { + return constant(__CLASS__.'::'.strtoupper($level)); + } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); } return $level; @@ -486,11 +377,11 @@ class Logger implements LoggerInterface * @param int $level * @return Boolean */ - public function isHandling($level) + public function isHandling(int $level): bool { - $record = array( + $record = [ 'level' => $level, - ); + ]; foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -506,16 +397,15 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param mixed $level The log level - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context */ - public function log($level, $message, array $context = array()) + public function log($level, $message, array $context = []) { $level = static::toMonologLevel($level); - return $this->addRecord($level, $message, $context); + $this->addRecord($level, (string) $message, $context); } /** @@ -523,13 +413,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function debug($message, array $context = array()) + public function debug($message, array $context = []) { - return $this->addRecord(static::DEBUG, $message, $context); + $this->addRecord(static::DEBUG, (string) $message, $context); } /** @@ -537,13 +426,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function info($message, array $context = array()) + public function info($message, array $context = []) { - return $this->addRecord(static::INFO, $message, $context); + $this->addRecord(static::INFO, (string) $message, $context); } /** @@ -551,13 +439,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function notice($message, array $context = array()) + public function notice($message, array $context = []) { - return $this->addRecord(static::NOTICE, $message, $context); + $this->addRecord(static::NOTICE, (string) $message, $context); } /** @@ -565,41 +452,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function warn($message, array $context = array()) - { - return $this->addRecord(static::WARNING, $message, $context); - } - - /** - * Adds a log record at the WARNING level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function warning($message, array $context = array()) - { - return $this->addRecord(static::WARNING, $message, $context); - } - - /** - * Adds a log record at the ERROR level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function err($message, array $context = array()) + public function warning($message, array $context = []) { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::WARNING, (string) $message, $context); } /** @@ -607,13 +465,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function error($message, array $context = array()) + public function error($message, array $context = []) { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::ERROR, (string) $message, $context); } /** @@ -621,27 +478,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function crit($message, array $context = array()) + public function critical($message, array $context = []) { - return $this->addRecord(static::CRITICAL, $message, $context); - } - - /** - * Adds a log record at the CRITICAL level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function critical($message, array $context = array()) - { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->addRecord(static::CRITICAL, (string) $message, $context); } /** @@ -649,13 +491,12 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function alert($message, array $context = array()) + public function alert($message, array $context = []) { - return $this->addRecord(static::ALERT, $message, $context); + $this->addRecord(static::ALERT, (string) $message, $context); } /** @@ -663,38 +504,33 @@ class Logger implements LoggerInterface * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string $message The log message + * @param array $context The log context */ - public function emerg($message, array $context = array()) + public function emergency($message, array $context = []) { - return $this->addRecord(static::EMERGENCY, $message, $context); + $this->addRecord(static::EMERGENCY, (string) $message, $context); } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. + * Set the timezone to be used for the timestamp of log records. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param DateTimeZone $tz Timezone object */ - public function emergency($message, array $context = array()) + public function setTimezone(DateTimeZone $tz): self { - return $this->addRecord(static::EMERGENCY, $message, $context); + $this->timezone = $tz; + + return $this; } /** * Set the timezone to be used for the timestamp of log records. * - * This is stored globally for all Logger instances - * - * @param \DateTimeZone $tz Timezone object + * @return DateTimeZone */ - public static function setTimezone(\DateTimeZone $tz) + public function getTimezone(): DateTimeZone { - self::$timezone = $tz; + return $this->timezone; } } diff --git a/src/Monolog/Processor/GitProcessor.php b/src/Monolog/Processor/GitProcessor.php index 1899400..9eec186 100644 --- a/src/Monolog/Processor/GitProcessor.php +++ b/src/Monolog/Processor/GitProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -29,11 +29,7 @@ class GitProcessor $this->level = Logger::toMonologLevel($level); } - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -45,7 +41,7 @@ class GitProcessor return $record; } - private static function getGitInfo() + private static function getGitInfo(): array { if (self::$cache) { return self::$cache; @@ -53,12 +49,12 @@ class GitProcessor $branches = `git branch -v --no-abbrev`; if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { - return self::$cache = array( + return self::$cache = [ 'branch' => $matches[1], 'commit' => $matches[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/src/Monolog/Processor/IntrospectionProcessor.php b/src/Monolog/Processor/IntrospectionProcessor.php index 2c07cae..3a27529 100644 --- a/src/Monolog/Processor/IntrospectionProcessor.php +++ b/src/Monolog/Processor/IntrospectionProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -32,23 +32,19 @@ class IntrospectionProcessor private $skipStackFramesCount; - private $skipFunctions = array( + private $skipFunctions = [ 'call_user_func', 'call_user_func_array', - ); + ]; - public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) { $this->level = Logger::toMonologLevel($level); - $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); $this->skipStackFramesCount = $skipStackFramesCount; } - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -90,18 +86,18 @@ class IntrospectionProcessor // we should have the call source now $record['extra'] = array_merge( $record['extra'], - array( + [ 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, - ) + ] ); return $record; } - private function isTraceClassOrSkippedFunction(array $trace, $index) + private function isTraceClassOrSkippedFunction(array $trace, int $index) { if (!isset($trace[$index])) { return false; diff --git a/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/src/Monolog/Processor/MemoryPeakUsageProcessor.php index 0543e92..b6f8acc 100644 --- a/src/Monolog/Processor/MemoryPeakUsageProcessor.php +++ b/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -19,11 +19,7 @@ namespace Monolog\Processor; */ class MemoryPeakUsageProcessor extends MemoryProcessor { - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { $bytes = memory_get_peak_usage($this->realUsage); $formatted = $this->formatBytes($bytes); diff --git a/src/Monolog/Processor/MemoryProcessor.php b/src/Monolog/Processor/MemoryProcessor.php index 85f9dc5..5b06c2e 100644 --- a/src/Monolog/Processor/MemoryProcessor.php +++ b/src/Monolog/Processor/MemoryProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -32,22 +32,20 @@ abstract class MemoryProcessor * @param bool $realUsage Set this to true to get the real size of memory allocated from system. * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) */ - public function __construct($realUsage = true, $useFormatting = true) + public function __construct(bool $realUsage = true, bool $useFormatting = true) { - $this->realUsage = (boolean) $realUsage; - $this->useFormatting = (boolean) $useFormatting; + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; } /** * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is * * @param int $bytes - * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int */ - protected function formatBytes($bytes) + protected function formatBytes(int $bytes) { - $bytes = (int) $bytes; - if (!$this->useFormatting) { return $bytes; } diff --git a/src/Monolog/Processor/MemoryUsageProcessor.php b/src/Monolog/Processor/MemoryUsageProcessor.php index 2783d65..31c9396 100644 --- a/src/Monolog/Processor/MemoryUsageProcessor.php +++ b/src/Monolog/Processor/MemoryUsageProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -19,11 +19,7 @@ namespace Monolog\Processor; */ class MemoryUsageProcessor extends MemoryProcessor { - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { $bytes = memory_get_usage($this->realUsage); $formatted = $this->formatBytes($bytes); diff --git a/src/Monolog/Processor/MercurialProcessor.php b/src/Monolog/Processor/MercurialProcessor.php index 7c07a7e..6ab5bba 100644 --- a/src/Monolog/Processor/MercurialProcessor.php +++ b/src/Monolog/Processor/MercurialProcessor.php @@ -1,9 +1,9 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. * - * (c) Jonathan A. Schweder <jonathanschweder@gmail.com> + * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -28,11 +28,7 @@ class MercurialProcessor $this->level = Logger::toMonologLevel($level); } - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -44,20 +40,21 @@ class MercurialProcessor return $record; } - private static function getMercurialInfo() + private static function getMercurialInfo(): array { if (self::$cache) { return self::$cache; } $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { - return self::$cache = array( + return self::$cache = [ 'branch' => $result[1], 'revision' => $result[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/src/Monolog/Processor/ProcessIdProcessor.php b/src/Monolog/Processor/ProcessIdProcessor.php index 9d3f559..392bd13 100644 --- a/src/Monolog/Processor/ProcessIdProcessor.php +++ b/src/Monolog/Processor/ProcessIdProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -18,11 +18,7 @@ namespace Monolog\Processor; */ class ProcessIdProcessor { - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { $record['extra']['process_id'] = getmypid(); diff --git a/src/Monolog/Processor/PsrLogMessageProcessor.php b/src/Monolog/Processor/PsrLogMessageProcessor.php index c2686ce..8078403 100644 --- a/src/Monolog/Processor/PsrLogMessageProcessor.php +++ b/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -20,20 +20,34 @@ namespace Monolog\Processor; */ class PsrLogMessageProcessor { + const SIMPLE_DATE = "Y-m-d\TH:i:sP"; + + private $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(string $dateFormat = null) + { + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; + } + /** * @param array $record * @return array */ - public function __invoke(array $record) + public function __invoke(array $record): array { if (false === strpos($record['message'], '{')) { return $record; } - $replacements = array(); + $replacements = []; foreach ($record['context'] as $key => $val) { if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { $replacements['{'.$key.'}'] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements['{'.$key.'}'] = $val->format($this->dateFormat); } elseif (is_object($val)) { $replacements['{'.$key.'}'] = '[object '.get_class($val).']'; } else { diff --git a/src/Monolog/Processor/TagProcessor.php b/src/Monolog/Processor/TagProcessor.php index 7e2df2a..6371986 100644 --- a/src/Monolog/Processor/TagProcessor.php +++ b/src/Monolog/Processor/TagProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -20,22 +20,22 @@ class TagProcessor { private $tags; - public function __construct(array $tags = array()) + public function __construct(array $tags = []) { $this->setTags($tags); } - public function addTags(array $tags = array()) + public function addTags(array $tags = []) { $this->tags = array_merge($this->tags, $tags); } - public function setTags(array $tags = array()) + public function setTags(array $tags = []) { $this->tags = $tags; } - public function __invoke(array $record) + public function __invoke(array $record): array { $record['extra']['tags'] = $this->tags; diff --git a/src/Monolog/Processor/UidProcessor.php b/src/Monolog/Processor/UidProcessor.php index 812707c..601171b 100644 --- a/src/Monolog/Processor/UidProcessor.php +++ b/src/Monolog/Processor/UidProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -20,16 +20,16 @@ class UidProcessor { private $uid; - public function __construct($length = 7) + public function __construct(int $length = 7) { if (!is_int($length) || $length > 32 || $length < 1) { throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); } - $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + $this->uid = substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); } - public function __invoke(array $record) + public function __invoke(array $record): array { $record['extra']['uid'] = $this->uid; @@ -39,7 +39,7 @@ class UidProcessor /** * @return string */ - public function getUid() + public function getUid(): string { return $this->uid; } diff --git a/src/Monolog/Processor/WebProcessor.php b/src/Monolog/Processor/WebProcessor.php index ea1d897..37324af 100644 --- a/src/Monolog/Processor/WebProcessor.php +++ b/src/Monolog/Processor/WebProcessor.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -30,13 +30,13 @@ class WebProcessor * * @var array */ - protected $extraFields = array( + protected $extraFields = [ 'url' => 'REQUEST_URI', 'ip' => 'REMOTE_ADDR', 'http_method' => 'REQUEST_METHOD', 'server' => 'SERVER_NAME', 'referrer' => 'HTTP_REFERER', - ); + ]; /** * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data @@ -65,11 +65,7 @@ class WebProcessor } } - /** - * @param array $record - * @return array - */ - public function __invoke(array $record) + public function __invoke(array $record): array { // skip processing if for some reason request data // is not present (CLI or wonky SAPIs) @@ -82,26 +78,17 @@ class WebProcessor return $record; } - /** - * @param string $extraName - * @param string $serverName - * @return $this - */ - public function addExtraField($extraName, $serverName) + public function addExtraField(string $extraName, string $serverName): self { $this->extraFields[$extraName] = $serverName; return $this; } - /** - * @param array $extra - * @return array - */ - private function appendExtraFields(array $extra) + private function appendExtraFields(array $extra): array { foreach ($this->extraFields as $extraName => $serverName) { - $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + $extra[$extraName] = $this->serverData[$serverName] ?? null; } if (isset($this->serverData['UNIQUE_ID'])) { diff --git a/src/Monolog/Registry.php b/src/Monolog/Registry.php index 159b751..99a4344 100644 --- a/src/Monolog/Registry.php +++ b/src/Monolog/Registry.php @@ -1,4 +1,4 @@ -<?php +<?php declare(strict_types=1); /* * This file is part of the Monolog package. @@ -28,8 +28,8 @@ use InvalidArgumentException; * * function testLogger() * { - * Monolog\Registry::api()->addError('Sent to $api Logger instance'); - * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); * } * </code> * @@ -42,7 +42,7 @@ class Registry * * @var Logger[] */ - private static $loggers = array(); + private static $loggers = []; /** * Adds new logging channel to the registry @@ -68,15 +68,15 @@ class Registry * * @param string|Logger $logger Name or logger instance */ - public static function hasLogger($logger) + public static function hasLogger($logger): bool { if ($logger instanceof Logger) { $index = array_search($logger, self::$loggers, true); return false !== $index; - } else { - return isset(self::$loggers[$logger]); } + + return isset(self::$loggers[$logger]); } /** @@ -100,7 +100,7 @@ class Registry */ public static function clear() { - self::$loggers = array(); + self::$loggers = []; } /** @@ -108,9 +108,8 @@ class Registry * * @param string $name Name of the requested Logger instance * @throws \InvalidArgumentException If named Logger instance is not in the registry - * @return Logger Requested instance of Logger */ - public static function getInstance($name) + public static function getInstance($name): Logger { if (!isset(self::$loggers[$name])) { throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); diff --git a/src/Monolog/Test/TestCase.php b/src/Monolog/Test/TestCase.php new file mode 100644 index 0000000..23cf9ad --- /dev/null +++ b/src/Monolog/Test/TestCase.php @@ -0,0 +1,66 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +class TestCase extends \PHPUnit\Framework\TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = []) + { + return [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => new DateTimeImmutable(true), + 'extra' => [], + ]; + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return [ + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { + return $record['message']; + })); + + return $formatter; + } +} |