init
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeZone;
|
||||
class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
|
||||
{
|
||||
private $useMicroseconds;
|
||||
public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null)
|
||||
{
|
||||
$this->useMicroseconds = $useMicroseconds;
|
||||
// if you like to use a custom time to pass to Logger::addRecord directly,
|
||||
// call modify() or setTimestamp() on this instance to change the date after creating it
|
||||
parent::__construct('now', $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();
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Psr\Log\LoggerInterface;
|
||||
use MailPoetVendor\Psr\Log\LogLevel;
|
||||
class ErrorHandler
|
||||
{
|
||||
private $logger;
|
||||
private $previousExceptionHandler = null;
|
||||
private $uncaughtExceptionLevelMap = [];
|
||||
private $previousErrorHandler = null;
|
||||
private $errorLevelMap = [];
|
||||
private $handleOnlyReportedErrors = \true;
|
||||
private $hasFatalErrorHandler = \false;
|
||||
private $fatalLevel = LogLevel::ALERT;
|
||||
private $reservedMemory = null;
|
||||
private $lastFatalData = null;
|
||||
private static $fatalErrors = [\E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR];
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null) : self
|
||||
{
|
||||
$handler = new static($logger);
|
||||
if ($errorLevelMap !== \false) {
|
||||
$handler->registerErrorHandler($errorLevelMap);
|
||||
}
|
||||
if ($exceptionLevelMap !== \false) {
|
||||
$handler->registerExceptionHandler($exceptionLevelMap);
|
||||
}
|
||||
if ($fatalLevel !== \false) {
|
||||
$handler->registerFatalHandler($fatalLevel);
|
||||
}
|
||||
return $handler;
|
||||
}
|
||||
public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = \true) : self
|
||||
{
|
||||
$prev = \set_exception_handler(function (\Throwable $e) : void {
|
||||
$this->handleException($e);
|
||||
});
|
||||
$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 = [], bool $callPrevious = \true, int $errorTypes = -1, bool $handleOnlyReportedErrors = \true) : self
|
||||
{
|
||||
$prev = \set_error_handler([$this, 'handleError'], $errorTypes);
|
||||
$this->errorLevelMap = \array_replace($this->defaultErrorLevelMap(), $levelMap);
|
||||
if ($callPrevious) {
|
||||
$this->previousErrorHandler = $prev ?: \true;
|
||||
} else {
|
||||
$this->previousErrorHandler = null;
|
||||
}
|
||||
$this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
|
||||
return $this;
|
||||
}
|
||||
public function registerFatalHandler($level = null, int $reservedMemorySize = 20) : self
|
||||
{
|
||||
\register_shutdown_function([$this, 'handleFatalError']);
|
||||
$this->reservedMemory = \str_repeat(' ', 1024 * $reservedMemorySize);
|
||||
$this->fatalLevel = null === $level ? LogLevel::ALERT : $level;
|
||||
$this->hasFatalErrorHandler = \true;
|
||||
return $this;
|
||||
}
|
||||
protected function defaultExceptionLevelMap() : array
|
||||
{
|
||||
return ['ParseError' => LogLevel::CRITICAL, 'Throwable' => LogLevel::ERROR];
|
||||
}
|
||||
protected function defaultErrorLevelMap() : array
|
||||
{
|
||||
return [\E_ERROR => LogLevel::CRITICAL, \E_WARNING => LogLevel::WARNING, \E_PARSE => LogLevel::ALERT, \E_NOTICE => LogLevel::NOTICE, \E_CORE_ERROR => LogLevel::CRITICAL, \E_CORE_WARNING => LogLevel::WARNING, \E_COMPILE_ERROR => LogLevel::ALERT, \E_COMPILE_WARNING => LogLevel::WARNING, \E_USER_ERROR => LogLevel::ERROR, \E_USER_WARNING => LogLevel::WARNING, \E_USER_NOTICE => LogLevel::NOTICE, \E_STRICT => LogLevel::NOTICE, \E_RECOVERABLE_ERROR => LogLevel::ERROR, \E_DEPRECATED => LogLevel::NOTICE, \E_USER_DEPRECATED => LogLevel::NOTICE];
|
||||
}
|
||||
private function handleException(\Throwable $e) : void
|
||||
{
|
||||
$level = LogLevel::ERROR;
|
||||
foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {
|
||||
if ($e instanceof $class) {
|
||||
$level = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->logger->log($level, \sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), ['exception' => $e]);
|
||||
if ($this->previousExceptionHandler) {
|
||||
($this->previousExceptionHandler)($e);
|
||||
}
|
||||
if (!\headers_sent() && \in_array(\strtolower((string) \ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], \true)) {
|
||||
\http_response_code(500);
|
||||
}
|
||||
exit(255);
|
||||
}
|
||||
public function handleError(int $code, string $message, string $file = '', int $line = 0, ?array $context = []) : bool
|
||||
{
|
||||
if ($this->handleOnlyReportedErrors && !(\error_reporting() & $code)) {
|
||||
return \false;
|
||||
}
|
||||
// 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 = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
|
||||
$this->logger->log($level, self::codeToString($code) . ': ' . $message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
|
||||
} else {
|
||||
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
\array_shift($trace);
|
||||
// Exclude handleError from trace
|
||||
$this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace];
|
||||
}
|
||||
if ($this->previousErrorHandler === \true) {
|
||||
return \false;
|
||||
} elseif ($this->previousErrorHandler) {
|
||||
return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context);
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
public function handleFatalError() : void
|
||||
{
|
||||
$this->reservedMemory = '';
|
||||
if (\is_array($this->lastFatalData)) {
|
||||
$lastError = $this->lastFatalData;
|
||||
} else {
|
||||
$lastError = \error_get_last();
|
||||
}
|
||||
if ($lastError && \in_array($lastError['type'], self::$fatalErrors, \true)) {
|
||||
$trace = $lastError['trace'] ?? null;
|
||||
$this->logger->log($this->fatalLevel, 'Fatal Error (' . self::codeToString($lastError['type']) . '): ' . $lastError['message'], ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace]);
|
||||
if ($this->logger instanceof Logger) {
|
||||
foreach ($this->logger->getHandlers() as $handler) {
|
||||
$handler->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private static function codeToString($code) : string
|
||||
{
|
||||
switch ($code) {
|
||||
case \E_ERROR:
|
||||
return 'E_ERROR';
|
||||
case \E_WARNING:
|
||||
return 'E_WARNING';
|
||||
case \E_PARSE:
|
||||
return 'E_PARSE';
|
||||
case \E_NOTICE:
|
||||
return 'E_NOTICE';
|
||||
case \E_CORE_ERROR:
|
||||
return 'E_CORE_ERROR';
|
||||
case \E_CORE_WARNING:
|
||||
return 'E_CORE_WARNING';
|
||||
case \E_COMPILE_ERROR:
|
||||
return 'E_COMPILE_ERROR';
|
||||
case \E_COMPILE_WARNING:
|
||||
return 'E_COMPILE_WARNING';
|
||||
case \E_USER_ERROR:
|
||||
return 'E_USER_ERROR';
|
||||
case \E_USER_WARNING:
|
||||
return 'E_USER_WARNING';
|
||||
case \E_USER_NOTICE:
|
||||
return 'E_USER_NOTICE';
|
||||
case \E_STRICT:
|
||||
return 'E_STRICT';
|
||||
case \E_RECOVERABLE_ERROR:
|
||||
return 'E_RECOVERABLE_ERROR';
|
||||
case \E_DEPRECATED:
|
||||
return 'E_DEPRECATED';
|
||||
case \E_USER_DEPRECATED:
|
||||
return 'E_USER_DEPRECATED';
|
||||
}
|
||||
return 'Unknown PHP error';
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Formatter;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface FormatterInterface
|
||||
{
|
||||
public function format(array $record);
|
||||
public function formatBatch(array $records);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Formatter;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
use MailPoetVendor\Monolog\LogRecord;
|
||||
final class GoogleCloudLoggingFormatter extends JsonFormatter
|
||||
{
|
||||
public function format(array $record) : string
|
||||
{
|
||||
// Re-key level for GCP logging
|
||||
$record['severity'] = $record['level_name'];
|
||||
$record['time'] = $record['datetime']->format(DateTimeInterface::RFC3339_EXTENDED);
|
||||
// Remove keys that are not used by GCP
|
||||
unset($record['level'], $record['level_name'], $record['datetime']);
|
||||
return parent::format($record);
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Formatter;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Utils;
|
||||
class LineFormatter extends NormalizerFormatter
|
||||
{
|
||||
public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
|
||||
protected $format;
|
||||
protected $allowInlineLineBreaks;
|
||||
protected $ignoreEmptyContextAndExtra;
|
||||
protected $includeStacktraces;
|
||||
protected $stacktracesParser;
|
||||
public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = \false, bool $ignoreEmptyContextAndExtra = \false, bool $includeStacktraces = \false)
|
||||
{
|
||||
$this->format = $format === null ? static::SIMPLE_FORMAT : $format;
|
||||
$this->allowInlineLineBreaks = $allowInlineLineBreaks;
|
||||
$this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
|
||||
$this->includeStacktraces($includeStacktraces);
|
||||
parent::__construct($dateFormat);
|
||||
}
|
||||
public function includeStacktraces(bool $include = \true, ?callable $parser = null) : self
|
||||
{
|
||||
$this->includeStacktraces = $include;
|
||||
if ($this->includeStacktraces) {
|
||||
$this->allowInlineLineBreaks = \true;
|
||||
$this->stacktracesParser = $parser;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function allowInlineLineBreaks(bool $allow = \true) : self
|
||||
{
|
||||
$this->allowInlineLineBreaks = $allow;
|
||||
return $this;
|
||||
}
|
||||
public function ignoreEmptyContextAndExtra(bool $ignore = \true) : self
|
||||
{
|
||||
$this->ignoreEmptyContextAndExtra = $ignore;
|
||||
return $this;
|
||||
}
|
||||
public function format(array $record) : string
|
||||
{
|
||||
$vars = parent::format($record);
|
||||
$output = $this->format;
|
||||
foreach ($vars['extra'] as $var => $val) {
|
||||
if (\false !== \strpos($output, '%extra.' . $var . '%')) {
|
||||
$output = \str_replace('%extra.' . $var . '%', $this->stringify($val), $output);
|
||||
unset($vars['extra'][$var]);
|
||||
}
|
||||
}
|
||||
foreach ($vars['context'] as $var => $val) {
|
||||
if (\false !== \strpos($output, '%context.' . $var . '%')) {
|
||||
$output = \str_replace('%context.' . $var . '%', $this->stringify($val), $output);
|
||||
unset($vars['context'][$var]);
|
||||
}
|
||||
}
|
||||
if ($this->ignoreEmptyContextAndExtra) {
|
||||
if (empty($vars['context'])) {
|
||||
unset($vars['context']);
|
||||
$output = \str_replace('%context%', '', $output);
|
||||
}
|
||||
if (empty($vars['extra'])) {
|
||||
unset($vars['extra']);
|
||||
$output = \str_replace('%extra%', '', $output);
|
||||
}
|
||||
}
|
||||
foreach ($vars as $var => $val) {
|
||||
if (\false !== \strpos($output, '%' . $var . '%')) {
|
||||
$output = \str_replace('%' . $var . '%', $this->stringify($val), $output);
|
||||
}
|
||||
}
|
||||
// remove leftover %extra.xxx% and %context.xxx% if any
|
||||
if (\false !== \strpos($output, '%')) {
|
||||
$output = \preg_replace('/%(?:extra|context)\\..+?%/', '', $output);
|
||||
if (null === $output) {
|
||||
$pcreErrorCode = \preg_last_error();
|
||||
throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
public function formatBatch(array $records) : string
|
||||
{
|
||||
$message = '';
|
||||
foreach ($records as $record) {
|
||||
$message .= $this->format($record);
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
public function stringify($value) : string
|
||||
{
|
||||
return $this->replaceNewlines($this->convertToString($value));
|
||||
}
|
||||
protected function normalizeException(\Throwable $e, int $depth = 0) : string
|
||||
{
|
||||
$str = $this->formatException($e);
|
||||
if ($previous = $e->getPrevious()) {
|
||||
do {
|
||||
$depth++;
|
||||
if ($depth > $this->maxNormalizeDepth) {
|
||||
$str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
|
||||
break;
|
||||
}
|
||||
$str .= "\n[previous exception] " . $this->formatException($previous);
|
||||
} while ($previous = $previous->getPrevious());
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
protected function convertToString($data) : string
|
||||
{
|
||||
if (null === $data || \is_bool($data)) {
|
||||
return \var_export($data, \true);
|
||||
}
|
||||
if (\is_scalar($data)) {
|
||||
return (string) $data;
|
||||
}
|
||||
return $this->toJson($data, \true);
|
||||
}
|
||||
protected function replaceNewlines(string $str) : string
|
||||
{
|
||||
if ($this->allowInlineLineBreaks) {
|
||||
if (0 === \strpos($str, '{')) {
|
||||
$str = \preg_replace('/(?<!\\\\)\\\\[rn]/', "\n", $str);
|
||||
if (null === $str) {
|
||||
$pcreErrorCode = \preg_last_error();
|
||||
throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
return \str_replace(["\r\n", "\r", "\n"], ' ', $str);
|
||||
}
|
||||
private function formatException(\Throwable $e) : string
|
||||
{
|
||||
$str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode();
|
||||
if ($e instanceof \SoapFault) {
|
||||
if (isset($e->faultcode)) {
|
||||
$str .= ' faultcode: ' . $e->faultcode;
|
||||
}
|
||||
if (isset($e->faultactor)) {
|
||||
$str .= ' faultactor: ' . $e->faultactor;
|
||||
}
|
||||
if (isset($e->detail)) {
|
||||
if (\is_string($e->detail)) {
|
||||
$str .= ' detail: ' . $e->detail;
|
||||
} elseif (\is_object($e->detail) || \is_array($e->detail)) {
|
||||
$str .= ' detail: ' . $this->toJson($e->detail, \true);
|
||||
}
|
||||
}
|
||||
}
|
||||
$str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
|
||||
if ($this->includeStacktraces) {
|
||||
$str .= $this->stacktracesParser($e);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
private function stacktracesParser(\Throwable $e) : string
|
||||
{
|
||||
$trace = $e->getTraceAsString();
|
||||
if ($this->stacktracesParser) {
|
||||
$trace = $this->stacktracesParserCustom($trace);
|
||||
}
|
||||
return "\n[stacktrace]\n" . $trace . "\n";
|
||||
}
|
||||
private function stacktracesParserCustom(string $trace) : string
|
||||
{
|
||||
return \implode("\n", \array_filter(\array_map($this->stacktracesParser, \explode("\n", $trace))));
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Formatter;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\DateTimeImmutable;
|
||||
use MailPoetVendor\Monolog\Utils;
|
||||
use Throwable;
|
||||
class NormalizerFormatter implements FormatterInterface
|
||||
{
|
||||
public const SIMPLE_DATE = "Y-m-d\\TH:i:sP";
|
||||
protected $dateFormat;
|
||||
protected $maxNormalizeDepth = 9;
|
||||
protected $maxNormalizeItemCount = 1000;
|
||||
private $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;
|
||||
public function __construct(?string $dateFormat = null)
|
||||
{
|
||||
$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');
|
||||
}
|
||||
}
|
||||
public function format(array $record)
|
||||
{
|
||||
return $this->normalize($record);
|
||||
}
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
foreach ($records as $key => $record) {
|
||||
$records[$key] = $this->format($record);
|
||||
}
|
||||
return $records;
|
||||
}
|
||||
public function getDateFormat() : string
|
||||
{
|
||||
return $this->dateFormat;
|
||||
}
|
||||
public function setDateFormat(string $dateFormat) : self
|
||||
{
|
||||
$this->dateFormat = $dateFormat;
|
||||
return $this;
|
||||
}
|
||||
public function getMaxNormalizeDepth() : int
|
||||
{
|
||||
return $this->maxNormalizeDepth;
|
||||
}
|
||||
public function setMaxNormalizeDepth(int $maxNormalizeDepth) : self
|
||||
{
|
||||
$this->maxNormalizeDepth = $maxNormalizeDepth;
|
||||
return $this;
|
||||
}
|
||||
public function getMaxNormalizeItemCount() : int
|
||||
{
|
||||
return $this->maxNormalizeItemCount;
|
||||
}
|
||||
public function setMaxNormalizeItemCount(int $maxNormalizeItemCount) : self
|
||||
{
|
||||
$this->maxNormalizeItemCount = $maxNormalizeItemCount;
|
||||
return $this;
|
||||
}
|
||||
public function setJsonPrettyPrint(bool $enable) : self
|
||||
{
|
||||
if ($enable) {
|
||||
$this->jsonEncodeOptions |= \JSON_PRETTY_PRINT;
|
||||
} else {
|
||||
$this->jsonEncodeOptions &= ~\JSON_PRETTY_PRINT;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
protected function normalize($data, int $depth = 0)
|
||||
{
|
||||
if ($depth > $this->maxNormalizeDepth) {
|
||||
return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
|
||||
}
|
||||
if (null === $data || \is_scalar($data)) {
|
||||
if (\is_float($data)) {
|
||||
if (\is_infinite($data)) {
|
||||
return ($data > 0 ? '' : '-') . 'INF';
|
||||
}
|
||||
if (\is_nan($data)) {
|
||||
return 'NaN';
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
if (\is_array($data)) {
|
||||
$normalized = [];
|
||||
$count = 1;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($count++ > $this->maxNormalizeItemCount) {
|
||||
$normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items (' . \count($data) . ' total), aborting normalization';
|
||||
break;
|
||||
}
|
||||
$normalized[$key] = $this->normalize($value, $depth + 1);
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
if ($data instanceof \DateTimeInterface) {
|
||||
return $this->formatDate($data);
|
||||
}
|
||||
if (\is_object($data)) {
|
||||
if ($data instanceof Throwable) {
|
||||
return $this->normalizeException($data, $depth);
|
||||
}
|
||||
if ($data instanceof \JsonSerializable) {
|
||||
$value = $data->jsonSerialize();
|
||||
} elseif (\get_class($data) === '__PHP_Incomplete_Class') {
|
||||
$accessor = new \ArrayObject($data);
|
||||
$value = (string) $accessor['__PHP_Incomplete_Class_Name'];
|
||||
} elseif (\method_exists($data, '__toString')) {
|
||||
$value = $data->__toString();
|
||||
} else {
|
||||
// the rest is normalized by json encoding and decoding it
|
||||
$value = \json_decode($this->toJson($data, \true), \true);
|
||||
}
|
||||
return [Utils::getClass($data) => $value];
|
||||
}
|
||||
if (\is_resource($data)) {
|
||||
return \sprintf('[resource(%s)]', \get_resource_type($data));
|
||||
}
|
||||
return '[unknown(' . \gettype($data) . ')]';
|
||||
}
|
||||
protected function normalizeException(Throwable $e, int $depth = 0)
|
||||
{
|
||||
if ($depth > $this->maxNormalizeDepth) {
|
||||
return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'];
|
||||
}
|
||||
if ($e instanceof \JsonSerializable) {
|
||||
return (array) $e->jsonSerialize();
|
||||
}
|
||||
$data = ['class' => Utils::getClass($e), 'message' => $e->getMessage(), 'code' => (int) $e->getCode(), '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)) {
|
||||
if (\is_string($e->detail)) {
|
||||
$data['detail'] = $e->detail;
|
||||
} elseif (\is_object($e->detail) || \is_array($e->detail)) {
|
||||
$data['detail'] = $this->toJson($e->detail, \true);
|
||||
}
|
||||
}
|
||||
}
|
||||
$trace = $e->getTrace();
|
||||
foreach ($trace as $frame) {
|
||||
if (isset($frame['file'])) {
|
||||
$data['trace'][] = $frame['file'] . ':' . $frame['line'];
|
||||
}
|
||||
}
|
||||
if ($previous = $e->getPrevious()) {
|
||||
$data['previous'] = $this->normalizeException($previous, $depth + 1);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
protected function toJson($data, bool $ignoreErrors = \false) : string
|
||||
{
|
||||
return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors);
|
||||
}
|
||||
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);
|
||||
}
|
||||
public function addJsonEncodeOption(int $option) : self
|
||||
{
|
||||
$this->jsonEncodeOptions |= $option;
|
||||
return $this;
|
||||
}
|
||||
public function removeJsonEncodeOption(int $option) : self
|
||||
{
|
||||
$this->jsonEncodeOptions &= ~$option;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Logger;
|
||||
use MailPoetVendor\Monolog\ResettableInterface;
|
||||
use MailPoetVendor\Psr\Log\LogLevel;
|
||||
abstract class AbstractHandler extends Handler implements ResettableInterface
|
||||
{
|
||||
protected $level = Logger::DEBUG;
|
||||
protected $bubble = \true;
|
||||
public function __construct($level = Logger::DEBUG, bool $bubble = \true)
|
||||
{
|
||||
$this->setLevel($level);
|
||||
$this->bubble = $bubble;
|
||||
}
|
||||
public function isHandling(array $record) : bool
|
||||
{
|
||||
return $record['level'] >= $this->level;
|
||||
}
|
||||
public function setLevel($level) : self
|
||||
{
|
||||
$this->level = Logger::toMonologLevel($level);
|
||||
return $this;
|
||||
}
|
||||
public function getLevel() : int
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
public function setBubble(bool $bubble) : self
|
||||
{
|
||||
$this->bubble = $bubble;
|
||||
return $this;
|
||||
}
|
||||
public function getBubble() : bool
|
||||
{
|
||||
return $this->bubble;
|
||||
}
|
||||
public function reset()
|
||||
{
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
|
||||
{
|
||||
use ProcessableHandlerTrait;
|
||||
use FormattableHandlerTrait;
|
||||
public function handle(array $record) : bool
|
||||
{
|
||||
if (!$this->isHandling($record)) {
|
||||
return \false;
|
||||
}
|
||||
if ($this->processors) {
|
||||
$record = $this->processRecord($record);
|
||||
}
|
||||
$record['formatted'] = $this->getFormatter()->format($record);
|
||||
$this->write($record);
|
||||
return \false === $this->bubble;
|
||||
}
|
||||
protected abstract function write(array $record) : void;
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
$this->resetProcessors();
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Throwable;
|
||||
class FallbackGroupHandler extends GroupHandler
|
||||
{
|
||||
public function handle(array $record) : bool
|
||||
{
|
||||
if ($this->processors) {
|
||||
$record = $this->processRecord($record);
|
||||
}
|
||||
foreach ($this->handlers as $handler) {
|
||||
try {
|
||||
$handler->handle($record);
|
||||
break;
|
||||
} catch (Throwable $e) {
|
||||
// What throwable?
|
||||
}
|
||||
}
|
||||
return \false === $this->bubble;
|
||||
}
|
||||
public function handleBatch(array $records) : void
|
||||
{
|
||||
if ($this->processors) {
|
||||
$processed = [];
|
||||
foreach ($records as $record) {
|
||||
$processed[] = $this->processRecord($record);
|
||||
}
|
||||
$records = $processed;
|
||||
}
|
||||
foreach ($this->handlers as $handler) {
|
||||
try {
|
||||
$handler->handleBatch($records);
|
||||
break;
|
||||
} catch (Throwable $e) {
|
||||
// What throwable?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Formatter\FormatterInterface;
|
||||
interface FormattableHandlerInterface
|
||||
{
|
||||
public function setFormatter(FormatterInterface $formatter) : HandlerInterface;
|
||||
public function getFormatter() : FormatterInterface;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Formatter\FormatterInterface;
|
||||
use MailPoetVendor\Monolog\Formatter\LineFormatter;
|
||||
trait FormattableHandlerTrait
|
||||
{
|
||||
protected $formatter;
|
||||
public function setFormatter(FormatterInterface $formatter) : HandlerInterface
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
return $this;
|
||||
}
|
||||
public function getFormatter() : FormatterInterface
|
||||
{
|
||||
if (!$this->formatter) {
|
||||
$this->formatter = $this->getDefaultFormatter();
|
||||
}
|
||||
return $this->formatter;
|
||||
}
|
||||
protected function getDefaultFormatter() : FormatterInterface
|
||||
{
|
||||
return new LineFormatter();
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class Handler implements HandlerInterface
|
||||
{
|
||||
public function handleBatch(array $records) : void
|
||||
{
|
||||
foreach ($records as $record) {
|
||||
$this->handle($record);
|
||||
}
|
||||
}
|
||||
public function close() : void
|
||||
{
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
$this->close();
|
||||
} catch (\Throwable $e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
public function __sleep()
|
||||
{
|
||||
$this->close();
|
||||
$reflClass = new \ReflectionClass($this);
|
||||
$keys = [];
|
||||
foreach ($reflClass->getProperties() as $reflProp) {
|
||||
if (!$reflProp->isStatic()) {
|
||||
$keys[] = $reflProp->getName();
|
||||
}
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface HandlerInterface
|
||||
{
|
||||
public function isHandling(array $record) : bool;
|
||||
public function handle(array $record) : bool;
|
||||
public function handleBatch(array $records) : void;
|
||||
public function close() : void;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class NoopHandler extends Handler
|
||||
{
|
||||
public function isHandling(array $record) : bool
|
||||
{
|
||||
return \true;
|
||||
}
|
||||
public function handle(array $record) : bool
|
||||
{
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Logger;
|
||||
use MailPoetVendor\Monolog\Formatter\FormatterInterface;
|
||||
class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface
|
||||
{
|
||||
private $handler;
|
||||
private $thresholdMap = [Logger::DEBUG => 0, Logger::INFO => 0, Logger::NOTICE => 0, Logger::WARNING => 0, Logger::ERROR => 0, Logger::CRITICAL => 0, Logger::ALERT => 0, Logger::EMERGENCY => 0];
|
||||
private $buffer = [];
|
||||
public function __construct(HandlerInterface $handler, array $thresholdMap = [], $level = Logger::DEBUG, bool $bubble = \true)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
foreach ($thresholdMap as $thresholdLevel => $threshold) {
|
||||
$this->thresholdMap[$thresholdLevel] = $threshold;
|
||||
}
|
||||
parent::__construct($level, $bubble);
|
||||
}
|
||||
public function handle(array $record) : bool
|
||||
{
|
||||
if ($record['level'] < $this->level) {
|
||||
return \false;
|
||||
}
|
||||
$level = $record['level'];
|
||||
if (!isset($this->thresholdMap[$level])) {
|
||||
$this->thresholdMap[$level] = 0;
|
||||
}
|
||||
if ($this->thresholdMap[$level] > 0) {
|
||||
// The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1
|
||||
$this->thresholdMap[$level]--;
|
||||
$this->buffer[$level][] = $record;
|
||||
return \false === $this->bubble;
|
||||
}
|
||||
if ($this->thresholdMap[$level] == 0) {
|
||||
// This current message is breaking the threshold. Flush the buffer and continue handling the current record
|
||||
foreach ($this->buffer[$level] ?? [] as $buffered) {
|
||||
$this->handler->handle($buffered);
|
||||
}
|
||||
$this->thresholdMap[$level]--;
|
||||
unset($this->buffer[$level]);
|
||||
}
|
||||
$this->handler->handle($record);
|
||||
return \false === $this->bubble;
|
||||
}
|
||||
public function setFormatter(FormatterInterface $formatter) : HandlerInterface
|
||||
{
|
||||
if ($this->handler instanceof FormattableHandlerInterface) {
|
||||
$this->handler->setFormatter($formatter);
|
||||
return $this;
|
||||
}
|
||||
throw new \UnexpectedValueException('The nested handler of type ' . \get_class($this->handler) . ' does not support formatters.');
|
||||
}
|
||||
public function getFormatter() : FormatterInterface
|
||||
{
|
||||
if ($this->handler instanceof FormattableHandlerInterface) {
|
||||
return $this->handler->getFormatter();
|
||||
}
|
||||
throw new \UnexpectedValueException('The nested handler of type ' . \get_class($this->handler) . ' does not support formatters.');
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Logger;
|
||||
class ProcessHandler extends AbstractProcessingHandler
|
||||
{
|
||||
private $process;
|
||||
private $command;
|
||||
private $cwd;
|
||||
private $pipes = [];
|
||||
protected 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'],
|
||||
];
|
||||
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;
|
||||
}
|
||||
protected function write(array $record) : void
|
||||
{
|
||||
$this->ensureProcessIsStarted();
|
||||
$this->writeProcessInput($record['formatted']);
|
||||
$errors = $this->readProcessErrors();
|
||||
if (empty($errors) === \false) {
|
||||
throw new \UnexpectedValueException(\sprintf('Errors while writing to process: %s', $errors));
|
||||
}
|
||||
}
|
||||
private function ensureProcessIsStarted() : void
|
||||
{
|
||||
if (\is_resource($this->process) === \false) {
|
||||
$this->startProcess();
|
||||
$this->handleStartupErrors();
|
||||
}
|
||||
}
|
||||
private function startProcess() : void
|
||||
{
|
||||
$this->process = \proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd);
|
||||
foreach ($this->pipes as $pipe) {
|
||||
\stream_set_blocking($pipe, \false);
|
||||
}
|
||||
}
|
||||
private function handleStartupErrors() : void
|
||||
{
|
||||
$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));
|
||||
}
|
||||
}
|
||||
protected function selectErrorStream()
|
||||
{
|
||||
$empty = [];
|
||||
$errorPipes = [$this->pipes[2]];
|
||||
return \stream_select($errorPipes, $empty, $empty, 1);
|
||||
}
|
||||
protected function readProcessErrors() : string
|
||||
{
|
||||
return (string) \stream_get_contents($this->pipes[2]);
|
||||
}
|
||||
protected function writeProcessInput(string $string) : void
|
||||
{
|
||||
\fwrite($this->pipes[0], $string);
|
||||
}
|
||||
public function close() : void
|
||||
{
|
||||
if (\is_resource($this->process)) {
|
||||
foreach ($this->pipes as $pipe) {
|
||||
\fclose($pipe);
|
||||
}
|
||||
\proc_close($this->process);
|
||||
$this->process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Processor\ProcessorInterface;
|
||||
interface ProcessableHandlerInterface
|
||||
{
|
||||
public function pushProcessor(callable $callback) : HandlerInterface;
|
||||
public function popProcessor() : callable;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\ResettableInterface;
|
||||
use MailPoetVendor\Monolog\Processor\ProcessorInterface;
|
||||
trait ProcessableHandlerTrait
|
||||
{
|
||||
protected $processors = [];
|
||||
public function pushProcessor(callable $callback) : HandlerInterface
|
||||
{
|
||||
\array_unshift($this->processors, $callback);
|
||||
return $this;
|
||||
}
|
||||
public function popProcessor() : callable
|
||||
{
|
||||
if (!$this->processors) {
|
||||
throw new \LogicException('You tried to pop from an empty processor stack.');
|
||||
}
|
||||
return \array_shift($this->processors);
|
||||
}
|
||||
protected function processRecord(array $record) : array
|
||||
{
|
||||
foreach ($this->processors as $processor) {
|
||||
$record = $processor($record);
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
protected function resetProcessors() : void
|
||||
{
|
||||
foreach ($this->processors as $processor) {
|
||||
if ($processor instanceof ResettableInterface) {
|
||||
$processor->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Logger;
|
||||
use MailPoetVendor\Monolog\Utils;
|
||||
use MailPoetVendor\Monolog\Formatter\FormatterInterface;
|
||||
use MailPoetVendor\Monolog\Formatter\LineFormatter;
|
||||
use MailPoetVendor\Symfony\Component\Mailer\MailerInterface;
|
||||
use MailPoetVendor\Symfony\Component\Mailer\Transport\TransportInterface;
|
||||
use MailPoetVendor\Symfony\Component\Mime\Email;
|
||||
class SymfonyMailerHandler extends MailHandler
|
||||
{
|
||||
protected $mailer;
|
||||
private $emailTemplate;
|
||||
public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = \true)
|
||||
{
|
||||
parent::__construct($level, $bubble);
|
||||
$this->mailer = $mailer;
|
||||
$this->emailTemplate = $email;
|
||||
}
|
||||
protected function send(string $content, array $records) : void
|
||||
{
|
||||
$this->mailer->send($this->buildMessage($content, $records));
|
||||
}
|
||||
protected function getSubjectFormatter(?string $format) : FormatterInterface
|
||||
{
|
||||
return new LineFormatter($format);
|
||||
}
|
||||
protected function buildMessage(string $content, array $records) : Email
|
||||
{
|
||||
$message = null;
|
||||
if ($this->emailTemplate instanceof Email) {
|
||||
$message = clone $this->emailTemplate;
|
||||
} elseif (\is_callable($this->emailTemplate)) {
|
||||
$message = ($this->emailTemplate)($content, $records);
|
||||
}
|
||||
if (!$message instanceof Email) {
|
||||
$record = \reset($records);
|
||||
throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : ''));
|
||||
}
|
||||
if ($records) {
|
||||
$subjectFormatter = $this->getSubjectFormatter($message->getSubject());
|
||||
$message->subject($subjectFormatter->format($this->getHighestRecord($records)));
|
||||
}
|
||||
if ($this->isHtmlBody($content)) {
|
||||
if (null !== ($charset = $message->getHtmlCharset())) {
|
||||
$message->html($content, $charset);
|
||||
} else {
|
||||
$message->html($content);
|
||||
}
|
||||
} else {
|
||||
if (null !== ($charset = $message->getTextCharset())) {
|
||||
$message->text($content, $charset);
|
||||
} else {
|
||||
$message->text($content);
|
||||
}
|
||||
}
|
||||
return $message->date(new \DateTimeImmutable());
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Handler;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
trait WebRequestRecognizerTrait
|
||||
{
|
||||
protected function isWebRequest() : bool
|
||||
{
|
||||
return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use ArrayAccess;
|
||||
interface LogRecord extends \ArrayAccess
|
||||
{
|
||||
public function toArray() : array;
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeZone;
|
||||
use MailPoetVendor\Monolog\Handler\HandlerInterface;
|
||||
use MailPoetVendor\Psr\Log\LoggerInterface;
|
||||
use MailPoetVendor\Psr\Log\InvalidArgumentException;
|
||||
use MailPoetVendor\Psr\Log\LogLevel;
|
||||
use Throwable;
|
||||
use Stringable;
|
||||
class Logger implements LoggerInterface, ResettableInterface
|
||||
{
|
||||
public const DEBUG = 100;
|
||||
public const INFO = 200;
|
||||
public const NOTICE = 250;
|
||||
public const WARNING = 300;
|
||||
public const ERROR = 400;
|
||||
public const CRITICAL = 500;
|
||||
public const ALERT = 550;
|
||||
public const EMERGENCY = 600;
|
||||
public const API = 2;
|
||||
protected static $levels = [self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', self::WARNING => 'WARNING', self::ERROR => 'ERROR', self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY'];
|
||||
private const RFC_5424_LEVELS = [7 => self::DEBUG, 6 => self::INFO, 5 => self::NOTICE, 4 => self::WARNING, 3 => self::ERROR, 2 => self::CRITICAL, 1 => self::ALERT, 0 => self::EMERGENCY];
|
||||
protected $name;
|
||||
protected $handlers;
|
||||
protected $processors;
|
||||
protected $microsecondTimestamps = \true;
|
||||
protected $timezone;
|
||||
protected $exceptionHandler;
|
||||
private $logDepth = 0;
|
||||
private $fiberLogDepth;
|
||||
private $detectCycles = \true;
|
||||
public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->setHandlers($handlers);
|
||||
$this->processors = $processors;
|
||||
$this->timezone = $timezone ?: new DateTimeZone(\date_default_timezone_get() ?: 'UTC');
|
||||
if (\PHP_VERSION_ID >= 80100) {
|
||||
// Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
|
||||
$fiberLogDepth = new \WeakMap();
|
||||
$this->fiberLogDepth = $fiberLogDepth;
|
||||
}
|
||||
}
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
public function withName(string $name) : self
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->name = $name;
|
||||
return $new;
|
||||
}
|
||||
public function pushHandler(HandlerInterface $handler) : self
|
||||
{
|
||||
\array_unshift($this->handlers, $handler);
|
||||
return $this;
|
||||
}
|
||||
public function popHandler() : HandlerInterface
|
||||
{
|
||||
if (!$this->handlers) {
|
||||
throw new \LogicException('You tried to pop from an empty handler stack.');
|
||||
}
|
||||
return \array_shift($this->handlers);
|
||||
}
|
||||
public function setHandlers(array $handlers) : self
|
||||
{
|
||||
$this->handlers = [];
|
||||
foreach (\array_reverse($handlers) as $handler) {
|
||||
$this->pushHandler($handler);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function getHandlers() : array
|
||||
{
|
||||
return $this->handlers;
|
||||
}
|
||||
public function pushProcessor(callable $callback) : self
|
||||
{
|
||||
\array_unshift($this->processors, $callback);
|
||||
return $this;
|
||||
}
|
||||
public function popProcessor() : callable
|
||||
{
|
||||
if (!$this->processors) {
|
||||
throw new \LogicException('You tried to pop from an empty processor stack.');
|
||||
}
|
||||
return \array_shift($this->processors);
|
||||
}
|
||||
public function getProcessors() : array
|
||||
{
|
||||
return $this->processors;
|
||||
}
|
||||
public function useMicrosecondTimestamps(bool $micro) : self
|
||||
{
|
||||
$this->microsecondTimestamps = $micro;
|
||||
return $this;
|
||||
}
|
||||
public function useLoggingLoopDetection(bool $detectCycles) : self
|
||||
{
|
||||
$this->detectCycles = $detectCycles;
|
||||
return $this;
|
||||
}
|
||||
public function addRecord(int $level, string $message, array $context = [], ?DateTimeImmutable $datetime = null) : bool
|
||||
{
|
||||
if (isset(self::RFC_5424_LEVELS[$level])) {
|
||||
$level = self::RFC_5424_LEVELS[$level];
|
||||
}
|
||||
if ($this->detectCycles) {
|
||||
if (\PHP_VERSION_ID >= 80100 && ($fiber = \Fiber::getCurrent())) {
|
||||
$this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0;
|
||||
$logDepth = ++$this->fiberLogDepth[$fiber];
|
||||
} else {
|
||||
$logDepth = ++$this->logDepth;
|
||||
}
|
||||
} else {
|
||||
$logDepth = 0;
|
||||
}
|
||||
if ($logDepth === 3) {
|
||||
$this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.');
|
||||
return \false;
|
||||
} elseif ($logDepth >= 5) {
|
||||
// log depth 4 is let through, so we can log the warning above
|
||||
return \false;
|
||||
}
|
||||
try {
|
||||
$record = null;
|
||||
foreach ($this->handlers as $handler) {
|
||||
if (null === $record) {
|
||||
// skip creating the record as long as no handler is going to handle it
|
||||
if (!$handler->isHandling(['level' => $level])) {
|
||||
continue;
|
||||
}
|
||||
$levelName = static::getLevelName($level);
|
||||
$record = ['message' => $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, 'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), 'extra' => []];
|
||||
try {
|
||||
foreach ($this->processors as $processor) {
|
||||
$record = $processor($record);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->handleException($e, $record);
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
// once the record exists, send it to all handlers as long as the bubbling chain is not interrupted
|
||||
try {
|
||||
if (\true === $handler->handle($record)) {
|
||||
break;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->handleException($e, $record);
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if ($this->detectCycles) {
|
||||
if (isset($fiber)) {
|
||||
$this->fiberLogDepth[$fiber]--;
|
||||
} else {
|
||||
$this->logDepth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null !== $record;
|
||||
}
|
||||
public function close() : void
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->close();
|
||||
}
|
||||
}
|
||||
public function reset() : void
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler instanceof ResettableInterface) {
|
||||
$handler->reset();
|
||||
}
|
||||
}
|
||||
foreach ($this->processors as $processor) {
|
||||
if ($processor instanceof ResettableInterface) {
|
||||
$processor->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
public static function getLevels() : array
|
||||
{
|
||||
return \array_flip(static::$levels);
|
||||
}
|
||||
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)));
|
||||
}
|
||||
return static::$levels[$level];
|
||||
}
|
||||
public static function toMonologLevel($level) : int
|
||||
{
|
||||
if (\is_string($level)) {
|
||||
if (\is_numeric($level)) {
|
||||
return \intval($level);
|
||||
}
|
||||
// Contains chars of all log levels and avoids using strtoupper() which may have
|
||||
// strange results depending on locale (for example, "i" will become "İ" in Turkish locale)
|
||||
$upper = \strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY');
|
||||
if (\defined(__CLASS__ . '::' . $upper)) {
|
||||
return \constant(__CLASS__ . '::' . $upper);
|
||||
}
|
||||
throw new InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . \implode(', ', \array_keys(static::$levels) + static::$levels));
|
||||
}
|
||||
if (!\is_int($level)) {
|
||||
throw new InvalidArgumentException('Level "' . \var_export($level, \true) . '" is not defined, use one of: ' . \implode(', ', \array_keys(static::$levels) + static::$levels));
|
||||
}
|
||||
return $level;
|
||||
}
|
||||
public function isHandling(int $level) : bool
|
||||
{
|
||||
$record = ['level' => $level];
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler->isHandling($record)) {
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
public function setExceptionHandler(?callable $callback) : self
|
||||
{
|
||||
$this->exceptionHandler = $callback;
|
||||
return $this;
|
||||
}
|
||||
public function getExceptionHandler() : ?callable
|
||||
{
|
||||
return $this->exceptionHandler;
|
||||
}
|
||||
public function log($level, $message, array $context = []) : void
|
||||
{
|
||||
if (!\is_int($level) && !\is_string($level)) {
|
||||
throw new \InvalidArgumentException('$level is expected to be a string or int');
|
||||
}
|
||||
if (isset(self::RFC_5424_LEVELS[$level])) {
|
||||
$level = self::RFC_5424_LEVELS[$level];
|
||||
}
|
||||
$level = static::toMonologLevel($level);
|
||||
$this->addRecord($level, (string) $message, $context);
|
||||
}
|
||||
public function debug($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::DEBUG, (string) $message, $context);
|
||||
}
|
||||
public function info($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::INFO, (string) $message, $context);
|
||||
}
|
||||
public function notice($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::NOTICE, (string) $message, $context);
|
||||
}
|
||||
public function warning($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::WARNING, (string) $message, $context);
|
||||
}
|
||||
public function error($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::ERROR, (string) $message, $context);
|
||||
}
|
||||
public function critical($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::CRITICAL, (string) $message, $context);
|
||||
}
|
||||
public function alert($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::ALERT, (string) $message, $context);
|
||||
}
|
||||
public function emergency($message, array $context = []) : void
|
||||
{
|
||||
$this->addRecord(static::EMERGENCY, (string) $message, $context);
|
||||
}
|
||||
public function setTimezone(DateTimeZone $tz) : self
|
||||
{
|
||||
$this->timezone = $tz;
|
||||
return $this;
|
||||
}
|
||||
public function getTimezone() : DateTimeZone
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
protected function handleException(Throwable $e, array $record) : void
|
||||
{
|
||||
if (!$this->exceptionHandler) {
|
||||
throw $e;
|
||||
}
|
||||
($this->exceptionHandler)($e, $record);
|
||||
}
|
||||
public function __serialize() : array
|
||||
{
|
||||
return ['name' => $this->name, 'handlers' => $this->handlers, 'processors' => $this->processors, 'microsecondTimestamps' => $this->microsecondTimestamps, 'timezone' => $this->timezone, 'exceptionHandler' => $this->exceptionHandler, 'logDepth' => $this->logDepth, 'detectCycles' => $this->detectCycles];
|
||||
}
|
||||
public function __unserialize(array $data) : void
|
||||
{
|
||||
foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) {
|
||||
if (isset($data[$property])) {
|
||||
$this->{$property} = $data[$property];
|
||||
}
|
||||
}
|
||||
if (\PHP_VERSION_ID >= 80100) {
|
||||
// Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
|
||||
$fiberLogDepth = new \WeakMap();
|
||||
$this->fiberLogDepth = $fiberLogDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Processor;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class HostnameProcessor implements ProcessorInterface
|
||||
{
|
||||
private static $host;
|
||||
public function __construct()
|
||||
{
|
||||
self::$host = (string) \gethostname();
|
||||
}
|
||||
public function __invoke(array $record) : array
|
||||
{
|
||||
$record['extra']['hostname'] = self::$host;
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Processor;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Logger;
|
||||
use MailPoetVendor\Psr\Log\LogLevel;
|
||||
class IntrospectionProcessor implements ProcessorInterface
|
||||
{
|
||||
private $level;
|
||||
private $skipClassesPartials;
|
||||
private $skipStackFramesCount;
|
||||
private $skipFunctions = ['call_user_func', 'call_user_func_array'];
|
||||
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;
|
||||
}
|
||||
public function __invoke(array $record) : array
|
||||
{
|
||||
// return if the level is not high enough
|
||||
if ($record['level'] < $this->level) {
|
||||
return $record;
|
||||
}
|
||||
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
// skip first since it's always the current method
|
||||
\array_shift($trace);
|
||||
// the call_user_func call is also skipped
|
||||
\array_shift($trace);
|
||||
$i = 0;
|
||||
while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
|
||||
if (isset($trace[$i]['class'])) {
|
||||
foreach ($this->skipClassesPartials as $part) {
|
||||
if (\strpos($trace[$i]['class'], $part) !== \false) {
|
||||
$i++;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
} elseif (\in_array($trace[$i]['function'], $this->skipFunctions)) {
|
||||
$i++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$i += $this->skipStackFramesCount;
|
||||
// we should have the call source now
|
||||
$record['extra'] = \array_merge($record['extra'], ['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, 'callType' => isset($trace[$i]['type']) ? $trace[$i]['type'] : null, 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null]);
|
||||
return $record;
|
||||
}
|
||||
private function isTraceClassOrSkippedFunction(array $trace, int $index) : bool
|
||||
{
|
||||
if (!isset($trace[$index])) {
|
||||
return \false;
|
||||
}
|
||||
return isset($trace[$index]['class']) || \in_array($trace[$index]['function'], $this->skipFunctions);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Processor;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class MemoryProcessor implements ProcessorInterface
|
||||
{
|
||||
protected $realUsage;
|
||||
protected $useFormatting;
|
||||
public function __construct(bool $realUsage = \true, bool $useFormatting = \true)
|
||||
{
|
||||
$this->realUsage = $realUsage;
|
||||
$this->useFormatting = $useFormatting;
|
||||
}
|
||||
protected function formatBytes(int $bytes)
|
||||
{
|
||||
if (!$this->useFormatting) {
|
||||
return $bytes;
|
||||
}
|
||||
if ($bytes > 1024 * 1024) {
|
||||
return \round($bytes / 1024 / 1024, 2) . ' MB';
|
||||
} elseif ($bytes > 1024) {
|
||||
return \round($bytes / 1024, 2) . ' KB';
|
||||
}
|
||||
return $bytes . ' B';
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Processor;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class MemoryUsageProcessor extends MemoryProcessor
|
||||
{
|
||||
public function __invoke(array $record) : array
|
||||
{
|
||||
$usage = \memory_get_usage($this->realUsage);
|
||||
if ($this->useFormatting) {
|
||||
$usage = $this->formatBytes($usage);
|
||||
}
|
||||
$record['extra']['memory_usage'] = $usage;
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Processor;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface ProcessorInterface
|
||||
{
|
||||
public function __invoke(array $record);
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Processor;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class WebProcessor implements ProcessorInterface
|
||||
{
|
||||
protected $serverData;
|
||||
protected $extraFields = ['url' => 'REQUEST_URI', 'ip' => 'REMOTE_ADDR', 'http_method' => 'REQUEST_METHOD', 'server' => 'SERVER_NAME', 'referrer' => 'HTTP_REFERER', 'user_agent' => 'HTTP_USER_AGENT'];
|
||||
public function __construct($serverData = null, ?array $extraFields = null)
|
||||
{
|
||||
if (null === $serverData) {
|
||||
$this->serverData =& $_SERVER;
|
||||
} elseif (\is_array($serverData) || $serverData instanceof \ArrayAccess) {
|
||||
$this->serverData = $serverData;
|
||||
} else {
|
||||
throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.');
|
||||
}
|
||||
$defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer'];
|
||||
if (isset($this->serverData['UNIQUE_ID'])) {
|
||||
$this->extraFields['unique_id'] = 'UNIQUE_ID';
|
||||
$defaultEnabled[] = 'unique_id';
|
||||
}
|
||||
if (null === $extraFields) {
|
||||
$extraFields = $defaultEnabled;
|
||||
}
|
||||
if (isset($extraFields[0])) {
|
||||
foreach (\array_keys($this->extraFields) as $fieldName) {
|
||||
if (!\in_array($fieldName, $extraFields)) {
|
||||
unset($this->extraFields[$fieldName]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->extraFields = $extraFields;
|
||||
}
|
||||
}
|
||||
public function __invoke(array $record) : array
|
||||
{
|
||||
// skip processing if for some reason request data
|
||||
// is not present (CLI or wonky SAPIs)
|
||||
if (!isset($this->serverData['REQUEST_URI'])) {
|
||||
return $record;
|
||||
}
|
||||
$record['extra'] = $this->appendExtraFields($record['extra']);
|
||||
return $record;
|
||||
}
|
||||
public function addExtraField(string $extraName, string $serverName) : self
|
||||
{
|
||||
$this->extraFields[$extraName] = $serverName;
|
||||
return $this;
|
||||
}
|
||||
private function appendExtraFields(array $extra) : array
|
||||
{
|
||||
foreach ($this->extraFields as $extraName => $serverName) {
|
||||
$extra[$extraName] = $this->serverData[$serverName] ?? null;
|
||||
}
|
||||
return $extra;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use InvalidArgumentException;
|
||||
class Registry
|
||||
{
|
||||
private static $loggers = [];
|
||||
public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = \false)
|
||||
{
|
||||
$name = $name ?: $logger->getName();
|
||||
if (isset(self::$loggers[$name]) && !$overwrite) {
|
||||
throw new InvalidArgumentException('Logger with the given name already exists');
|
||||
}
|
||||
self::$loggers[$name] = $logger;
|
||||
}
|
||||
public static function hasLogger($logger) : bool
|
||||
{
|
||||
if ($logger instanceof Logger) {
|
||||
$index = \array_search($logger, self::$loggers, \true);
|
||||
return \false !== $index;
|
||||
}
|
||||
return isset(self::$loggers[$logger]);
|
||||
}
|
||||
public static function removeLogger($logger) : void
|
||||
{
|
||||
if ($logger instanceof Logger) {
|
||||
if (\false !== ($idx = \array_search($logger, self::$loggers, \true))) {
|
||||
unset(self::$loggers[$idx]);
|
||||
}
|
||||
} else {
|
||||
unset(self::$loggers[$logger]);
|
||||
}
|
||||
}
|
||||
public static function clear() : void
|
||||
{
|
||||
self::$loggers = [];
|
||||
}
|
||||
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));
|
||||
}
|
||||
return self::$loggers[$name];
|
||||
}
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
return self::getInstance($name);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface ResettableInterface
|
||||
{
|
||||
public function reset();
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Psr\Log\LoggerInterface;
|
||||
use MailPoetVendor\Psr\Log\LogLevel;
|
||||
use ReflectionExtension;
|
||||
class SignalHandler
|
||||
{
|
||||
private $logger;
|
||||
private $previousSignalHandler = [];
|
||||
private $signalLevelMap = [];
|
||||
private $signalRestartSyscalls = [];
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = \true, bool $restartSyscalls = \true, ?bool $async = \true) : self
|
||||
{
|
||||
if (!\extension_loaded('pcntl') || !\function_exists('pcntl_signal')) {
|
||||
return $this;
|
||||
}
|
||||
$level = Logger::toMonologLevel($level);
|
||||
if ($callPrevious) {
|
||||
$handler = \pcntl_signal_get_handler($signo);
|
||||
$this->previousSignalHandler[$signo] = $handler;
|
||||
} else {
|
||||
unset($this->previousSignalHandler[$signo]);
|
||||
}
|
||||
$this->signalLevelMap[$signo] = $level;
|
||||
$this->signalRestartSyscalls[$signo] = $restartSyscalls;
|
||||
if ($async !== null) {
|
||||
\pcntl_async_signals($async);
|
||||
}
|
||||
\pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls);
|
||||
return $this;
|
||||
}
|
||||
public function handleSignal(int $signo, $siginfo = null) : void
|
||||
{
|
||||
static $signals = [];
|
||||
if (!$signals && \extension_loaded('pcntl')) {
|
||||
$pcntl = new ReflectionExtension('pcntl');
|
||||
// HHVM 3.24.2 returns an empty array.
|
||||
foreach ($pcntl->getConstants() ?: \get_defined_constants(\true)['Core'] as $name => $value) {
|
||||
if (\substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && \is_int($value)) {
|
||||
$signals[$value] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
$level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL;
|
||||
$signal = $signals[$signo] ?? $signo;
|
||||
$context = $siginfo ?? [];
|
||||
$this->logger->log($level, \sprintf('Program received signal %s', $signal), $context);
|
||||
if (!isset($this->previousSignalHandler[$signo])) {
|
||||
return;
|
||||
}
|
||||
if ($this->previousSignalHandler[$signo] === \SIG_DFL) {
|
||||
if (\extension_loaded('pcntl') && \function_exists('pcntl_signal') && \function_exists('pcntl_sigprocmask') && \function_exists('pcntl_signal_dispatch') && \extension_loaded('posix') && \function_exists('posix_getpid') && \function_exists('posix_kill')) {
|
||||
$restartSyscalls = $this->signalRestartSyscalls[$signo] ?? \true;
|
||||
\pcntl_signal($signo, \SIG_DFL, $restartSyscalls);
|
||||
\pcntl_sigprocmask(\SIG_UNBLOCK, [$signo], $oldset);
|
||||
\posix_kill(\posix_getpid(), $signo);
|
||||
\pcntl_signal_dispatch();
|
||||
\pcntl_sigprocmask(\SIG_SETMASK, $oldset);
|
||||
\pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls);
|
||||
}
|
||||
} elseif (\is_callable($this->previousSignalHandler[$signo])) {
|
||||
$this->previousSignalHandler[$signo]($signo, $siginfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog\Test;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoetVendor\Monolog\Logger;
|
||||
use MailPoetVendor\Monolog\DateTimeImmutable;
|
||||
use MailPoetVendor\Monolog\Formatter\FormatterInterface;
|
||||
class TestCase extends \MailPoetVendor\PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function tearDown() : void
|
||||
{
|
||||
parent::tearDown();
|
||||
if (isset($this->handler)) {
|
||||
unset($this->handler);
|
||||
}
|
||||
}
|
||||
protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []) : array
|
||||
{
|
||||
return ['message' => (string) $message, 'context' => $context, 'level' => $level, 'level_name' => Logger::getLevelName($level), 'channel' => 'test', 'datetime' => new DateTimeImmutable(\true), 'extra' => []];
|
||||
}
|
||||
protected function getMultipleRecords() : array
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace MailPoetVendor\Monolog;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
final class Utils
|
||||
{
|
||||
const DEFAULT_JSON_FLAGS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION | \JSON_INVALID_UTF8_SUBSTITUTE | \JSON_PARTIAL_OUTPUT_ON_ERROR;
|
||||
public static function getClass(object $object) : string
|
||||
{
|
||||
$class = \get_class($object);
|
||||
if (\false === ($pos = \strpos($class, "@anonymous\x00"))) {
|
||||
return $class;
|
||||
}
|
||||
if (\false === ($parent = \get_parent_class($class))) {
|
||||
return \substr($class, 0, $pos + 10);
|
||||
}
|
||||
return $parent . '@anonymous';
|
||||
}
|
||||
public static function substr(string $string, int $start, ?int $length = null) : string
|
||||
{
|
||||
if (\extension_loaded('mbstring')) {
|
||||
return \mb_strcut($string, $start, $length);
|
||||
}
|
||||
return \substr($string, $start, null === $length ? \strlen($string) : $length);
|
||||
}
|
||||
public static function canonicalizePath(string $streamUrl) : string
|
||||
{
|
||||
$prefix = '';
|
||||
if ('file://' === \substr($streamUrl, 0, 7)) {
|
||||
$streamUrl = \substr($streamUrl, 7);
|
||||
$prefix = 'file://';
|
||||
}
|
||||
// other type of stream, not supported
|
||||
if (\false !== \strpos($streamUrl, '://')) {
|
||||
return $streamUrl;
|
||||
}
|
||||
// already absolute
|
||||
if (\substr($streamUrl, 0, 1) === '/' || \substr($streamUrl, 1, 1) === ':' || \substr($streamUrl, 0, 2) === '\\\\') {
|
||||
return $prefix . $streamUrl;
|
||||
}
|
||||
$streamUrl = \getcwd() . '/' . $streamUrl;
|
||||
return $prefix . $streamUrl;
|
||||
}
|
||||
public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = \false) : string
|
||||
{
|
||||
if (null === $encodeFlags) {
|
||||
$encodeFlags = self::DEFAULT_JSON_FLAGS;
|
||||
}
|
||||
if ($ignoreErrors) {
|
||||
$json = @\json_encode($data, $encodeFlags);
|
||||
if (\false === $json) {
|
||||
return 'null';
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
$json = \json_encode($data, $encodeFlags);
|
||||
if (\false === $json) {
|
||||
$json = self::handleJsonError(\json_last_error(), $data);
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
public static function handleJsonError(int $code, $data, ?int $encodeFlags = null) : string
|
||||
{
|
||||
if ($code !== \JSON_ERROR_UTF8) {
|
||||
self::throwEncodeError($code, $data);
|
||||
}
|
||||
if (\is_string($data)) {
|
||||
self::detectAndCleanUtf8($data);
|
||||
} elseif (\is_array($data)) {
|
||||
\array_walk_recursive($data, array('Monolog\\Utils', 'detectAndCleanUtf8'));
|
||||
} else {
|
||||
self::throwEncodeError($code, $data);
|
||||
}
|
||||
if (null === $encodeFlags) {
|
||||
$encodeFlags = self::DEFAULT_JSON_FLAGS;
|
||||
}
|
||||
$json = \json_encode($data, $encodeFlags);
|
||||
if ($json === \false) {
|
||||
self::throwEncodeError(\json_last_error(), $data);
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
public static function pcreLastErrorMessage(int $code) : string
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
return \preg_last_error_msg();
|
||||
}
|
||||
$constants = \get_defined_constants(\true)['pcre'];
|
||||
$constants = \array_filter($constants, function ($key) {
|
||||
return \substr($key, -6) == '_ERROR';
|
||||
}, \ARRAY_FILTER_USE_KEY);
|
||||
$constants = \array_flip($constants);
|
||||
return $constants[$code] ?? 'UNDEFINED_ERROR';
|
||||
}
|
||||
private static function throwEncodeError(int $code, $data) : void
|
||||
{
|
||||
switch ($code) {
|
||||
case \JSON_ERROR_DEPTH:
|
||||
$msg = 'Maximum stack depth exceeded';
|
||||
break;
|
||||
case \JSON_ERROR_STATE_MISMATCH:
|
||||
$msg = 'Underflow or the modes mismatch';
|
||||
break;
|
||||
case \JSON_ERROR_CTRL_CHAR:
|
||||
$msg = 'Unexpected control character found';
|
||||
break;
|
||||
case \JSON_ERROR_UTF8:
|
||||
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
break;
|
||||
default:
|
||||
$msg = 'Unknown error';
|
||||
}
|
||||
throw new \RuntimeException('JSON encoding failed: ' . $msg . '. Encoding: ' . \var_export($data, \true));
|
||||
}
|
||||
private static function detectAndCleanUtf8(&$data) : void
|
||||
{
|
||||
if (\is_string($data) && !\preg_match('//u', $data)) {
|
||||
$data = \preg_replace_callback('/[\\x80-\\xFF]+/', function ($m) {
|
||||
return \function_exists('mb_convert_encoding') ? \mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : \utf8_encode($m[0]);
|
||||
}, $data);
|
||||
if (!\is_string($data)) {
|
||||
$pcreErrorCode = \preg_last_error();
|
||||
throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode));
|
||||
}
|
||||
$data = \str_replace(['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], $data);
|
||||
}
|
||||
}
|
||||
public static function expandIniShorthandBytes($val)
|
||||
{
|
||||
if (!\is_string($val)) {
|
||||
return \false;
|
||||
}
|
||||
// support -1
|
||||
if ((int) $val < 0) {
|
||||
return (int) $val;
|
||||
}
|
||||
if (!\preg_match('/^\\s*(?<val>\\d+)(?:\\.\\d+)?\\s*(?<unit>[gmk]?)\\s*$/i', $val, $match)) {
|
||||
return \false;
|
||||
}
|
||||
$val = (int) $match['val'];
|
||||
switch (\strtolower($match['unit'] ?? '')) {
|
||||
case 'g':
|
||||
$val *= 1024;
|
||||
case 'm':
|
||||
$val *= 1024;
|
||||
case 'k':
|
||||
$val *= 1024;
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
public static function getRecordMessageForException(array $record) : string
|
||||
{
|
||||
$context = '';
|
||||
$extra = '';
|
||||
try {
|
||||
if ($record['context']) {
|
||||
$context = "\nContext: " . \json_encode($record['context']);
|
||||
}
|
||||
if ($record['extra']) {
|
||||
$extra = "\nExtra: " . \json_encode($record['extra']);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// noop
|
||||
}
|
||||
return "\nThe exception occurred while attempting to log: " . $record['message'] . $context . $extra;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user