diff options
Diffstat (limited to 'src')
50 files changed, 467 insertions, 313 deletions
diff --git a/src/Monolog/DateTimeImmutable.php b/src/Monolog/DateTimeImmutable.php index 6ca786c..45b80c7 100644 --- a/src/Monolog/DateTimeImmutable.php +++ b/src/Monolog/DateTimeImmutable.php @@ -23,17 +23,27 @@ class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable public function __construct($useMicroseconds, \DateTimeZone $timezone = null) { + $this->useMicroseconds = $useMicroseconds; $date = 'now'; + if ($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 - $timestamp = microtime(true); - $microseconds = sprintf("%06d", ($timestamp - floor($timestamp)) * 1000000); - $date = date('Y-m-d H:i:s.' . $microseconds, (int) $timestamp); + // + // 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); - $this->useMicroseconds = $useMicroseconds; + parent::__construct($date, $timezone); } public function jsonSerialize(): string diff --git a/src/Monolog/ErrorHandler.php b/src/Monolog/ErrorHandler.php index 4038d82..18c783f 100644 --- a/src/Monolog/ErrorHandler.php +++ b/src/Monolog/ErrorHandler.php @@ -32,6 +32,7 @@ class ErrorHandler private $previousErrorHandler; private $errorLevelMap; + private $handleOnlyReportedErrors; private $hasFatalErrorHandler; private $fatalLevel; @@ -54,7 +55,7 @@ class ErrorHandler * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling * @return ErrorHandler */ - public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null) + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self { $handler = new static($logger); if ($errorLevelMap !== false) { @@ -70,34 +71,42 @@ class ErrorHandler return $handler; } - public function registerExceptionHandler($levelMap = [], $callPrevious = true) + public function registerExceptionHandler($levelMap = [], $callPrevious = true): self { $prev = set_exception_handler([$this, 'handleException']); $this->uncaughtExceptionLevelMap = array_replace($this->defaultExceptionLevelMap(), $levelMap); if ($callPrevious && $prev) { $this->previousExceptionHandler = $prev; } + + return $this; } - public function registerErrorHandler(array $levelMap = [], $callPrevious = true, $errorTypes = -1) + public function registerErrorHandler(array $levelMap = [], $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true): self { $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([$this, 'handleFatalError']); $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); $this->fatalLevel = $level; $this->hasFatalErrorHandler = true; + + return $this; } - protected function defaultExceptionLevelMap() + protected function defaultExceptionLevelMap(): array { return [ 'ParseError' => LogLevel::CRITICAL, @@ -105,7 +114,7 @@ class ErrorHandler ]; } - protected function defaultErrorLevelMap() + protected function defaultErrorLevelMap(): array { return [ E_ERROR => LogLevel::CRITICAL, @@ -157,14 +166,14 @@ class ErrorHandler */ public function handleError($code, $message, $file = '', $line = 0, $context = []) { - if (!(error_reporting() & $code)) { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { return; } // 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, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'context' => $context]); + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); } if ($this->previousErrorHandler === true) { @@ -179,7 +188,7 @@ class ErrorHandler */ public function handleFatalError() { - $this->reservedMemory = null; + $this->reservedMemory = ''; $lastError = error_get_last(); if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { @@ -197,7 +206,7 @@ class ErrorHandler } } - 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 12b0d60..2b4d649 100644 --- a/src/Monolog/Formatter/ChromePHPFormatter.php +++ b/src/Monolog/Formatter/ChromePHPFormatter.php @@ -65,6 +65,9 @@ class ChromePHPFormatter implements FormatterInterface ]; } + /** + * {@inheritdoc} + */ public function formatBatch(array $records) { $formatted = []; diff --git a/src/Monolog/Formatter/ElasticaFormatter.php b/src/Monolog/Formatter/ElasticaFormatter.php index e6a5e67..a6354f5 100644 --- a/src/Monolog/Formatter/ElasticaFormatter.php +++ b/src/Monolog/Formatter/ElasticaFormatter.php @@ -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 6a33038..301b74b 100644 --- a/src/Monolog/Formatter/FlowdockFormatter.php +++ b/src/Monolog/Formatter/FlowdockFormatter.php @@ -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,7 +37,7 @@ class FlowdockFormatter implements FormatterInterface /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): array { $tags = [ '#logs', @@ -75,7 +71,7 @@ class FlowdockFormatter implements FormatterInterface /** * {@inheritdoc} */ - public function formatBatch(array $records) + public function formatBatch(array $records): array { $formatted = []; @@ -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 e2a6658..a84f826 100644 --- a/src/Monolog/Formatter/FluentdFormatter.php +++ b/src/Monolog/Formatter/FluentdFormatter.php @@ -39,21 +39,21 @@ 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) { @@ -73,7 +73,7 @@ class FluentdFormatter implements FormatterInterface 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/GelfMessageFormatter.php b/src/Monolog/Formatter/GelfMessageFormatter.php index 047358a..d6d46c0 100644 --- a/src/Monolog/Formatter/GelfMessageFormatter.php +++ b/src/Monolog/Formatter/GelfMessageFormatter.php @@ -53,7 +53,7 @@ class GelfMessageFormatter extends NormalizerFormatter Logger::EMERGENCY => 0, ]; - public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + public function __construct(string $systemName = null, string $extraPrefix = null, string $contextPrefix = 'ctxt_') { parent::__construct('U.u'); @@ -66,7 +66,7 @@ class GelfMessageFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): Message { if (isset($record['context'])) { $record['context'] = parent::format($record['context']); diff --git a/src/Monolog/Formatter/HtmlFormatter.php b/src/Monolog/Formatter/HtmlFormatter.php index f5f9d94..a343b06 100644 --- a/src/Monolog/Formatter/HtmlFormatter.php +++ b/src/Monolog/Formatter/HtmlFormatter.php @@ -39,7 +39,7 @@ class HtmlFormatter extends NormalizerFormatter /** * @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); } @@ -52,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) { @@ -69,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'); @@ -82,7 +82,7 @@ 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">'; @@ -116,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) { @@ -126,7 +126,7 @@ class HtmlFormatter extends NormalizerFormatter return $message; } - protected function convertToString($data) + protected function convertToString($data): string { if (null === $data || is_scalar($data)) { return (string) $data; diff --git a/src/Monolog/Formatter/JsonFormatter.php b/src/Monolog/Formatter/JsonFormatter.php index 1a8fe0b..61ccf2c 100644 --- a/src/Monolog/Formatter/JsonFormatter.php +++ b/src/Monolog/Formatter/JsonFormatter.php @@ -11,7 +11,7 @@ namespace Monolog\Formatter; -use Exception; +use Throwable; /** * Encodes whatever record data is passed to it as json @@ -32,10 +32,7 @@ class JsonFormatter extends NormalizerFormatter */ protected $includeStacktraces = false; - /** - * @param int $batchMode - */ - 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; @@ -47,20 +44,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; } @@ -68,7 +61,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" : ''); } @@ -76,7 +69,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,18 +84,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); } @@ -110,11 +100,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; @@ -135,7 +122,7 @@ class JsonFormatter extends NormalizerFormatter * * @return mixed */ - protected function normalize($data, $depth = 0) + protected function normalize($data, int $depth = 0) { if ($depth > 9) { return 'Over 9 levels deep, aborting normalization'; @@ -156,8 +143,8 @@ class JsonFormatter extends NormalizerFormatter return $normalized; } - if ($data instanceof Exception) { - return $this->normalizeException($data); + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); } return $data; @@ -166,12 +153,8 @@ class JsonFormatter extends NormalizerFormatter /** * Normalizes given exception with or without its own stack trace based on * `includeStacktraces` property. - * - * @param Throwable $e - * - * @return array */ - protected function normalizeException(\Throwable $e) + protected function normalizeException(Throwable $e, int $depth = 0): array { $data = [ 'class' => get_class($e), @@ -196,7 +179,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 5a65b4c..254a618 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -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,6 +76,7 @@ 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); @@ -101,10 +102,15 @@ class LineFormatter extends NormalizerFormatter } } + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + return $output; } - public function formatBatch(array $records) + public function formatBatch(array $records): string { $message = ''; foreach ($records as $record) { @@ -114,12 +120,12 @@ class LineFormatter extends NormalizerFormatter return $message; } - public function stringify($value) + public function stringify($value): string { return $this->replaceNewlines($this->convertToString($value)); } - protected function normalizeException(\Throwable $e) + protected function normalizeException(\Throwable $e, int $depth = 0): string { $previousText = ''; if ($previous = $e->getPrevious()) { @@ -130,13 +136,13 @@ class LineFormatter extends NormalizerFormatter $str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; if ($this->includeStacktraces) { - $str .= "\n[stacktrace]\n".$e->getTraceAsString(); + $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; } return $str; } - protected function convertToString($data) + protected function convertToString($data): string { if (null === $data || is_bool($data)) { return var_export($data, true); @@ -146,12 +152,16 @@ class LineFormatter extends NormalizerFormatter return (string) $data; } - return $this->toJson($data, true); + return (string) $this->toJson($data, true); } - protected function replaceNewlines($str) + protected function replaceNewlines(string $str): string { if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(['\r', '\n'], ["\r", "\n"], $str); + } + return $str; } diff --git a/src/Monolog/Formatter/LogglyFormatter.php b/src/Monolog/Formatter/LogglyFormatter.php index d4190df..29841aa 100644 --- a/src/Monolog/Formatter/LogglyFormatter.php +++ b/src/Monolog/Formatter/LogglyFormatter.php @@ -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,7 +33,7 @@ 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 \DateTimeInterface)) { $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); diff --git a/src/Monolog/Formatter/LogmaticFormatter.php b/src/Monolog/Formatter/LogmaticFormatter.php index 2e10312..0b999a2 100644 --- a/src/Monolog/Formatter/LogmaticFormatter.php +++ b/src/Monolog/Formatter/LogmaticFormatter.php @@ -54,7 +54,7 @@ class LogmaticFormatter extends JsonFormatter * @see http://doc.logmatic.io/docs/basics-to-send-data * @see \Monolog\Formatter\JsonFormatter::format() */ - public function format(array $record) + public function format(array $record): string { if (!empty($this->hostname)) { $record['hostname'] = $this->hostname; diff --git a/src/Monolog/Formatter/LogstashFormatter.php b/src/Monolog/Formatter/LogstashFormatter.php index 31e354c..bc22da1 100644 --- a/src/Monolog/Formatter/LogstashFormatter.php +++ b/src/Monolog/Formatter/LogstashFormatter.php @@ -46,9 +46,8 @@ class LogstashFormatter extends NormalizerFormatter * @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_') + 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'); @@ -62,7 +61,7 @@ class LogstashFormatter extends NormalizerFormatter /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): string { $record = parent::format($record); diff --git a/src/Monolog/Formatter/MongoDBFormatter.php b/src/Monolog/Formatter/MongoDBFormatter.php index 0630f7c..4725c80 100644 --- a/src/Monolog/Formatter/MongoDBFormatter.php +++ b/src/Monolog/Formatter/MongoDBFormatter.php @@ -22,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); } @@ -44,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); @@ -53,7 +56,10 @@ 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) { @@ -74,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); @@ -82,7 +88,7 @@ class MongoDBFormatter implements FormatterInterface return $this->formatArray($objectVars, $nestingLevel); } - protected function formatException(\Throwable $exception, $nestingLevel) + protected function formatException(\Throwable $exception, int $nestingLevel) { $formattedException = [ 'class' => get_class($exception), @@ -100,8 +106,35 @@ class MongoDBFormatter implements FormatterInterface return $this->formatArray($formattedException, $nestingLevel); } - protected function formatDate(\DateTimeInterface $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) ($value->format('U.u') * 1000)); + return new UTCDateTime((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 + { + $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 fcac4a6..592713f 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -39,7 +39,7 @@ class NormalizerFormatter implements FormatterInterface /** * {@inheritdoc} */ - public function format(array $record) + public function format(array $record): array { return $this->normalize($record); } @@ -47,7 +47,7 @@ class NormalizerFormatter implements FormatterInterface /** * {@inheritdoc} */ - public function formatBatch(array $records) + public function formatBatch(array $records): array { foreach ($records as $key => $record) { $records[$key] = $this->format($record); @@ -56,7 +56,11 @@ class NormalizerFormatter implements FormatterInterface return $records; } - protected function normalize($data, $depth = 0) + /** + * @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'; @@ -79,9 +83,13 @@ class NormalizerFormatter implements FormatterInterface $normalized = []; $count = 1; + if ($data instanceof \Generator && !$data->valid()) { + return array('...' => 'Generator is already consumed, aborting'); + } + foreach ($data as $key => $value) { if ($count++ >= 1000) { - $normalized['...'] = 'Over 1000 items, aborting normalization'; + $normalized['...'] = 'Over 1000 items ('.($data instanceof \Generator ? 'generator function' : count($data).' total').'), aborting normalization'; break; } $normalized[$key] = $this->normalize($value, $depth + 1); @@ -96,7 +104,7 @@ class NormalizerFormatter implements FormatterInterface if (is_object($data)) { if ($data instanceof Throwable) { - return $this->normalizeException($data); + return $this->normalizeException($data, $depth); } if ($data instanceof \JsonSerializable) { @@ -123,7 +131,10 @@ class NormalizerFormatter implements FormatterInterface return '[unknown('.gettype($data).')]'; } - protected function normalizeException(Throwable $e) + /** + * @return array + */ + protected function normalizeException(Throwable $e, int $depth = 0) { $data = [ 'class' => get_class($e), @@ -132,6 +143,20 @@ class NormalizerFormatter implements FormatterInterface 'file' => $e->getFile().':'.$e->getLine(), ]; + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + $data['detail'] = $e->detail; + } + } + $trace = $e->getTrace(); foreach ($trace as $frame) { if (isset($frame['file'])) { @@ -141,12 +166,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; @@ -156,11 +181,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|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) { @@ -182,7 +206,7 @@ class NormalizerFormatter implements FormatterInterface */ private function jsonEncode($data) { - return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION); } /** @@ -286,7 +310,9 @@ class NormalizerFormatter implements FormatterInterface protected function formatDate(\DateTimeInterface $date) { - if ($date instanceof DateTimeImmutable) { + // 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; } diff --git a/src/Monolog/Formatter/ScalarFormatter.php b/src/Monolog/Formatter/ScalarFormatter.php index fcff129..8d560e7 100644 --- a/src/Monolog/Formatter/ScalarFormatter.php +++ b/src/Monolog/Formatter/ScalarFormatter.php @@ -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 9c00aa7..c8a3bb4 100644 --- a/src/Monolog/Formatter/WildfireFormatter.php +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -41,7 +41,7 @@ class WildfireFormatter extends NormalizerFormatter /** * {@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 = ''; @@ -97,12 +97,18 @@ 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, $depth = 0) + /** + * {@inheritdoc} + */ + protected function normalize($data, int $depth = 0) { if (is_object($data) && !$data instanceof \DateTimeInterface) { return $data; diff --git a/src/Monolog/Handler/AmqpHandler.php b/src/Monolog/Handler/AmqpHandler.php index b46435a..6e39a11 100644 --- a/src/Monolog/Handler/AmqpHandler.php +++ b/src/Monolog/Handler/AmqpHandler.php @@ -112,7 +112,7 @@ class AmqpHandler extends AbstractProcessingHandler * @param array $record * @return string */ - private function getRoutingKey(array $record) + protected function getRoutingKey(array $record) { $routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']); diff --git a/src/Monolog/Handler/ChromePHPHandler.php b/src/Monolog/Handler/ChromePHPHandler.php index 31573ad..5dc5d5c 100644 --- a/src/Monolog/Handler/ChromePHPHandler.php +++ b/src/Monolog/Handler/ChromePHPHandler.php @@ -174,16 +174,14 @@ 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']); + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; } /** diff --git a/src/Monolog/Handler/DynamoDbHandler.php b/src/Monolog/Handler/DynamoDbHandler.php index 43d4ddb..edbc630 100644 --- a/src/Monolog/Handler/DynamoDbHandler.php +++ b/src/Monolog/Handler/DynamoDbHandler.php @@ -14,6 +14,7 @@ namespace Monolog\Handler; use Aws\Common\Aws; use Aws\DynamoDb\DynamoDbClient; use Monolog\Formatter\FormatterInterface; +use Aws\DynamoDb\Marshaler; use Monolog\Formatter\ScalarFormatter; use Monolog\Logger; @@ -38,6 +39,16 @@ class DynamoDbHandler extends AbstractProcessingHandler protected $table; /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + /** * @param DynamoDbClient $client * @param string $table * @param int $level @@ -45,8 +56,11 @@ class DynamoDbHandler extends AbstractProcessingHandler */ public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true) { - if (!defined('Aws\Common\Aws::VERSION') || version_compare('3.0', Aws::VERSION, '<=')) { - throw new \RuntimeException('The DynamoDbHandler is only known to work with the AWS SDK 2.x releases'); + if (defined('Aws\Common\Aws::VERSION') && version_compare(Aws::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; } $this->client = $client; @@ -61,7 +75,11 @@ class DynamoDbHandler extends AbstractProcessingHandler protected function write(array $record) { $filtered = $this->filterEmptyFields($record['formatted']); - $formatted = $this->client->formatAttributes($filtered); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + $formatted = $this->client->formatAttributes($filtered); + } $this->client->putItem([ 'TableName' => $this->table, diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php index 42c78f6..9a9acb5 100644 --- a/src/Monolog/Handler/FirePHPHandler.php +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -61,7 +61,7 @@ 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)); @@ -72,10 +72,8 @@ class FirePHPHandler extends AbstractProcessingHandler * 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. diff --git a/src/Monolog/Handler/FlowdockHandler.php b/src/Monolog/Handler/FlowdockHandler.php index b3fc9b4..67d1291 100644 --- a/src/Monolog/Handler/FlowdockHandler.php +++ b/src/Monolog/Handler/FlowdockHandler.php @@ -65,7 +65,7 @@ class FlowdockHandler extends SocketHandler /** * Gets the default formatter. * - * @return FormatterInterface + * @suppress PhanTypeMissingReturn */ protected function getDefaultFormatter(): FormatterInterface { diff --git a/src/Monolog/Handler/GelfHandler.php b/src/Monolog/Handler/GelfHandler.php index 4a6c0f8..b5c3a8c 100644 --- a/src/Monolog/Handler/GelfHandler.php +++ b/src/Monolog/Handler/GelfHandler.php @@ -25,7 +25,7 @@ use Monolog\Formatter\FormatterInterface; 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; diff --git a/src/Monolog/Handler/MandrillHandler.php b/src/Monolog/Handler/MandrillHandler.php index 68ec568..066c649 100644 --- a/src/Monolog/Handler/MandrillHandler.php +++ b/src/Monolog/Handler/MandrillHandler.php @@ -46,7 +46,7 @@ class MandrillHandler extends MailHandler /** * {@inheritdoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records) { $mime = null; if ($this->isHtmlBody($content)) { diff --git a/src/Monolog/Handler/NoopHandler.php b/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 0000000..7c7ec03 --- /dev/null +++ b/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,42 @@ +<?php declare(strict_types=1); + +/* + * This file is part of the Monolog package. + * + * (c) Roel Harbers <roelharbers@gmail.com> + * + * 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; + +/** + * 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/ProcessHandler.php b/src/Monolog/Handler/ProcessHandler.php index 9c135f8..8cf6087 100644 --- a/src/Monolog/Handler/ProcessHandler.php +++ b/src/Monolog/Handler/ProcessHandler.php @@ -29,7 +29,7 @@ class ProcessHandler extends AbstractProcessingHandler /** * Holds the process to receive data on its STDIN. * - * @var resource + * @var resource|bool|null */ private $process; @@ -58,20 +58,20 @@ class ProcessHandler extends AbstractProcessingHandler ]; /** - * @param int $command Command for the process to start. Absolute paths are recommended, + * @param string $command Command for the process to start. Absolute paths are recommended, * especially if you do not use the $cwd parameter. - * @param bool|int $level The minimum logging level at which this handler will be triggered. - * @param bool|true $bubble Whether the messages that are handled can bubble up the stack or not. - * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @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($command, $level = Logger::DEBUG, $bubble = true, $cwd = null) + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, string $cwd = null) { - if (empty($command) || is_string($command) === false) { + if ($command === '') { throw new \InvalidArgumentException('The command argument must be a non-empty string.'); } - if ($cwd !== null && (empty($cwd) || is_string($cwd) === false)) { - throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string, if any.'); + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); } parent::__construct($level, $bubble); diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php index db64f80..c2283e8 100644 --- a/src/Monolog/Handler/RavenHandler.php +++ b/src/Monolog/Handler/RavenHandler.php @@ -84,7 +84,7 @@ class RavenHandler extends AbstractProcessingHandler // the record with the highest severity is the "main" one $record = array_reduce($records, function ($highest, $record) { - if ($record['level'] >= $highest['level']) { + if ($record['level'] > $highest['level']) { return $record; } @@ -133,6 +133,7 @@ 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 = []; $options['level'] = $this->logLevels[$record['level']]; @@ -179,14 +180,15 @@ class RavenHandler extends AbstractProcessingHandler $options['release'] = $this->release; } - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { $options['extra']['message'] = $record['formatted']; $this->ravenClient->captureException($record['context']['exception'], $options); } else { $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); } } diff --git a/src/Monolog/Handler/RedisHandler.php b/src/Monolog/Handler/RedisHandler.php index f77d4fd..e7ddb44 100644 --- a/src/Monolog/Handler/RedisHandler.php +++ b/src/Monolog/Handler/RedisHandler.php @@ -37,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'); diff --git a/src/Monolog/Handler/RollbarHandler.php b/src/Monolog/Handler/RollbarHandler.php index 0a82faa..94e4757 100644 --- a/src/Monolog/Handler/RollbarHandler.php +++ b/src/Monolog/Handler/RollbarHandler.php @@ -21,6 +21,14 @@ use Monolog\Logger; * If the context data contains a `payload` key, that is used as an array * of payload options to RollbarNotifier's report_message/report_exception methods. * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * * @author Paul Statezny <paulstatezny@gmail.com> */ class RollbarHandler extends AbstractProcessingHandler @@ -32,6 +40,17 @@ class RollbarHandler extends AbstractProcessingHandler */ protected $rollbarNotifier; + protected $levelMap = [ + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + 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 * @@ -39,6 +58,8 @@ class RollbarHandler extends AbstractProcessingHandler */ private $hasRecords = false; + protected $initialized = false; + /** * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token * @param int $level The minimum logging level at which this handler will be triggered @@ -56,36 +77,35 @@ class RollbarHandler extends AbstractProcessingHandler */ protected function write(array $record) { - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { - $context = $record['context']; + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $payload = []; + if (isset($context['payload'])) { + $payload = $context['payload']; + unset($context['payload']); + } + $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) { $exception = $context['exception']; unset($context['exception']); - $payload = []; - if (isset($context['payload'])) { - $payload = $context['payload']; - unset($context['payload']); - } - $this->rollbarNotifier->report_exception($exception, $context, $payload); } else { - $extraData = [ - 'level' => $record['level'], - 'channel' => $record['channel'], - 'datetime' => $record['datetime']->format('U'), - ]; - - $context = $record['context']; - $payload = []; - if (isset($context['payload'])) { - $payload = $context['payload']; - unset($context['payload']); - } - $this->rollbarNotifier->report_message( $record['message'], - $record['level_name'], - array_merge($record['context'], $record['extra'], $extraData), + $context['level'], + $context, $payload ); } @@ -93,14 +113,19 @@ class RollbarHandler extends AbstractProcessingHandler $this->hasRecords = true; } - /** - * {@inheritdoc} - */ - public function close() + public function flush() { if ($this->hasRecords) { $this->rollbarNotifier->flush(); $this->hasRecords = false; } } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } } diff --git a/src/Monolog/Handler/RotatingFileHandler.php b/src/Monolog/Handler/RotatingFileHandler.php index 8c99f2d..a4a5ba4 100644 --- a/src/Monolog/Handler/RotatingFileHandler.php +++ b/src/Monolog/Handler/RotatingFileHandler.php @@ -69,11 +69,12 @@ class RotatingFileHandler extends StreamHandler public function setFilenameFormat($filenameFormat, $dateFormat) { - if (!in_array($dateFormat, [self::FILE_PER_DAY, self::FILE_PER_MONTH, self::FILE_PER_YEAR])) { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { throw new InvalidArgumentException( 'Invalid date format - format must be one of '. - 'RotatingFileHandler::FILE_PER_DAY, RotatingFileHandler::FILE_PER_MONTH '. - 'or RotatingFileHandler::FILE_PER_YEAR.' + '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.' ); } if (substr_count($filenameFormat, '{date}') === 0) { diff --git a/src/Monolog/Handler/SendGridHandler.php b/src/Monolog/Handler/SendGridHandler.php index 3a91cb4..7d82d94 100644 --- a/src/Monolog/Handler/SendGridHandler.php +++ b/src/Monolog/Handler/SendGridHandler.php @@ -72,7 +72,7 @@ class SendGridHandler extends MailHandler /** * {@inheritdoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records) { $message = []; $message['api_user'] = $this->apiUser; diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index a5db721..d311e2d 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -79,9 +79,10 @@ class SlackHandler extends SocketHandler * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @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 bool $printSimpleMessage Whether the attachment should include only the message without extradata * @throws MissingExtensionException If no OpenSSL PHP extension configured */ - public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false) + public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, $printSimpleMessage = false) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); @@ -144,19 +145,25 @@ class SlackHandler extends SocketHandler 'attachments' => [], ]; + if ($this->formatter && !$printSimpleMessage) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + if ($this->useAttachment) { $attachment = [ - 'fallback' => $record['message'], + 'fallback' => $message, 'color' => $this->getAttachmentColor($record['level']), 'fields' => [], ]; if ($this->useShortAttachment) { $attachment['title'] = $record['level_name']; - $attachment['text'] = $record['message']; + $attachment['text'] = $message; } else { $attachment['title'] = 'Message'; - $attachment['text'] = $record['message']; + $attachment['text'] = $message; $attachment['fields'][] = [ 'title' => 'Level', 'value' => $record['level_name'], @@ -206,7 +213,7 @@ class SlackHandler extends SocketHandler $dataArray['attachments'] = json_encode([$attachment]); } else { - $dataArray['text'] = $record['message']; + $dataArray['text'] = $message; } if ($this->iconEmoji) { diff --git a/src/Monolog/Handler/SocketHandler.php b/src/Monolog/Handler/SocketHandler.php index 06a2019..e2161ff 100644 --- a/src/Monolog/Handler/SocketHandler.php +++ b/src/Monolog/Handler/SocketHandler.php @@ -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; } diff --git a/src/Monolog/Handler/StreamHandler.php b/src/Monolog/Handler/StreamHandler.php index 4a7433c..661e164 100644 --- a/src/Monolog/Handler/StreamHandler.php +++ b/src/Monolog/Handler/StreamHandler.php @@ -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; diff --git a/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/src/Monolog/Handler/SyslogUdp/UdpSocket.php index 7a23ae8..f1801b3 100644 --- a/src/Monolog/Handler/SyslogUdp/UdpSocket.php +++ b/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -17,6 +17,8 @@ class UdpSocket protected $ip; protected $port; + + /** @var resource|null */ protected $socket; public function __construct($ip, $port = 514) diff --git a/src/Monolog/Handler/SyslogUdpHandler.php b/src/Monolog/Handler/SyslogUdpHandler.php index 4e2335d..2f16c3b 100644 --- a/src/Monolog/Handler/SyslogUdpHandler.php +++ b/src/Monolog/Handler/SyslogUdpHandler.php @@ -53,19 +53,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); + 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; @@ -75,7 +75,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/Logger.php b/src/Monolog/Logger.php index 8f981e3..438c770 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -136,7 +136,7 @@ class Logger implements LoggerInterface protected $timezone; /** - * @param string $name The logging channel + * @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 @@ -149,9 +149,6 @@ class Logger implements LoggerInterface $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC'); } - /** - * @return string - */ public function getName(): string { return $this->name; @@ -159,8 +156,6 @@ class Logger implements LoggerInterface /** * Return a new cloned instance with the name changed - * - * @return static */ public function withName(string $name): self { @@ -172,9 +167,6 @@ class Logger implements LoggerInterface /** * Pushes a handler on to the stack. - * - * @param HandlerInterface $handler - * @return $this */ public function pushHandler(HandlerInterface $handler): self { @@ -186,7 +178,7 @@ class Logger implements LoggerInterface /** * Pops a handler from the stack * - * @return HandlerInterface + * @throws \LogicException If empty handler stack */ public function popHandler(): HandlerInterface { @@ -202,8 +194,7 @@ 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): self { @@ -225,9 +216,6 @@ class Logger implements LoggerInterface /** * Adds a processor on to the stack. - * - * @param callable $callback - * @return $this */ public function pushProcessor(callable $callback): self { @@ -239,6 +227,7 @@ 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(): callable @@ -304,16 +293,13 @@ class Logger implements LoggerInterface return false; } - $dateTime = new DateTimeImmutable($this->microsecondTimestamps, $this->timezone); - $dateTime->setTimezone($this->timezone); - $record = [ 'message' => $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, - 'datetime' => $dateTime, + 'datetime' => new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), 'extra' => [], ]; @@ -345,7 +331,8 @@ 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(int $level): string @@ -361,6 +348,7 @@ 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): int @@ -405,13 +393,12 @@ class Logger implements LoggerInterface * @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 */ public function log($level, $message, array $context = []) { $level = static::toMonologLevel($level); - return $this->addRecord($level, (string) $message, $context); + $this->addRecord($level, (string) $message, $context); } /** @@ -421,11 +408,10 @@ class Logger implements LoggerInterface * * @param string $message The log message * @param array $context The log context - * @return Boolean Whether the record has been processed */ public function debug($message, array $context = []) { - return $this->addRecord(static::DEBUG, (string) $message, $context); + $this->addRecord(static::DEBUG, (string) $message, $context); } /** @@ -435,11 +421,10 @@ class Logger implements LoggerInterface * * @param string $message The log message * @param array $context The log context - * @return Boolean Whether the record has been processed */ public function info($message, array $context = []) { - return $this->addRecord(static::INFO, (string) $message, $context); + $this->addRecord(static::INFO, (string) $message, $context); } /** @@ -449,11 +434,10 @@ class Logger implements LoggerInterface * * @param string $message The log message * @param array $context The log context - * @return Boolean Whether the record has been processed */ public function notice($message, array $context = []) { - return $this->addRecord(static::NOTICE, (string) $message, $context); + $this->addRecord(static::NOTICE, (string) $message, $context); } /** @@ -463,11 +447,10 @@ class Logger implements LoggerInterface * * @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 = []) { - return $this->addRecord(static::WARNING, (string) $message, $context); + $this->addRecord(static::WARNING, (string) $message, $context); } /** @@ -477,11 +460,10 @@ class Logger implements LoggerInterface * * @param string $message The log message * @param array $context The log context - * @return Boolean Whether the record has been processed */ public function error($message, array $context = []) { - return $this->addRecord(static::ERROR, (string) $message, $context); + $this->addRecord(static::ERROR, (string) $message, $context); } /** @@ -491,11 +473,10 @@ class Logger implements LoggerInterface * * @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 = []) { - return $this->addRecord(static::CRITICAL, (string) $message, $context); + $this->addRecord(static::CRITICAL, (string) $message, $context); } /** @@ -505,11 +486,10 @@ class Logger implements LoggerInterface * * @param string $message The log message * @param array $context The log context - * @return Boolean Whether the record has been processed */ public function alert($message, array $context = []) { - return $this->addRecord(static::ALERT, (string) $message, $context); + $this->addRecord(static::ALERT, (string) $message, $context); } /** @@ -519,11 +499,10 @@ class Logger implements LoggerInterface * * @param string $message The log message * @param array $context The log context - * @return Boolean Whether the record has been processed */ public function emergency($message, array $context = []) { - return $this->addRecord(static::EMERGENCY, (string) $message, $context); + $this->addRecord(static::EMERGENCY, (string) $message, $context); } /** @@ -531,9 +510,11 @@ class Logger implements LoggerInterface * * @param DateTimeZone $tz Timezone object */ - public function setTimezone(DateTimeZone $tz) + public function setTimezone(DateTimeZone $tz): self { $this->timezone = $tz; + + return $this; } /** diff --git a/src/Monolog/Processor/GitProcessor.php b/src/Monolog/Processor/GitProcessor.php index cf16be8..9eec186 100644 --- a/src/Monolog/Processor/GitProcessor.php +++ b/src/Monolog/Processor/GitProcessor.php @@ -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; diff --git a/src/Monolog/Processor/IntrospectionProcessor.php b/src/Monolog/Processor/IntrospectionProcessor.php index 59e2f48..e1b5d3a 100644 --- a/src/Monolog/Processor/IntrospectionProcessor.php +++ b/src/Monolog/Processor/IntrospectionProcessor.php @@ -37,18 +37,14 @@ class IntrospectionProcessor 'call_user_func_array', ]; - public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], $skipStackFramesCount = 0) + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) { $this->level = Logger::toMonologLevel($level); $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) { @@ -96,7 +92,7 @@ class IntrospectionProcessor 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 b8fd91c..b6f8acc 100644 --- a/src/Monolog/Processor/MemoryPeakUsageProcessor.php +++ b/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -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 460caa6..5b06c2e 100644 --- a/src/Monolog/Processor/MemoryProcessor.php +++ b/src/Monolog/Processor/MemoryProcessor.php @@ -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 cb6b2ea..31c9396 100644 --- a/src/Monolog/Processor/MemoryUsageProcessor.php +++ b/src/Monolog/Processor/MemoryUsageProcessor.php @@ -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 new file mode 100644 index 0000000..87f254b --- /dev/null +++ b/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,60 @@ +<?php + +/* + * 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\Processor; + +use Monolog\Logger; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder <jonathanschweder@gmail.com> + */ +class MercurialProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + private static function getMercurialInfo(): array + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + + if (count($result) >= 3) { + return self::$cache = [ + 'branch' => $result[1], + 'revision' => $result[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/src/Monolog/Processor/ProcessIdProcessor.php b/src/Monolog/Processor/ProcessIdProcessor.php index 7ddafea..392bd13 100644 --- a/src/Monolog/Processor/ProcessIdProcessor.php +++ b/src/Monolog/Processor/ProcessIdProcessor.php @@ -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 e1a42cd..b9a00a4 100644 --- a/src/Monolog/Processor/PsrLogMessageProcessor.php +++ b/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -24,7 +24,7 @@ class PsrLogMessageProcessor * @param array $record * @return array */ - public function __invoke(array $record) + public function __invoke(array $record): array { if (false === strpos($record['message'], '{')) { return $record; diff --git a/src/Monolog/Processor/TagProcessor.php b/src/Monolog/Processor/TagProcessor.php index 19458e2..6371986 100644 --- a/src/Monolog/Processor/TagProcessor.php +++ b/src/Monolog/Processor/TagProcessor.php @@ -35,7 +35,7 @@ class TagProcessor $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 891e9df..601171b 100644 --- a/src/Monolog/Processor/UidProcessor.php +++ b/src/Monolog/Processor/UidProcessor.php @@ -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 91abeec..2e7134f 100644 --- a/src/Monolog/Processor/WebProcessor.php +++ b/src/Monolog/Processor/WebProcessor.php @@ -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,23 +78,14 @@ 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; diff --git a/src/Monolog/Registry.php b/src/Monolog/Registry.php index 64612c8..b63ef2e 100644 --- a/src/Monolog/Registry.php +++ b/src/Monolog/Registry.php @@ -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]); } /** @@ -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 index 7cb7fbf..32a0b23 100644 --- a/src/Monolog/Test/TestCase.php +++ b/src/Monolog/Test/TestCase.php @@ -13,6 +13,7 @@ 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 @@ -51,12 +52,9 @@ class TestCase extends \PHPUnit_Framework_TestCase ]; } - /** - * @return Monolog\Formatter\FormatterInterface - */ - protected function getIdentityFormatter() + protected function getIdentityFormatter(): FormatterInterface { - $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter = $this->createMock(FormatterInterface::class); $formatter->expects($this->any()) ->method('format') ->will($this->returnCallback(function ($record) { |