Vendor directory removed

This commit is contained in:
Sergei Solovev 2024-02-20 03:29:24 +03:00
parent 1b34389a67
commit 212c0e172e
653 changed files with 0 additions and 146017 deletions

12
vendor/autoload.php vendored
View file

@ -1,12 +0,0 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit76468c2eb232fdf4a8e3502b319270a4::getLoader();

View file

@ -1,572 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* 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 Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{
include $file;
}

View file

@ -1,352 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* 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 Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View file

@ -1,21 +0,0 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,44 +0,0 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'AllowDynamicProperties' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/AllowDynamicProperties.php',
'Attribute' => $vendorDir . '/symfony/polyfill/src/Php80/Resources/stubs/Attribute.php',
'CURLStringFile' => $vendorDir . '/symfony/polyfill/src/Php81/Resources/stubs/CURLStringFile.php',
'Collator' => $vendorDir . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/Collator.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'DateError' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateError.php',
'DateException' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateException.php',
'DateInvalidOperationException' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateInvalidOperationException.php',
'DateInvalidTimeZoneException' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateInvalidTimeZoneException.php',
'DateMalformedIntervalStringException' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateMalformedIntervalStringException.php',
'DateMalformedPeriodStringException' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateMalformedPeriodStringException.php',
'DateMalformedStringException' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateMalformedStringException.php',
'DateObjectError' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateObjectError.php',
'DateRangeError' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/DateRangeError.php',
'IntlDateFormatter' => $vendorDir . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/IntlDateFormatter.php',
'IntlException' => $vendorDir . '/symfony/polyfill/src/Intl/MessageFormatter/Resources/stubs/IntlException.php',
'JsonException' => $vendorDir . '/symfony/polyfill/src/Php73/Resources/stubs/JsonException.php',
'Locale' => $vendorDir . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/Locale.php',
'MessageFormatter' => $vendorDir . '/symfony/polyfill/src/Intl/MessageFormatter/Resources/stubs/MessageFormatter.php',
'Normalizer' => $vendorDir . '/symfony/polyfill/src/Intl/Normalizer/Resources/stubs/Normalizer.php',
'NumberFormatter' => $vendorDir . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/NumberFormatter.php',
'Override' => $vendorDir . '/symfony/polyfill/src/Php83/Resources/stubs/Override.php',
'PhpToken' => $vendorDir . '/symfony/polyfill/src/Php80/Resources/stubs/PhpToken.php',
'Random\\BrokenRandomEngineError' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/Random/BrokenRandomEngineError.php',
'Random\\CryptoSafeEngine' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/Random/CryptoSafeEngine.php',
'Random\\Engine' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/Random/Engine.php',
'Random\\Engine\\Secure' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/Random/Engine/Secure.php',
'Random\\RandomError' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/Random/RandomError.php',
'Random\\RandomException' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/Random/RandomException.php',
'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill/src/Php81/Resources/stubs/ReturnTypeWillChange.php',
'SensitiveParameter' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/SensitiveParameter.php',
'SensitiveParameterValue' => $vendorDir . '/symfony/polyfill/src/Php82/Resources/stubs/SensitiveParameterValue.php',
'Stringable' => $vendorDir . '/symfony/polyfill/src/Php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill/src/Php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill/src/Php80/Resources/stubs/ValueError.php',
);

View file

@ -1,21 +0,0 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'0333f8b5cd538ebe8681405ac533a9e1' => $vendorDir . '/symfony/polyfill/src/bootstrap.php',
'848a06382c1c883893a9c9833ae2b551' => $vendorDir . '/symfony/polyfill/src/Apcu/bootstrap.php',
'acf59695ef9115a36483a1d0a770cb98' => $vendorDir . '/symfony/polyfill/src/Ctype/bootstrap.php',
'5bea57cdb4ad2ea22b305ab861fa7a70' => $vendorDir . '/symfony/polyfill/src/Uuid/bootstrap.php',
'0782f09865a7e9f4ebd12e5f68b3135f' => $vendorDir . '/symfony/polyfill/src/Iconv/bootstrap.php',
'f38c346c3a1bb49bd02ba8e9177d7e56' => $vendorDir . '/symfony/polyfill/src/Intl/Grapheme/bootstrap.php',
'957101f585e4a9a2c29a37969ab22028' => $vendorDir . '/symfony/polyfill/src/Intl/Idn/bootstrap.php',
'946db64f9a5c0688514fdcdafcabe7d2' => $vendorDir . '/symfony/polyfill/src/Intl/Icu/bootstrap.php',
'a4e34e0535907b5c234b9abc547237ca' => $vendorDir . '/symfony/polyfill/src/Intl/MessageFormatter/bootstrap.php',
'299b3c040b39cb03c6eceb9bb272ad1d' => $vendorDir . '/symfony/polyfill/src/Intl/Normalizer/bootstrap.php',
'e59f725579f9974327c76777296d6dcc' => $vendorDir . '/symfony/polyfill/src/Mbstring/bootstrap.php',
'7bb4f001eb5212bde073bf47a4bbedad' => $vendorDir . '/szymach/c-pchart/constants.php',
);

View file

@ -1,9 +0,0 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View file

@ -1,15 +0,0 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'xPaw\\SourceQuery\\' => array($vendorDir . '/xpaw/php-source-query-class/SourceQuery'),
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'Symfony\\Polyfill\\' => array($vendorDir . '/symfony/polyfill/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'CpChart\\' => array($vendorDir . '/szymach/c-pchart/src'),
);

View file

@ -1,57 +0,0 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit76468c2eb232fdf4a8e3502b319270a4
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit76468c2eb232fdf4a8e3502b319270a4', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit76468c2eb232fdf4a8e3502b319270a4', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4::getInitializer($loader));
$loader->register(true);
$includeFiles = \Composer\Autoload\ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4::$files;
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire76468c2eb232fdf4a8e3502b319270a4($fileIdentifier, $file);
}
return $loader;
}
}
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequire76468c2eb232fdf4a8e3502b319270a4($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}

View file

@ -1,125 +0,0 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4
{
public static $files = array (
'0333f8b5cd538ebe8681405ac533a9e1' => __DIR__ . '/..' . '/symfony/polyfill/src/bootstrap.php',
'848a06382c1c883893a9c9833ae2b551' => __DIR__ . '/..' . '/symfony/polyfill/src/Apcu/bootstrap.php',
'acf59695ef9115a36483a1d0a770cb98' => __DIR__ . '/..' . '/symfony/polyfill/src/Ctype/bootstrap.php',
'5bea57cdb4ad2ea22b305ab861fa7a70' => __DIR__ . '/..' . '/symfony/polyfill/src/Uuid/bootstrap.php',
'0782f09865a7e9f4ebd12e5f68b3135f' => __DIR__ . '/..' . '/symfony/polyfill/src/Iconv/bootstrap.php',
'f38c346c3a1bb49bd02ba8e9177d7e56' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Grapheme/bootstrap.php',
'957101f585e4a9a2c29a37969ab22028' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Idn/bootstrap.php',
'946db64f9a5c0688514fdcdafcabe7d2' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Icu/bootstrap.php',
'a4e34e0535907b5c234b9abc547237ca' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/MessageFormatter/bootstrap.php',
'299b3c040b39cb03c6eceb9bb272ad1d' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Normalizer/bootstrap.php',
'e59f725579f9974327c76777296d6dcc' => __DIR__ . '/..' . '/symfony/polyfill/src/Mbstring/bootstrap.php',
'7bb4f001eb5212bde073bf47a4bbedad' => __DIR__ . '/..' . '/szymach/c-pchart/constants.php',
);
public static $prefixLengthsPsr4 = array (
'x' =>
array (
'xPaw\\SourceQuery\\' => 17,
),
'W' =>
array (
'Whoops\\' => 7,
),
'S' =>
array (
'Symfony\\Polyfill\\' => 17,
),
'P' =>
array (
'Psr\\Log\\' => 8,
),
'M' =>
array (
'Monolog\\' => 8,
),
'C' =>
array (
'CpChart\\' => 8,
),
);
public static $prefixDirsPsr4 = array (
'xPaw\\SourceQuery\\' =>
array (
0 => __DIR__ . '/..' . '/xpaw/php-source-query-class/SourceQuery',
),
'Whoops\\' =>
array (
0 => __DIR__ . '/..' . '/filp/whoops/src/Whoops',
),
'Symfony\\Polyfill\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'CpChart\\' =>
array (
0 => __DIR__ . '/..' . '/szymach/c-pchart/src',
),
);
public static $classMap = array (
'AllowDynamicProperties' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/AllowDynamicProperties.php',
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill/src/Php80/Resources/stubs/Attribute.php',
'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill/src/Php81/Resources/stubs/CURLStringFile.php',
'Collator' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/Collator.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'DateError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateError.php',
'DateException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateException.php',
'DateInvalidOperationException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateInvalidOperationException.php',
'DateInvalidTimeZoneException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateInvalidTimeZoneException.php',
'DateMalformedIntervalStringException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateMalformedIntervalStringException.php',
'DateMalformedPeriodStringException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateMalformedPeriodStringException.php',
'DateMalformedStringException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateMalformedStringException.php',
'DateObjectError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateObjectError.php',
'DateRangeError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/DateRangeError.php',
'IntlDateFormatter' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/IntlDateFormatter.php',
'IntlException' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/MessageFormatter/Resources/stubs/IntlException.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php73/Resources/stubs/JsonException.php',
'Locale' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/Locale.php',
'MessageFormatter' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/MessageFormatter/Resources/stubs/MessageFormatter.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Normalizer/Resources/stubs/Normalizer.php',
'NumberFormatter' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Icu/Resources/stubs/NumberFormatter.php',
'Override' => __DIR__ . '/..' . '/symfony/polyfill/src/Php83/Resources/stubs/Override.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill/src/Php80/Resources/stubs/PhpToken.php',
'Random\\BrokenRandomEngineError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/Random/BrokenRandomEngineError.php',
'Random\\CryptoSafeEngine' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/Random/CryptoSafeEngine.php',
'Random\\Engine' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/Random/Engine.php',
'Random\\Engine\\Secure' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/Random/Engine/Secure.php',
'Random\\RandomError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/Random/RandomError.php',
'Random\\RandomException' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/Random/RandomException.php',
'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill/src/Php81/Resources/stubs/ReturnTypeWillChange.php',
'SensitiveParameter' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/SensitiveParameter.php',
'SensitiveParameterValue' => __DIR__ . '/..' . '/symfony/polyfill/src/Php82/Resources/stubs/SensitiveParameterValue.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill/src/Php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill/src/Php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4::$classMap;
}, null, ClassLoader::class);
}
}

View file

@ -1,487 +0,0 @@
{
"packages": [
{
"name": "filp/whoops",
"version": "2.15.4",
"version_normalized": "2.15.4.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
"reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
"whoops/soap": "Formats errors as SOAP responses"
},
"time": "2023-11-03T12:00:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Whoops\\": "src/Whoops/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"description": "php error handling for cool kids",
"homepage": "https://filp.github.io/whoops/",
"keywords": [
"error",
"exception",
"handling",
"library",
"throwable",
"whoops"
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.15.4"
},
"funding": [
{
"url": "https://github.com/denis-sokolov",
"type": "github"
}
],
"install-path": "../filp/whoops"
},
{
"name": "monolog/monolog",
"version": "2.9.2",
"version_normalized": "2.9.2.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
"reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^0.12.91",
"phpunit/phpunit": "^8.5.14",
"predis/predis": "^1.1 || ^2.0",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"time": "2023-10-27T15:25:26+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.9.2"
},
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"install-path": "../monolog/monolog"
},
{
"name": "psr/log",
"version": "1.1.4",
"version_normalized": "1.1.4.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2021-05-03T11:20:27+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.4"
},
"install-path": "../psr/log"
},
{
"name": "symfony/polyfill",
"version": "v1.28.0",
"version_normalized": "1.28.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill.git",
"reference": "33def419104fb3cf14be4e8638683eb9845c2522"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill/zipball/33def419104fb3cf14be4e8638683eb9845c2522",
"reference": "33def419104fb3cf14be4e8638683eb9845c2522",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"replace": {
"symfony/polyfill-apcu": "self.version",
"symfony/polyfill-ctype": "self.version",
"symfony/polyfill-iconv": "self.version",
"symfony/polyfill-intl-grapheme": "self.version",
"symfony/polyfill-intl-icu": "self.version",
"symfony/polyfill-intl-idn": "self.version",
"symfony/polyfill-intl-messageformatter": "self.version",
"symfony/polyfill-intl-normalizer": "self.version",
"symfony/polyfill-mbstring": "self.version",
"symfony/polyfill-php72": "self.version",
"symfony/polyfill-php73": "self.version",
"symfony/polyfill-php74": "self.version",
"symfony/polyfill-php80": "self.version",
"symfony/polyfill-php81": "self.version",
"symfony/polyfill-php82": "self.version",
"symfony/polyfill-php83": "self.version",
"symfony/polyfill-util": "self.version",
"symfony/polyfill-uuid": "self.version",
"symfony/polyfill-xml": "self.version"
},
"require-dev": {
"symfony/intl": "^4.4|^5.0|^6.0",
"symfony/phpunit-bridge": "^5.3|^6.0",
"symfony/var-dumper": "^4.4|^5.1|^6.0"
},
"time": "2023-08-25T17:27:34+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"src/bootstrap.php",
"src/Apcu/bootstrap.php",
"src/Ctype/bootstrap.php",
"src/Uuid/bootstrap.php",
"src/Iconv/bootstrap.php",
"src/Intl/Grapheme/bootstrap.php",
"src/Intl/Idn/bootstrap.php",
"src/Intl/Icu/bootstrap.php",
"src/Intl/MessageFormatter/bootstrap.php",
"src/Intl/Normalizer/bootstrap.php",
"src/Mbstring/bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\": "src/"
},
"classmap": [
"src/Intl/Icu/Resources/stubs",
"src/Intl/MessageFormatter/Resources/stubs",
"src/Intl/Normalizer/Resources/stubs",
"src/Php83/Resources/stubs",
"src/Php82/Resources/stubs",
"src/Php81/Resources/stubs",
"src/Php80/Resources/stubs",
"src/Php73/Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfills backporting features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compat",
"compatibility",
"polyfill",
"shim"
],
"support": {
"issues": "https://github.com/symfony/polyfill/issues",
"source": "https://github.com/symfony/polyfill/tree/v1.28.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill"
},
{
"name": "szymach/c-pchart",
"version": "v3.0.17",
"version_normalized": "3.0.17.0",
"source": {
"type": "git",
"url": "https://github.com/szymach/c-pchart.git",
"reference": "c022568da78f73bb7acf7db7a974934a434d47d7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/szymach/c-pchart/zipball/c022568da78f73bb7acf7db7a974934a434d47d7",
"reference": "c022568da78f73bb7acf7db7a974934a434d47d7",
"shasum": ""
},
"require": {
"ext-gd": "*",
"php": "^5.4|^7.0|^8.0"
},
"require-dev": {
"codeception/codeception": "^4.1.22",
"codeception/module-asserts": "^1.3",
"codeception/module-filesystem": "^1.0",
"phpunit/phpunit": "^5.7|^9.5",
"squizlabs/php_codesniffer": "^3.4"
},
"time": "2023-05-27T11:12:02+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev",
"2.0": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"constants.php"
],
"psr-4": {
"CpChart\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-only"
],
"authors": [
{
"name": "Jean-Damien Pogolotti",
"homepage": "http://www.pchart.net",
"role": "Creator of the original pChart library"
},
{
"name": "Piotr Szymaszek",
"homepage": "https://github.com/szymach",
"role": "Developer of the CpChart wrapper package"
}
],
"description": "Port of \"pChart\" library into PHP 5+",
"homepage": "https://github.com/szymach/c-pchart",
"keywords": [
"CpChart",
"c-pChart",
"charts",
"pchart",
"statistics"
],
"support": {
"issues": "https://github.com/szymach/c-pchart/issues",
"source": "https://github.com/szymach/c-pchart/tree/v3.0.17"
},
"install-path": "../szymach/c-pchart"
},
{
"name": "xpaw/php-source-query-class",
"version": "2.1.0",
"version_normalized": "2.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/xPaw/PHP-Source-Query.git",
"reference": "673e572233c3ab4b70b302d648ebaf5a8d9ba32b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/xPaw/PHP-Source-Query/zipball/673e572233c3ab4b70b302d648ebaf5a8d9ba32b",
"reference": "673e572233c3ab4b70b302d648ebaf5a8d9ba32b",
"shasum": ""
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "9.2",
"vimeo/psalm": "^3.12"
},
"time": "2020-12-04T08:20:42+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"xPaw\\SourceQuery\\": "SourceQuery/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"description": "PHP library to query and send RCON commands to servers based on \"Source Engine Query\" protocol",
"homepage": "https://github.com/xPaw/PHP-Source-Query",
"keywords": [
"ark",
"counter-strike",
"csgo",
"gmod",
"minecraft",
"rcon",
"rust",
"starbound",
"team fortress"
],
"support": {
"source": "https://github.com/xPaw/PHP-Source-Query/tree/2.1.0"
},
"funding": [
{
"url": "https://github.com/xPaw",
"type": "github"
}
],
"install-path": "../xpaw/php-source-query-class"
}
],
"dev": true,
"dev-package-names": []
}

View file

@ -1,197 +0,0 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'filp/whoops' => array(
'pretty_version' => '2.15.4',
'version' => '2.15.4.0',
'reference' => 'a139776fa3f5985a50b509f2a02ff0f709d2a546',
'type' => 'library',
'install_path' => __DIR__ . '/../filp/whoops',
'aliases' => array(),
'dev_requirement' => false,
),
'monolog/monolog' => array(
'pretty_version' => '2.9.2',
'version' => '2.9.2.0',
'reference' => '437cb3628f4cf6042cc10ae97fc2b8472e48ca1f',
'type' => 'library',
'install_path' => __DIR__ . '/../monolog/monolog',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log' => array(
'pretty_version' => '1.1.4',
'version' => '1.1.4.0',
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0.0 || 2.0.0 || 3.0.0',
),
),
'symfony/polyfill' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => '33def419104fb3cf14be4e8638683eb9845c2522',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-apcu' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-ctype' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-iconv' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-intl-grapheme' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-intl-icu' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-intl-idn' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-intl-messageformatter' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-intl-normalizer' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-mbstring' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php72' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php73' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php74' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php80' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php81' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php82' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-php83' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-util' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-uuid' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'symfony/polyfill-xml' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.28.0',
),
),
'szymach/c-pchart' => array(
'pretty_version' => 'v3.0.17',
'version' => '3.0.17.0',
'reference' => 'c022568da78f73bb7acf7db7a974934a434d47d7',
'type' => 'library',
'install_path' => __DIR__ . '/../szymach/c-pchart',
'aliases' => array(),
'dev_requirement' => false,
),
'xpaw/php-source-query-class' => array(
'pretty_version' => '2.1.0',
'version' => '2.1.0.0',
'reference' => '673e572233c3ab4b70b302d648ebaf5a8d9ba32b',
'type' => 'library',
'install_path' => __DIR__ . '/../xpaw/php-source-query-class',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View file

@ -1,26 +0,0 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70400)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View file

@ -1,2 +0,0 @@
Denis Sokolov <denis@sokolov.cc>
Filipe Dobreira <dobreira@gmail.com>

View file

@ -1,155 +0,0 @@
# CHANGELOG
## v2.15.4
* Improve link color in comments.
## v2.15.3
* Improve performance of the syntax highlighting (#758).
## v2.15.2
* Fixed missing code highlight, which additionally led to issue with switching tabs, between application and all frames ([#747](https://github.com/filp/whoops/issues/747)).
## v2.15.1
* Fixed bug with PrettyPageHandler "*Calling `getFrameFilters` method on null*" ([#751](https://github.com/filp/whoops/pull/751)).
## v2.15.0
* Add addFrameFilter ([#749](https://github.com/filp/whoops/pull/749))
## v2.14.6
* Upgraded prismJS to version `1.29.0` due to security issue ([#741][i741]).
[i741]: https://github.com/filp/whoops/pull/741
## v2.14.5
* Allow `ArrayAccess` on super globals.
## v2.14.4
* Fix PHP `5.5` support.
* Allow to use psr/log `2` or `3`.
## v2.14.3
* Support PHP `8.1`.
## v2.14.1
* Fix syntax highlighting scrolling too far.
* Improve the way we detect xdebug linkformat.
## v2.14.0
* Switched syntax highlighting to Prism.js.
Avoids licensing issues with prettify, and uses a maintained, modern project.
## v2.13.0
* Add Netbeans editor.
## v2.12.1
* Avoid redirecting away from an error.
## v2.12.0
* Hide non-string values in super globals when requested.
## v2.11.0
* Customize exit code.
## v2.10.0
* Better chaining on handler classes.
## v2.9.2
* Fix copy button styles.
## v2.9.1
* Fix xdebug function crash on PHP `8`.
## v2.9.0
* `JsonResponseHandler` includes the exception code.
## v2.8.0
* Support PHP 8.
## v2.7.3
* `PrettyPageHandler` functionality to hide superglobal keys has a clearer name
(`hideSuperglobalKey`).
## v2.7.2
* `PrettyPageHandler` now accepts custom js files.
* `PrettyPageHandler` and `templateHelper` is now accessible through inheritance.
## v2.7.1
* Fix a PHP warning in some cases with anonymous classes.
## v2.7.0
* Added `removeFirstHandler` and `removeLastHandler`.
## v2.6.0
* Fix 2.4.0 `pushHandler` changing the order of handlers.
## v2.5.1
* Fix error messaging in a rare case.
## v2.5.0
* Automatically configure xdebug if available.
## v2.4.1
* Try harder to close all output buffers.
## v2.4.0
* Allow to prepend and append handlers.
## v2.3.2
* Various fixes from the community.
## v2.3.1
* Prevent exception in Whoops when caught exception frame is not related to real file.
## v2.3.0
* Show previous exception messages.
## v2.2.0
* Support PHP `7.2`.
## v2.1.0
* Add a `SystemFacade` to allow clients to override Whoops behavior.
* Show frame arguments in `PrettyPageHandler`.
* Highlight the line with the error.
* Add icons to search on Google and Stack Overflow.
## v2.0.0
Backwards compatibility breaking changes:
* `Run` class is now `final`. If you inherited from `Run`, please now instead use a custom `SystemFacade` injected into the `Run` constructor, or contribute your changes to our core.
* PHP < 5.5 support dropped.

View file

@ -1,19 +0,0 @@
# The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,12 +0,0 @@
# Security Policy
## Supported Versions
Only the latest released version of Whoops is supported.
To facilitate upgrades we almost never make backwards-incompatible changes.
## Reporting a Vulnerability
Please report vulnerabilities over email, by sending an email to `denis` at `sokolov` dot `cc`.

View file

@ -1,45 +0,0 @@
{
"name": "filp/whoops",
"license": "MIT",
"description": "php error handling for cool kids",
"keywords": ["library", "error", "handling", "exception", "whoops", "throwable"],
"homepage": "https://filp.github.io/whoops/",
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"scripts": {
"test": "phpunit --testdox tests"
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"mockery/mockery": "^0.9 || ^1.0",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
"whoops/soap": "Formats errors as SOAP responses"
},
"autoload": {
"psr-4": {
"Whoops\\": "src/Whoops/"
}
},
"autoload-dev": {
"psr-4": {
"Whoops\\": "tests/Whoops/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
}
}

View file

@ -1,17 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use ErrorException as BaseErrorException;
/**
* Wraps ErrorException; mostly used for typing (at least now)
* to easily cleanup the stack trace of redundant info.
*/
class ErrorException extends BaseErrorException
{
}

View file

@ -1,77 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use Whoops\Inspector\InspectorInterface;
class Formatter
{
/**
* Returns all basic information about the exception in a simple array
* for further convertion to other languages
* @param InspectorInterface $inspector
* @param bool $shouldAddTrace
* @param array<callable> $frameFilters
* @return array
*/
public static function formatExceptionAsDataArray(InspectorInterface $inspector, $shouldAddTrace, array $frameFilters = [])
{
$exception = $inspector->getException();
$response = [
'type' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
];
if ($shouldAddTrace) {
$frames = $inspector->getFrames($frameFilters);
$frameData = [];
foreach ($frames as $frame) {
/** @var Frame $frame */
$frameData[] = [
'file' => $frame->getFile(),
'line' => $frame->getLine(),
'function' => $frame->getFunction(),
'class' => $frame->getClass(),
'args' => $frame->getArgs(),
];
}
$response['trace'] = $frameData;
}
return $response;
}
public static function formatExceptionPlain(InspectorInterface $inspector)
{
$message = $inspector->getException()->getMessage();
$frames = $inspector->getFrames();
$plain = $inspector->getExceptionName();
$plain .= ' thrown with message "';
$plain .= $message;
$plain .= '"'."\n\n";
$plain .= "Stacktrace:\n";
foreach ($frames as $i => $frame) {
$plain .= "#". (count($frames) - $i - 1). " ";
$plain .= $frame->getClass() ?: '';
$plain .= $frame->getClass() && $frame->getFunction() ? ":" : "";
$plain .= $frame->getFunction() ?: '';
$plain .= ' in ';
$plain .= ($frame->getFile() ?: '<#unknown>');
$plain .= ':';
$plain .= (int) $frame->getLine(). "\n";
}
return $plain;
}
}

View file

@ -1,311 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use InvalidArgumentException;
use Serializable;
class Frame implements Serializable
{
/**
* @var array
*/
protected $frame;
/**
* @var string
*/
protected $fileContentsCache;
/**
* @var array[]
*/
protected $comments = [];
/**
* @var bool
*/
protected $application;
public function __construct(array $frame)
{
$this->frame = $frame;
}
/**
* @param bool $shortened
* @return string|null
*/
public function getFile($shortened = false)
{
if (empty($this->frame['file'])) {
return null;
}
$file = $this->frame['file'];
// Check if this frame occurred within an eval().
// @todo: This can be made more reliable by checking if we've entered
// eval() in a previous trace, but will need some more work on the upper
// trace collector(s).
if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) {
$file = $this->frame['file'] = $matches[1];
$this->frame['line'] = (int) $matches[2];
}
if ($shortened && is_string($file)) {
// Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks.
$dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
if ($dirname !== '/') {
$file = str_replace($dirname, "&hellip;", $file);
}
$file = str_replace("/", "/&shy;", $file);
}
return $file;
}
/**
* @return int|null
*/
public function getLine()
{
return isset($this->frame['line']) ? $this->frame['line'] : null;
}
/**
* @return string|null
*/
public function getClass()
{
return isset($this->frame['class']) ? $this->frame['class'] : null;
}
/**
* @return string|null
*/
public function getFunction()
{
return isset($this->frame['function']) ? $this->frame['function'] : null;
}
/**
* @return array
*/
public function getArgs()
{
return isset($this->frame['args']) ? (array) $this->frame['args'] : [];
}
/**
* Returns the full contents of the file for this frame,
* if it's known.
* @return string|null
*/
public function getFileContents()
{
if ($this->fileContentsCache === null && $filePath = $this->getFile()) {
// Leave the stage early when 'Unknown' or '[internal]' is passed
// this would otherwise raise an exception when
// open_basedir is enabled.
if ($filePath === "Unknown" || $filePath === '[internal]') {
return null;
}
try {
$this->fileContentsCache = file_get_contents($filePath);
} catch (ErrorException $exception) {
// Internal file paths of PHP extensions cannot be opened
}
}
return $this->fileContentsCache;
}
/**
* Adds a comment to this frame, that can be received and
* used by other handlers. For example, the PrettyPage handler
* can attach these comments under the code for each frame.
*
* An interesting use for this would be, for example, code analysis
* & annotations.
*
* @param string $comment
* @param string $context Optional string identifying the origin of the comment
*/
public function addComment($comment, $context = 'global')
{
$this->comments[] = [
'comment' => $comment,
'context' => $context,
];
}
/**
* Returns all comments for this frame. Optionally allows
* a filter to only retrieve comments from a specific
* context.
*
* @param string $filter
* @return array[]
*/
public function getComments($filter = null)
{
$comments = $this->comments;
if ($filter !== null) {
$comments = array_filter($comments, function ($c) use ($filter) {
return $c['context'] == $filter;
});
}
return $comments;
}
/**
* Returns the array containing the raw frame data from which
* this Frame object was built
*
* @return array
*/
public function getRawFrame()
{
return $this->frame;
}
/**
* Returns the contents of the file for this frame as an
* array of lines, and optionally as a clamped range of lines.
*
* NOTE: lines are 0-indexed
*
* @example
* Get all lines for this file
* $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...)
* @example
* Get one line for this file, starting at line 10 (zero-indexed, remember!)
* $frame->getFileLines(9, 1); // array( 9 => '...' )
*
* @throws InvalidArgumentException if $length is less than or equal to 0
* @param int $start
* @param int $length
* @return string[]|null
*/
public function getFileLines($start = 0, $length = null)
{
if (null !== ($contents = $this->getFileContents())) {
$lines = explode("\n", $contents);
// Get a subset of lines from $start to $end
if ($length !== null) {
$start = (int) $start;
$length = (int) $length;
if ($start < 0) {
$start = 0;
}
if ($length <= 0) {
throw new InvalidArgumentException(
"\$length($length) cannot be lower or equal to 0"
);
}
$lines = array_slice($lines, $start, $length, true);
}
return $lines;
}
}
/**
* Implements the Serializable interface, with special
* steps to also save the existing comments.
*
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
$frame = $this->frame;
if (!empty($this->comments)) {
$frame['_comments'] = $this->comments;
}
return serialize($frame);
}
public function __serialize()
{
$frame = $this->frame;
if (!empty($this->comments)) {
$frame['_comments'] = $this->comments;
}
return $frame;
}
/**
* Unserializes the frame data, while also preserving
* any existing comment data.
*
* @see Serializable::unserialize
* @param string $serializedFrame
*/
public function unserialize($serializedFrame)
{
$frame = unserialize($serializedFrame);
if (!empty($frame['_comments'])) {
$this->comments = $frame['_comments'];
unset($frame['_comments']);
}
$this->frame = $frame;
}
public function __unserialize($frame)
{
if (!empty($frame['_comments'])) {
$this->comments = $frame['_comments'];
unset($frame['_comments']);
}
$this->frame = $frame;
}
/**
* Compares Frame against one another
* @param Frame $frame
* @return bool
*/
public function equals(Frame $frame)
{
if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) {
return false;
}
return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine();
}
/**
* Returns whether this frame belongs to the application or not.
*
* @return boolean
*/
public function isApplication()
{
return $this->application;
}
/**
* Mark as an frame belonging to the application.
*
* @param boolean $application
*/
public function setApplication($application)
{
$this->application = $application;
}
}

View file

@ -1,219 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use ReturnTypeWillChange;
use Serializable;
use UnexpectedValueException;
/**
* Exposes a fluent interface for dealing with an ordered list
* of stack-trace frames.
*/
class FrameCollection implements ArrayAccess, IteratorAggregate, Serializable, Countable
{
/**
* @var array[]
*/
private $frames;
public function __construct(array $frames)
{
$this->frames = array_map(function ($frame) {
return new Frame($frame);
}, $frames);
}
/**
* Filters frames using a callable, returns the same FrameCollection
*
* @param callable $callable
* @return FrameCollection
*/
public function filter($callable)
{
$this->frames = array_values(array_filter($this->frames, $callable));
return $this;
}
/**
* Map the collection of frames
*
* @param callable $callable
* @return FrameCollection
*/
public function map($callable)
{
// Contain the map within a higher-order callable
// that enforces type-correctness for the $callable
$this->frames = array_map(function ($frame) use ($callable) {
$frame = call_user_func($callable, $frame);
if (!$frame instanceof Frame) {
throw new UnexpectedValueException(
"Callable to " . __CLASS__ . "::map must return a Frame object"
);
}
return $frame;
}, $this->frames);
return $this;
}
/**
* Returns an array with all frames, does not affect
* the internal array.
*
* @todo If this gets any more complex than this,
* have getIterator use this method.
* @see FrameCollection::getIterator
* @return array
*/
public function getArray()
{
return $this->frames;
}
/**
* @see IteratorAggregate::getIterator
* @return ArrayIterator
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->frames);
}
/**
* @see ArrayAccess::offsetExists
* @param int $offset
*/
#[ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->frames[$offset]);
}
/**
* @see ArrayAccess::offsetGet
* @param int $offset
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->frames[$offset];
}
/**
* @see ArrayAccess::offsetSet
* @param int $offset
*/
#[ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
throw new \Exception(__CLASS__ . ' is read only');
}
/**
* @see ArrayAccess::offsetUnset
* @param int $offset
*/
#[ReturnTypeWillChange]
public function offsetUnset($offset)
{
throw new \Exception(__CLASS__ . ' is read only');
}
/**
* @see Countable::count
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
return count($this->frames);
}
/**
* Count the frames that belongs to the application.
*
* @return int
*/
public function countIsApplication()
{
return count(array_filter($this->frames, function (Frame $f) {
return $f->isApplication();
}));
}
/**
* @see Serializable::serialize
* @return string
*/
#[ReturnTypeWillChange]
public function serialize()
{
return serialize($this->frames);
}
/**
* @see Serializable::unserialize
* @param string $serializedFrames
*/
#[ReturnTypeWillChange]
public function unserialize($serializedFrames)
{
$this->frames = unserialize($serializedFrames);
}
public function __serialize()
{
return $this->frames;
}
public function __unserialize(array $serializedFrames)
{
$this->frames = $serializedFrames;
}
/**
* @param Frame[] $frames Array of Frame instances, usually from $e->getPrevious()
*/
public function prependFrames(array $frames)
{
$this->frames = array_merge($frames, $this->frames);
}
/**
* Gets the innermost part of stack trace that is not the same as that of outer exception
*
* @param FrameCollection $parentFrames Outer exception frames to compare tail against
* @return Frame[]
*/
public function topDiff(FrameCollection $parentFrames)
{
$diff = $this->frames;
$parentFrames = $parentFrames->getArray();
$p = count($parentFrames)-1;
for ($i = count($diff)-1; $i >= 0 && $p >= 0; $i--) {
/** @var Frame $tailFrame */
$tailFrame = $diff[$i];
if ($tailFrame->equals($parentFrames[$p])) {
unset($diff[$i]);
}
$p--;
}
return $diff;
}
}

View file

@ -1,341 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use Whoops\Inspector\InspectorFactory;
use Whoops\Inspector\InspectorInterface;
use Whoops\Util\Misc;
class Inspector implements InspectorInterface
{
/**
* @var \Throwable
*/
private $exception;
/**
* @var \Whoops\Exception\FrameCollection
*/
private $frames;
/**
* @var \Whoops\Exception\Inspector
*/
private $previousExceptionInspector;
/**
* @var \Throwable[]
*/
private $previousExceptions;
/**
* @var \Whoops\Inspector\InspectorFactoryInterface|null
*/
protected $inspectorFactory;
/**
* @param \Throwable $exception The exception to inspect
* @param \Whoops\Inspector\InspectorFactoryInterface $factory
*/
public function __construct($exception, $factory = null)
{
$this->exception = $exception;
$this->inspectorFactory = $factory ?: new InspectorFactory();
}
/**
* @return \Throwable
*/
public function getException()
{
return $this->exception;
}
/**
* @return string
*/
public function getExceptionName()
{
return get_class($this->exception);
}
/**
* @return string
*/
public function getExceptionMessage()
{
return $this->extractDocrefUrl($this->exception->getMessage())['message'];
}
/**
* @return string[]
*/
public function getPreviousExceptionMessages()
{
return array_map(function ($prev) {
/** @var \Throwable $prev */
return $this->extractDocrefUrl($prev->getMessage())['message'];
}, $this->getPreviousExceptions());
}
/**
* @return int[]
*/
public function getPreviousExceptionCodes()
{
return array_map(function ($prev) {
/** @var \Throwable $prev */
return $prev->getCode();
}, $this->getPreviousExceptions());
}
/**
* Returns a url to the php-manual related to the underlying error - when available.
*
* @return string|null
*/
public function getExceptionDocrefUrl()
{
return $this->extractDocrefUrl($this->exception->getMessage())['url'];
}
private function extractDocrefUrl($message)
{
$docref = [
'message' => $message,
'url' => null,
];
// php embbeds urls to the manual into the Exception message with the following ini-settings defined
// http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
if (!ini_get('html_errors') || !ini_get('docref_root')) {
return $docref;
}
$pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
if (preg_match($pattern, $message, $matches)) {
// -> strip those automatically generated links from the exception message
$docref['message'] = preg_replace($pattern, '', $message, 1);
$docref['url'] = $matches[1];
}
return $docref;
}
/**
* Does the wrapped Exception has a previous Exception?
* @return bool
*/
public function hasPreviousException()
{
return $this->previousExceptionInspector || $this->exception->getPrevious();
}
/**
* Returns an Inspector for a previous Exception, if any.
* @todo Clean this up a bit, cache stuff a bit better.
* @return Inspector
*/
public function getPreviousExceptionInspector()
{
if ($this->previousExceptionInspector === null) {
$previousException = $this->exception->getPrevious();
if ($previousException) {
$this->previousExceptionInspector = $this->inspectorFactory->create($previousException);
}
}
return $this->previousExceptionInspector;
}
/**
* Returns an array of all previous exceptions for this inspector's exception
* @return \Throwable[]
*/
public function getPreviousExceptions()
{
if ($this->previousExceptions === null) {
$this->previousExceptions = [];
$prev = $this->exception->getPrevious();
while ($prev !== null) {
$this->previousExceptions[] = $prev;
$prev = $prev->getPrevious();
}
}
return $this->previousExceptions;
}
/**
* Returns an iterator for the inspected exception's
* frames.
*
* @param array<callable> $frameFilters
*
* @return \Whoops\Exception\FrameCollection
*/
public function getFrames(array $frameFilters = [])
{
if ($this->frames === null) {
$frames = $this->getTrace($this->exception);
// Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
foreach ($frames as $k => $frame) {
if (empty($frame['file'])) {
// Default values when file and line are missing
$file = '[internal]';
$line = 0;
$next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
if ($this->isValidNextFrame($next_frame)) {
$file = $next_frame['file'];
$line = $next_frame['line'];
}
$frames[$k]['file'] = $file;
$frames[$k]['line'] = $line;
}
}
// Find latest non-error handling frame index ($i) used to remove error handling frames
$i = 0;
foreach ($frames as $k => $frame) {
if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
$i = $k;
}
}
// Remove error handling frames
if ($i > 0) {
array_splice($frames, 0, $i);
}
$firstFrame = $this->getFrameFromException($this->exception);
array_unshift($frames, $firstFrame);
$this->frames = new FrameCollection($frames);
if ($previousInspector = $this->getPreviousExceptionInspector()) {
// Keep outer frame on top of the inner one
$outerFrames = $this->frames;
$newFrames = clone $previousInspector->getFrames();
// I assume it will always be set, but let's be safe
if (isset($newFrames[0])) {
$newFrames[0]->addComment(
$previousInspector->getExceptionMessage(),
'Exception message:'
);
}
$newFrames->prependFrames($outerFrames->topDiff($newFrames));
$this->frames = $newFrames;
}
// Apply frame filters callbacks on the frames stack
if (!empty($frameFilters)) {
foreach ($frameFilters as $filterCallback) {
$this->frames->filter($filterCallback);
}
}
}
return $this->frames;
}
/**
* Gets the backtrace from an exception.
*
* If xdebug is installed
*
* @param \Throwable $e
* @return array
*/
protected function getTrace($e)
{
$traces = $e->getTrace();
// Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
if (!$e instanceof \ErrorException) {
return $traces;
}
if (!Misc::isLevelFatal($e->getSeverity())) {
return $traces;
}
if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) {
return $traces;
}
// Use xdebug to get the full stack trace and remove the shutdown handler stack trace
$stack = array_reverse(xdebug_get_function_stack());
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$traces = array_diff_key($stack, $trace);
return $traces;
}
/**
* Given an exception, generates an array in the format
* generated by Exception::getTrace()
* @param \Throwable $exception
* @return array
*/
protected function getFrameFromException($exception)
{
return [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => get_class($exception),
'args' => [
$exception->getMessage(),
],
];
}
/**
* Given an error, generates an array in the format
* generated by ErrorException
* @param ErrorException $exception
* @return array
*/
protected function getFrameFromError(ErrorException $exception)
{
return [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => null,
'args' => [],
];
}
/**
* Determine if the frame can be used to fill in previous frame's missing info
* happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
*
* @return bool
*/
protected function isValidNextFrame(array $frame)
{
if (empty($frame['file'])) {
return false;
}
if (empty($frame['line'])) {
return false;
}
if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
return false;
}
return true;
}
}

View file

@ -1,52 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
/**
* Wrapper for Closures passed as handlers. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class CallbackHandler extends Handler
{
/**
* @var callable
*/
protected $callable;
/**
* @throws InvalidArgumentException If argument is not callable
* @param callable $callable
*/
public function __construct($callable)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ . ' must be valid callable'
);
}
$this->callable = $callable;
}
/**
* @return int|null
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
$callable = $this->callable;
// invoke the callable directly, to get simpler stacktraces (in comparison to call_user_func).
// this assumes that $callable is a properly typed php-callable, which we check in __construct().
return $callable($exception, $inspector, $run);
}
}

View file

@ -1,95 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Inspector\InspectorInterface;
use Whoops\RunInterface;
/**
* Abstract implementation of a Handler.
*/
abstract class Handler implements HandlerInterface
{
/*
Return constants that can be returned from Handler::handle
to message the handler walker.
*/
const DONE = 0x10; // returning this is optional, only exists for
// semantic purposes
/**
* The Handler has handled the Throwable in some way, and wishes to skip any other Handler.
* Execution will continue.
*/
const LAST_HANDLER = 0x20;
/**
* The Handler has handled the Throwable in some way, and wishes to quit/stop execution
*/
const QUIT = 0x30;
/**
* @var RunInterface
*/
private $run;
/**
* @var InspectorInterface $inspector
*/
private $inspector;
/**
* @var \Throwable $exception
*/
private $exception;
/**
* @param RunInterface $run
*/
public function setRun(RunInterface $run)
{
$this->run = $run;
}
/**
* @return RunInterface
*/
protected function getRun()
{
return $this->run;
}
/**
* @param InspectorInterface $inspector
*/
public function setInspector(InspectorInterface $inspector)
{
$this->inspector = $inspector;
}
/**
* @return InspectorInterface
*/
protected function getInspector()
{
return $this->inspector;
}
/**
* @param \Throwable $exception
*/
public function setException($exception)
{
$this->exception = $exception;
}
/**
* @return \Throwable
*/
protected function getException()
{
return $this->exception;
}
}

View file

@ -1,36 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Inspector\InspectorInterface;
use Whoops\RunInterface;
interface HandlerInterface
{
/**
* @return int|null A handler may return nothing, or a Handler::HANDLE_* constant
*/
public function handle();
/**
* @param RunInterface $run
* @return void
*/
public function setRun(RunInterface $run);
/**
* @param \Throwable $exception
* @return void
*/
public function setException($exception);
/**
* @param InspectorInterface $inspector
* @return void
*/
public function setInspector(InspectorInterface $inspector);
}

View file

@ -1,90 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Exception\Formatter;
/**
* Catches an exception and converts it to a JSON
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class JsonResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @var bool
*/
private $jsonApi = false;
/**
* Returns errors[[]] instead of error[] to be in compliance with the json:api spec
* @param bool $jsonApi Default is false
* @return static
*/
public function setJsonApi($jsonApi = false)
{
$this->jsonApi = (bool) $jsonApi;
return $this;
}
/**
* @param bool|null $returnFrames
* @return bool|static
*/
public function addTraceToOutput($returnFrames = null)
{
if (func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
return $this;
}
/**
* @return int
*/
public function handle()
{
if ($this->jsonApi === true) {
$response = [
'errors' => [
Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput(),
$this->getRun()->getFrameFilters()
),
]
];
} else {
$response = [
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput(),
$this->getRun()->getFrameFilters()
),
];
}
echo json_encode($response, defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0);
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'application/json';
}
}

View file

@ -1,359 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
* Plaintext handler for command line and logs.
* @author Pierre-Yves Landuré <https://howto.biapy.com/>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Whoops\Exception\Frame;
/**
* Handler outputing plaintext error messages. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class PlainTextHandler extends Handler
{
const VAR_DUMP_PREFIX = ' | ';
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* @var callable
*/
protected $dumper;
/**
* @var bool
*/
private $addTraceToOutput = true;
/**
* @var bool|integer
*/
private $addTraceFunctionArgsToOutput = false;
/**
* @var integer
*/
private $traceFunctionArgsOutputLimit = 1024;
/**
* @var bool
*/
private $addPreviousToOutput = true;
/**
* @var bool
*/
private $loggerOnly = false;
/**
* Constructor.
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
* @param \Psr\Log\LoggerInterface|null $logger
*/
public function __construct($logger = null)
{
$this->setLogger($logger);
}
/**
* Set the output logger interface.
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
* @param \Psr\Log\LoggerInterface|null $logger
*/
public function setLogger($logger = null)
{
if (! (is_null($logger)
|| $logger instanceof LoggerInterface)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ .
" must be a valid Logger Interface (aka. Monolog), " .
get_class($logger) . ' given.'
);
}
$this->logger = $logger;
}
/**
* @return \Psr\Log\LoggerInterface|null
*/
public function getLogger()
{
return $this->logger;
}
/**
* Set var dumper callback function.
*
* @param callable $dumper
* @return static
*/
public function setDumper(callable $dumper)
{
$this->dumper = $dumper;
return $this;
}
/**
* Add error trace to output.
* @param bool|null $addTraceToOutput
* @return bool|static
*/
public function addTraceToOutput($addTraceToOutput = null)
{
if (func_num_args() == 0) {
return $this->addTraceToOutput;
}
$this->addTraceToOutput = (bool) $addTraceToOutput;
return $this;
}
/**
* Add previous exceptions to output.
* @param bool|null $addPreviousToOutput
* @return bool|static
*/
public function addPreviousToOutput($addPreviousToOutput = null)
{
if (func_num_args() == 0) {
return $this->addPreviousToOutput;
}
$this->addPreviousToOutput = (bool) $addPreviousToOutput;
return $this;
}
/**
* Add error trace function arguments to output.
* Set to True for all frame args, or integer for the n first frame args.
* @param bool|integer|null $addTraceFunctionArgsToOutput
* @return static|bool|integer
*/
public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
{
if (func_num_args() == 0) {
return $this->addTraceFunctionArgsToOutput;
}
if (! is_integer($addTraceFunctionArgsToOutput)) {
$this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
} else {
$this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
}
return $this;
}
/**
* Set the size limit in bytes of frame arguments var_dump output.
* If the limit is reached, the var_dump output is discarded.
* Prevent memory limit errors.
* @var integer
* @return static
*/
public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
{
$this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
return $this;
}
/**
* Create plain text response and return it as a string
* @return string
*/
public function generateResponse()
{
$exception = $this->getException();
$message = $this->getExceptionOutput($exception);
if ($this->addPreviousToOutput) {
$previous = $exception->getPrevious();
while ($previous) {
$message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous);
$previous = $previous->getPrevious();
}
}
return $message . $this->getTraceOutput() . "\n";
}
/**
* Get the size limit in bytes of frame arguments var_dump output.
* If the limit is reached, the var_dump output is discarded.
* Prevent memory limit errors.
* @return integer
*/
public function getTraceFunctionArgsOutputLimit()
{
return $this->traceFunctionArgsOutputLimit;
}
/**
* Only output to logger.
* @param bool|null $loggerOnly
* @return static|bool
*/
public function loggerOnly($loggerOnly = null)
{
if (func_num_args() == 0) {
return $this->loggerOnly;
}
$this->loggerOnly = (bool) $loggerOnly;
return $this;
}
/**
* Test if handler can output to stdout.
* @return bool
*/
private function canOutput()
{
return !$this->loggerOnly();
}
/**
* Get the frame args var_dump.
* @param \Whoops\Exception\Frame $frame [description]
* @param integer $line [description]
* @return string
*/
private function getFrameArgsOutput(Frame $frame, $line)
{
if ($this->addTraceFunctionArgsToOutput() === false
|| $this->addTraceFunctionArgsToOutput() < $line) {
return '';
}
// Dump the arguments:
ob_start();
$this->dump($frame->getArgs());
if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) {
// The argument var_dump is to big.
// Discarded to limit memory usage.
ob_clean();
return sprintf(
"\n%sArguments dump length greater than %d Bytes. Discarded.",
self::VAR_DUMP_PREFIX,
$this->getTraceFunctionArgsOutputLimit()
);
}
return sprintf(
"\n%s",
preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
);
}
/**
* Dump variable.
*
* @param mixed $var
* @return void
*/
protected function dump($var)
{
if ($this->dumper) {
call_user_func($this->dumper, $var);
} else {
var_dump($var);
}
}
/**
* Get the exception trace as plain text.
* @return string
*/
private function getTraceOutput()
{
if (! $this->addTraceToOutput()) {
return '';
}
$inspector = $this->getInspector();
$frames = $inspector->getFrames($this->getRun()->getFrameFilters());
$response = "\nStack trace:";
$line = 1;
foreach ($frames as $frame) {
/** @var Frame $frame */
$class = $frame->getClass();
$template = "\n%3d. %s->%s() %s:%d%s";
if (! $class) {
// Remove method arrow (->) from output.
$template = "\n%3d. %s%s() %s:%d%s";
}
$response .= sprintf(
$template,
$line,
$class,
$frame->getFunction(),
$frame->getFile(),
$frame->getLine(),
$this->getFrameArgsOutput($frame, $line)
);
$line++;
}
return $response;
}
/**
* Get the exception as plain text.
* @param \Throwable $exception
* @return string
*/
private function getExceptionOutput($exception)
{
return sprintf(
"%s: %s in file %s on line %d",
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
);
}
/**
* @return int
*/
public function handle()
{
$response = $this->generateResponse();
if ($this->getLogger()) {
$this->getLogger()->error($response);
}
if (! $this->canOutput()) {
return Handler::DONE;
}
echo $response;
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'text/plain';
}
}

View file

@ -1,832 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use UnexpectedValueException;
use Whoops\Exception\Formatter;
use Whoops\Util\Misc;
use Whoops\Util\TemplateHelper;
class PrettyPageHandler extends Handler
{
const EDITOR_SUBLIME = "sublime";
const EDITOR_TEXTMATE = "textmate";
const EDITOR_EMACS = "emacs";
const EDITOR_MACVIM = "macvim";
const EDITOR_PHPSTORM = "phpstorm";
const EDITOR_IDEA = "idea";
const EDITOR_VSCODE = "vscode";
const EDITOR_ATOM = "atom";
const EDITOR_ESPRESSO = "espresso";
const EDITOR_XDEBUG = "xdebug";
const EDITOR_NETBEANS = "netbeans";
/**
* Search paths to be scanned for resources.
*
* Stored in the reverse order they're declared.
*
* @var array
*/
private $searchPaths = [];
/**
* Fast lookup cache for known resource locations.
*
* @var array
*/
private $resourceCache = [];
/**
* The name of the custom css file.
*
* @var string|null
*/
private $customCss = null;
/**
* The name of the custom js file.
*
* @var string|null
*/
private $customJs = null;
/**
* @var array[]
*/
private $extraTables = [];
/**
* @var bool
*/
private $handleUnconditionally = false;
/**
* @var string
*/
private $pageTitle = "Whoops! There was an error.";
/**
* @var array[]
*/
private $applicationPaths;
/**
* @var array[]
*/
private $blacklist = [
'_GET' => [],
'_POST' => [],
'_FILES' => [],
'_COOKIE' => [],
'_SESSION' => [],
'_SERVER' => [],
'_ENV' => [],
];
/**
* An identifier for a known IDE/text editor.
*
* Either a string, or a calalble that resolves a string, that can be used
* to open a given file in an editor. If the string contains the special
* substrings %file or %line, they will be replaced with the correct data.
*
* @example
* "txmt://open?url=%file&line=%line"
*
* @var callable|string $editor
*/
protected $editor;
/**
* A list of known editor strings.
*
* @var array
*/
protected $editors = [
"sublime" => "subl://open?url=file://%file&line=%line",
"textmate" => "txmt://open?url=file://%file&line=%line",
"emacs" => "emacs://open?url=file://%file&line=%line",
"macvim" => "mvim://open/?url=file://%file&line=%line",
"phpstorm" => "phpstorm://open?file=%file&line=%line",
"idea" => "idea://open?file=%file&line=%line",
"vscode" => "vscode://file/%file:%line",
"atom" => "atom://core/open/file?filename=%file&line=%line",
"espresso" => "x-espresso://open?filepath=%file&lines=%line",
"netbeans" => "netbeans://open/?f=%file:%line",
];
/**
* @var TemplateHelper
*/
protected $templateHelper;
/**
* Constructor.
*
* @return void
*/
public function __construct()
{
if (ini_get('xdebug.file_link_format') || get_cfg_var('xdebug.file_link_format')) {
// Register editor using xdebug's file_link_format option.
$this->editors['xdebug'] = function ($file, $line) {
return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'));
};
// If xdebug is available, use it as default editor.
$this->setEditor('xdebug');
}
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/../Resources";
// blacklist php provided auth based values
$this->blacklist('_SERVER', 'PHP_AUTH_PW');
$this->templateHelper = new TemplateHelper();
if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$cloner = new VarCloner();
// Only dump object internals if a custom caster exists for performance reasons
// https://github.com/filp/whoops/pull/404
$cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
$class = $stub->class;
$classes = [$class => $class] + class_parents($obj) + class_implements($obj);
foreach ($classes as $class) {
if (isset(AbstractCloner::$defaultCasters[$class])) {
return $a;
}
}
// Remove all internals
return [];
}]);
$this->templateHelper->setCloner($cloner);
}
}
/**
* @return int|null
*
* @throws \Exception
*/
public function handle()
{
if (!$this->handleUnconditionally()) {
// Check conditions for outputting HTML:
// @todo: Make this more robust
if (PHP_SAPI === 'cli') {
// Help users who have been relying on an internal test value
// fix their code to the proper method
if (isset($_ENV['whoops-test'])) {
throw new \Exception(
'Use handleUnconditionally instead of whoops-test'
.' environment variable'
);
}
return Handler::DONE;
}
}
$templateFile = $this->getResource("views/layout.html.php");
$cssFile = $this->getResource("css/whoops.base.css");
$zeptoFile = $this->getResource("js/zepto.min.js");
$prismJs = $this->getResource("js/prism.js");
$prismCss = $this->getResource("css/prism.css");
$clipboard = $this->getResource("js/clipboard.min.js");
$jsFile = $this->getResource("js/whoops.base.js");
if ($this->customCss) {
$customCssFile = $this->getResource($this->customCss);
}
if ($this->customJs) {
$customJsFile = $this->getResource($this->customJs);
}
$inspector = $this->getInspector();
$frames = $this->getExceptionFrames();
$code = $this->getExceptionCode();
// List of variables that will be passed to the layout template.
$vars = [
"page_title" => $this->getPageTitle(),
// @todo: Asset compiler
"stylesheet" => file_get_contents($cssFile),
"zepto" => file_get_contents($zeptoFile),
"prismJs" => file_get_contents($prismJs),
"prismCss" => file_get_contents($prismCss),
"clipboard" => file_get_contents($clipboard),
"javascript" => file_get_contents($jsFile),
// Template paths:
"header" => $this->getResource("views/header.html.php"),
"header_outer" => $this->getResource("views/header_outer.html.php"),
"frame_list" => $this->getResource("views/frame_list.html.php"),
"frames_description" => $this->getResource("views/frames_description.html.php"),
"frames_container" => $this->getResource("views/frames_container.html.php"),
"panel_details" => $this->getResource("views/panel_details.html.php"),
"panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
"panel_left" => $this->getResource("views/panel_left.html.php"),
"panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"),
"frame_code" => $this->getResource("views/frame_code.html.php"),
"env_details" => $this->getResource("views/env_details.html.php"),
"title" => $this->getPageTitle(),
"name" => explode("\\", $inspector->getExceptionName()),
"message" => $inspector->getExceptionMessage(),
"previousMessages" => $inspector->getPreviousExceptionMessages(),
"docref_url" => $inspector->getExceptionDocrefUrl(),
"code" => $code,
"previousCodes" => $inspector->getPreviousExceptionCodes(),
"plain_exception" => Formatter::formatExceptionPlain($inspector),
"frames" => $frames,
"has_frames" => !!count($frames),
"handler" => $this,
"handlers" => $this->getRun()->getHandlers(),
"active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
"has_frames_tabs" => $this->getApplicationPaths(),
"tables" => [
"GET Data" => $this->masked($_GET, '_GET'),
"POST Data" => $this->masked($_POST, '_POST'),
"Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
"Cookies" => $this->masked($_COOKIE, '_COOKIE'),
"Session" => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [],
"Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
"Environment Variables" => $this->masked($_ENV, '_ENV'),
],
];
if (isset($customCssFile)) {
$vars["stylesheet"] .= file_get_contents($customCssFile);
}
if (isset($customJsFile)) {
$vars["javascript"] .= file_get_contents($customJsFile);
}
// Add extra entries list of data tables:
// @todo: Consolidate addDataTable and addDataTableCallback
$extraTables = array_map(function ($table) use ($inspector) {
return $table instanceof \Closure ? $table($inspector) : $table;
}, $this->getDataTables());
$vars["tables"] = array_merge($extraTables, $vars["tables"]);
$plainTextHandler = new PlainTextHandler();
$plainTextHandler->setRun($this->getRun());
$plainTextHandler->setException($this->getException());
$plainTextHandler->setInspector($this->getInspector());
$vars["preface"] = "<!--\n\n\n" . $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
$this->templateHelper->setVariables($vars);
$this->templateHelper->render($templateFile);
return Handler::QUIT;
}
/**
* Get the stack trace frames of the exception currently being handled.
*
* @return \Whoops\Exception\FrameCollection
*/
protected function getExceptionFrames()
{
$frames = $this->getInspector()->getFrames($this->getRun()->getFrameFilters());
if ($this->getApplicationPaths()) {
foreach ($frames as $frame) {
foreach ($this->getApplicationPaths() as $path) {
if (strpos($frame->getFile(), $path) === 0) {
$frame->setApplication(true);
break;
}
}
}
}
return $frames;
}
/**
* Get the code of the exception currently being handled.
*
* @return string
*/
protected function getExceptionCode()
{
$exception = $this->getException();
$code = $exception->getCode();
if ($exception instanceof \ErrorException) {
// ErrorExceptions wrap the php-error types within the 'severity' property
$code = Misc::translateErrorCode($exception->getSeverity());
}
return (string) $code;
}
/**
* @return string
*/
public function contentType()
{
return 'text/html';
}
/**
* Adds an entry to the list of tables displayed in the template.
*
* The expected data is a simple associative array. Any nested arrays
* will be flattened with `print_r`.
*
* @param string $label
*
* @return static
*/
public function addDataTable($label, array $data)
{
$this->extraTables[$label] = $data;
return $this;
}
/**
* Lazily adds an entry to the list of tables displayed in the table.
*
* The supplied callback argument will be called when the error is
* rendered, it should produce a simple associative array. Any nested
* arrays will be flattened with `print_r`.
*
* @param string $label
* @param callable $callback Callable returning an associative array
*
* @throws InvalidArgumentException If $callback is not callable
*
* @return static
*/
public function addDataTableCallback($label, /* callable */ $callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException('Expecting callback argument to be callable');
}
$this->extraTables[$label] = function (\Whoops\Inspector\InspectorInterface $inspector = null) use ($callback) {
try {
$result = call_user_func($callback, $inspector);
// Only return the result if it can be iterated over by foreach().
return is_array($result) || $result instanceof \Traversable ? $result : [];
} catch (\Exception $e) {
// Don't allow failure to break the rendering of the original exception.
return [];
}
};
return $this;
}
/**
* Returns all the extra data tables registered with this handler.
*
* Optionally accepts a 'label' parameter, to only return the data table
* under that label.
*
* @param string|null $label
*
* @return array[]|callable
*/
public function getDataTables($label = null)
{
if ($label !== null) {
return isset($this->extraTables[$label]) ?
$this->extraTables[$label] : [];
}
return $this->extraTables;
}
/**
* Set whether to handle unconditionally.
*
* Allows to disable all attempts to dynamically decide whether to handle
* or return prematurely. Set this to ensure that the handler will perform,
* no matter what.
*
* @param bool|null $value
*
* @return bool|static
*/
public function handleUnconditionally($value = null)
{
if (func_num_args() == 0) {
return $this->handleUnconditionally;
}
$this->handleUnconditionally = (bool) $value;
return $this;
}
/**
* Adds an editor resolver.
*
* Either a string, or a closure that resolves a string, that can be used
* to open a given file in an editor. If the string contains the special
* substrings %file or %line, they will be replaced with the correct data.
*
* @example
* $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
* @example
* $run->addEditor('remove-it', function($file, $line) {
* unlink($file);
* return "http://stackoverflow.com";
* });
*
* @param string $identifier
* @param string|callable $resolver
*
* @return static
*/
public function addEditor($identifier, $resolver)
{
$this->editors[$identifier] = $resolver;
return $this;
}
/**
* Set the editor to use to open referenced files.
*
* Pass either the name of a configured editor, or a closure that directly
* resolves an editor string.
*
* @example
* $run->setEditor(function($file, $line) { return "file:///{$file}"; });
* @example
* $run->setEditor('sublime');
*
* @param string|callable $editor
*
* @throws InvalidArgumentException If invalid argument identifier provided
*
* @return static
*/
public function setEditor($editor)
{
if (!is_callable($editor) && !isset($this->editors[$editor])) {
throw new InvalidArgumentException(
"Unknown editor identifier: $editor. Known editors:" .
implode(",", array_keys($this->editors))
);
}
$this->editor = $editor;
return $this;
}
/**
* Get the editor href for a given file and line, if available.
*
* @param string $filePath
* @param int $line
*
* @throws InvalidArgumentException If editor resolver does not return a string
*
* @return string|bool
*/
public function getEditorHref($filePath, $line)
{
$editor = $this->getEditor($filePath, $line);
if (empty($editor)) {
return false;
}
// Check that the editor is a string, and replace the
// %line and %file placeholders:
if (!isset($editor['url']) || !is_string($editor['url'])) {
throw new UnexpectedValueException(
__METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
);
}
$editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
$editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
return $editor['url'];
}
/**
* Determine if the editor link should act as an Ajax request.
*
* @param string $filePath
* @param int $line
*
* @throws UnexpectedValueException If editor resolver does not return a boolean
*
* @return bool
*/
public function getEditorAjax($filePath, $line)
{
$editor = $this->getEditor($filePath, $line);
// Check that the ajax is a bool
if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
throw new UnexpectedValueException(
__METHOD__ . " should always resolve to a bool; got something else instead."
);
}
return $editor['ajax'];
}
/**
* Determines both the editor and if ajax should be used.
*
* @param string $filePath
* @param int $line
*
* @return array
*/
protected function getEditor($filePath, $line)
{
if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
return [];
}
if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
return [
'ajax' => false,
'url' => $this->editors[$this->editor],
];
}
if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
if (is_callable($this->editor)) {
$callback = call_user_func($this->editor, $filePath, $line);
} else {
$callback = call_user_func($this->editors[$this->editor], $filePath, $line);
}
if (empty($callback)) {
return [];
}
if (is_string($callback)) {
return [
'ajax' => false,
'url' => $callback,
];
}
return [
'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
'url' => isset($callback['url']) ? $callback['url'] : $callback,
];
}
return [];
}
/**
* Set the page title.
*
* @param string $title
*
* @return static
*/
public function setPageTitle($title)
{
$this->pageTitle = (string) $title;
return $this;
}
/**
* Get the page title.
*
* @return string
*/
public function getPageTitle()
{
return $this->pageTitle;
}
/**
* Adds a path to the list of paths to be searched for resources.
*
* @param string $path
*
* @throws InvalidArgumentException If $path is not a valid directory
*
* @return static
*/
public function addResourcePath($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException(
"'$path' is not a valid directory"
);
}
array_unshift($this->searchPaths, $path);
return $this;
}
/**
* Adds a custom css file to be loaded.
*
* @param string|null $name
*
* @return static
*/
public function addCustomCss($name)
{
$this->customCss = $name;
return $this;
}
/**
* Adds a custom js file to be loaded.
*
* @param string|null $name
*
* @return static
*/
public function addCustomJs($name)
{
$this->customJs = $name;
return $this;
}
/**
* @return array
*/
public function getResourcePaths()
{
return $this->searchPaths;
}
/**
* Finds a resource, by its relative path, in all available search paths.
*
* The search is performed starting at the last search path, and all the
* way back to the first, enabling a cascading-type system of overrides for
* all resources.
*
* @param string $resource
*
* @throws RuntimeException If resource cannot be found in any of the available paths
*
* @return string
*/
protected function getResource($resource)
{
// If the resource was found before, we can speed things up
// by caching its absolute, resolved path:
if (isset($this->resourceCache[$resource])) {
return $this->resourceCache[$resource];
}
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
$fullPath = $path . "/$resource";
if (is_file($fullPath)) {
// Cache the result:
$this->resourceCache[$resource] = $fullPath;
return $fullPath;
}
}
// If we got this far, nothing was found.
throw new RuntimeException(
"Could not find resource '$resource' in any resource paths."
. "(searched: " . join(", ", $this->searchPaths). ")"
);
}
/**
* @deprecated
*
* @return string
*/
public function getResourcesPath()
{
$allPaths = $this->getResourcePaths();
// Compat: return only the first path added
return end($allPaths) ?: null;
}
/**
* @deprecated
*
* @param string $resourcesPath
*
* @return static
*/
public function setResourcesPath($resourcesPath)
{
$this->addResourcePath($resourcesPath);
return $this;
}
/**
* Return the application paths.
*
* @return array
*/
public function getApplicationPaths()
{
return $this->applicationPaths;
}
/**
* Set the application paths.
*
* @return void
*/
public function setApplicationPaths(array $applicationPaths)
{
$this->applicationPaths = $applicationPaths;
}
/**
* Set the application root path.
*
* @param string $applicationRootPath
*
* @return void
*/
public function setApplicationRootPath($applicationRootPath)
{
$this->templateHelper->setApplicationRootPath($applicationRootPath);
}
/**
* blacklist a sensitive value within one of the superglobal arrays.
* Alias for the hideSuperglobalKey method.
*
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
* @param string $key The key within the superglobal
* @see hideSuperglobalKey
*
* @return static
*/
public function blacklist($superGlobalName, $key)
{
$this->blacklist[$superGlobalName][] = $key;
return $this;
}
/**
* Hide a sensitive value within one of the superglobal arrays.
*
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
* @param string $key The key within the superglobal
* @return static
*/
public function hideSuperglobalKey($superGlobalName, $key)
{
return $this->blacklist($superGlobalName, $key);
}
/**
* Checks all values within the given superGlobal array.
*
* Blacklisted values will be replaced by a equal length string containing
* only '*' characters for string values.
* Non-string values will be replaced with a fixed asterisk count.
* We intentionally dont rely on $GLOBALS as it depends on the 'auto_globals_jit' php.ini setting.
*
* @param array|\ArrayAccess $superGlobal One of the superglobal arrays
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
*
* @return array $values without sensitive data
*/
private function masked($superGlobal, $superGlobalName)
{
$blacklisted = $this->blacklist[$superGlobalName];
$values = $superGlobal;
foreach ($blacklisted as $key) {
if (isset($superGlobal[$key])) {
$values[$key] = str_repeat('*', is_string($superGlobal[$key]) ? strlen($superGlobal[$key]) : 3);
}
}
return $values;
}
}

View file

@ -1,108 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use SimpleXMLElement;
use Whoops\Exception\Formatter;
/**
* Catches an exception and converts it to an XML
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class XmlResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @param bool|null $returnFrames
* @return bool|static
*/
public function addTraceToOutput($returnFrames = null)
{
if (func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
return $this;
}
/**
* @return int
*/
public function handle()
{
$response = [
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput(),
$this->getRun()->getFrameFilters()
),
];
echo self::toXml($response);
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'application/xml';
}
/**
* @param SimpleXMLElement $node Node to append data to, will be modified in place
* @param array|\Traversable $data
* @return SimpleXMLElement The modified node, for chaining
*/
private static function addDataToNode(\SimpleXMLElement $node, $data)
{
assert(is_array($data) || $data instanceof Traversable);
foreach ($data as $key => $value) {
if (is_numeric($key)) {
// Convert the key to a valid string
$key = "unknownNode_". (string) $key;
}
// Delete any char not allowed in XML element names
$key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key);
if (is_array($value)) {
$child = $node->addChild($key);
self::addDataToNode($child, $value);
} else {
$value = str_replace('&', '&amp;', print_r($value, true));
$node->addChild($key, $value);
}
}
return $node;
}
/**
* The main function for converting to an XML document.
*
* @param array|\Traversable $data
* @return string XML
*/
private static function toXml($data)
{
assert(is_array($data) || $data instanceof Traversable);
$node = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><root />");
return self::addDataToNode($node, $data)->asXML();
}
}

View file

@ -1,21 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Inspector;
use Whoops\Exception\Inspector;
class InspectorFactory implements InspectorFactoryInterface
{
/**
* @param \Throwable $exception
* @return InspectorInterface
*/
public function create($exception)
{
return new Inspector($exception, $this);
}
}

View file

@ -1,16 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Inspector;
interface InspectorFactoryInterface
{
/**
* @param \Throwable $exception
* @return InspectorInterface
*/
public function create($exception);
}

View file

@ -1,71 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Inspector;
interface InspectorInterface
{
/**
* @return \Throwable
*/
public function getException();
/**
* @return string
*/
public function getExceptionName();
/**
* @return string
*/
public function getExceptionMessage();
/**
* @return string[]
*/
public function getPreviousExceptionMessages();
/**
* @return int[]
*/
public function getPreviousExceptionCodes();
/**
* Returns a url to the php-manual related to the underlying error - when available.
*
* @return string|null
*/
public function getExceptionDocrefUrl();
/**
* Does the wrapped Exception has a previous Exception?
* @return bool
*/
public function hasPreviousException();
/**
* Returns an Inspector for a previous Exception, if any.
* @todo Clean this up a bit, cache stuff a bit better.
* @return InspectorInterface
*/
public function getPreviousExceptionInspector();
/**
* Returns an array of all previous exceptions for this inspector's exception
* @return \Throwable[]
*/
public function getPreviousExceptions();
/**
* Returns an iterator for the inspected exception's
* frames.
*
* @param array<callable> $frameFilters
*
* @return \Whoops\Exception\FrameCollection
*/
public function getFrames(array $frameFilters = []);
}

View file

@ -1,5 +0,0 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php&plugins=line-highlight+line-numbers */
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)}
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}

View file

@ -1,560 +0,0 @@
body {
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
color: #131313;
background: #eeeeee;
padding:0;
margin: 0;
max-height: 100%;
text-rendering: optimizeLegibility;
}
a {
text-decoration: none;
}
.Whoops.container {
position: relative;
z-index: 9999999999;
}
.panel {
overflow-y: scroll;
height: 100%;
position: fixed;
margin: 0;
left: 0;
top: 0;
}
.branding {
position: absolute;
top: 10px;
right: 20px;
color: #777777;
font-size: 10px;
z-index: 100;
}
.branding a {
color: #e95353;
}
header {
color: white;
box-sizing: border-box;
background-color: #2a2a2a;
padding: 35px 40px;
max-height: 180px;
overflow: hidden;
transition: 0.5s;
}
header.header-expand {
max-height: 1000px;
}
.exc-title {
margin: 0;
color: #bebebe;
font-size: 14px;
}
.exc-title-primary, .exc-title-secondary {
color: #e95353;
}
.exc-message {
font-size: 20px;
word-wrap: break-word;
margin: 4px 0 0 0;
color: white;
}
.exc-message span {
display: block;
}
.exc-message-empty-notice {
color: #a29d9d;
font-weight: 300;
}
.prev-exc-title {
margin: 10px 0;
}
.prev-exc-title + ul {
margin: 0;
padding: 0 0 0 20px;
line-height: 12px;
}
.prev-exc-title + ul li {
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
}
.prev-exc-title + ul li .prev-exc-code {
display: inline-block;
color: #bebebe;
}
.details-container {
left: 30%;
width: 70%;
background: #fafafa;
}
.details {
padding: 5px;
}
.details-heading {
color: #4288CE;
font-weight: 300;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid rgba(0, 0, 0, .1);
}
.details pre.sf-dump {
white-space: pre;
word-wrap: inherit;
}
.details pre.sf-dump,
.details pre.sf-dump .sf-dump-num,
.details pre.sf-dump .sf-dump-const,
.details pre.sf-dump .sf-dump-str,
.details pre.sf-dump .sf-dump-note,
.details pre.sf-dump .sf-dump-ref,
.details pre.sf-dump .sf-dump-public,
.details pre.sf-dump .sf-dump-protected,
.details pre.sf-dump .sf-dump-private,
.details pre.sf-dump .sf-dump-meta,
.details pre.sf-dump .sf-dump-key,
.details pre.sf-dump .sf-dump-index {
color: #463C54;
}
.left-panel {
width: 30%;
background: #ded8d8;
}
.frames-description {
background: rgba(0, 0, 0, .05);
padding: 8px 15px;
color: #a29d9d;
font-size: 11px;
}
.frames-description.frames-description-application {
text-align: center;
font-size: 12px;
}
.frames-container.frames-container-application .frame:not(.frame-application) {
display: none;
}
.frames-tab {
color: #a29d9d;
display: inline-block;
padding: 4px 8px;
margin: 0 2px;
border-radius: 3px;
}
.frames-tab.frames-tab-active {
background-color: #2a2a2a;
color: #bebebe;
}
.frame {
padding: 14px;
cursor: pointer;
transition: all 0.1s ease;
background: #eeeeee;
}
.frame:not(:last-child) {
border-bottom: 1px solid rgba(0, 0, 0, .05);
}
.frame.active {
box-shadow: inset -5px 0 0 0 #4288CE;
color: #4288CE;
}
.frame:not(.active):hover {
background: #BEE9EA;
}
.frame-method-info {
margin-bottom: 10px;
}
.frame-class, .frame-function, .frame-index {
font-size: 14px;
}
.frame-index {
float: left;
}
.frame-method-info {
margin-left: 24px;
}
.frame-index {
font-size: 11px;
color: #a29d9d;
background-color: rgba(0, 0, 0, .05);
height: 18px;
width: 18px;
line-height: 18px;
border-radius: 5px;
padding: 0 1px 0 1px;
text-align: center;
display: inline-block;
}
.frame-application .frame-index {
background-color: #2a2a2a;
color: #bebebe;
}
.frame-file {
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
color: #a29d9d;
}
.frame-file .editor-link {
color: #a29d9d;
}
.frame-line {
font-weight: bold;
}
.frame-line:before {
content: ":";
}
.frame-code {
padding: 5px;
background: #303030;
display: none;
}
.frame-code.active {
display: block;
}
.frame-code .frame-file {
color: #a29d9d;
padding: 12px 6px;
border-bottom: none;
}
.code-block {
padding: 10px;
margin: 0;
border-radius: 6px;
box-shadow: 0 3px 0 rgba(0, 0, 0, .05),
0 10px 30px rgba(0, 0, 0, .05),
inset 0 0 1px 0 rgba(255, 255, 255, .07);
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
}
.linenums {
margin: 0;
margin-left: 10px;
}
.frame-comments {
border-top: none;
margin-top: 15px;
font-size: 12px;
}
.frame-comments.empty {
}
.frame-comments.empty:before {
content: "No comments for this stack frame.";
font-weight: 300;
color: #a29d9d;
}
.frame-comment {
padding: 10px;
color: #e3e3e3;
border-radius: 6px;
background-color: rgba(255, 255, 255, .05);
}
.frame-comment a {
font-weight: bold;
text-decoration: underline;
color: #c6c6c6;
}
.frame-comment:not(:last-child) {
border-bottom: 1px dotted rgba(0, 0, 0, .3);
}
.frame-comment-context {
font-size: 10px;
color: white;
}
.delimiter {
display: inline-block;
}
.data-table-container label {
font-size: 16px;
color: #303030;
font-weight: bold;
margin: 10px 0;
display: block;
margin-bottom: 5px;
padding-bottom: 5px;
}
.data-table {
width: 100%;
margin-bottom: 10px;
}
.data-table tbody {
font: 13px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
}
.data-table thead {
display: none;
}
.data-table tr {
padding: 5px 0;
}
.data-table td:first-child {
width: 20%;
min-width: 130px;
overflow: hidden;
font-weight: bold;
color: #463C54;
padding-right: 5px;
}
.data-table td:last-child {
width: 80%;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
.data-table span.empty {
color: rgba(0, 0, 0, .3);
font-weight: 300;
}
.data-table label.empty {
display: inline;
}
.handler {
padding: 4px 0;
font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
}
#plain-exception {
display: none;
}
.rightButton {
cursor: pointer;
border: 0;
opacity: .8;
background: none;
color: rgba(255, 255, 255, 0.1);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1);
border-radius: 3px;
outline: none !important;
}
.rightButton:hover {
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.3);
}
/* inspired by githubs kbd styles */
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #fcfcfc;
border-color: #ccc #ccc #bbb;
border-image: none;
border-style: solid;
border-width: 1px;
color: #555;
display: inline-block;
font-size: 11px;
line-height: 10px;
padding: 3px 5px;
vertical-align: middle;
}
/* == Media queries */
/* Expand the spacing in the details section */
@media (min-width: 1000px) {
.details, .frame-code {
padding: 20px 40px;
}
.details-container {
left: 32%;
width: 68%;
}
.frames-container {
margin: 5px;
}
.left-panel {
width: 32%;
}
}
/* Stack panels */
@media (max-width: 600px) {
.panel {
position: static;
width: 100%;
}
}
/* Stack details tables */
@media (max-width: 400px) {
.data-table,
.data-table tbody,
.data-table tbody tr,
.data-table tbody td {
display: block;
width: 100%;
}
.data-table tbody tr:first-child {
padding-top: 0;
}
.data-table tbody td:first-child,
.data-table tbody td:last-child {
padding-left: 0;
padding-right: 0;
}
.data-table tbody td:last-child {
padding-top: 3px;
}
}
.tooltipped {
position: relative
}
.tooltipped:after {
position: absolute;
z-index: 1000000;
display: none;
padding: 5px 8px;
color: #fff;
text-align: center;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-wrap: break-word;
white-space: pre;
pointer-events: none;
content: attr(aria-label);
background: rgba(0, 0, 0, 0.8);
border-radius: 3px;
-webkit-font-smoothing: subpixel-antialiased
}
.tooltipped:before {
position: absolute;
z-index: 1000001;
display: none;
width: 0;
height: 0;
color: rgba(0, 0, 0, 0.8);
pointer-events: none;
content: "";
border: 5px solid transparent
}
.tooltipped:hover:before,
.tooltipped:hover:after,
.tooltipped:active:before,
.tooltipped:active:after,
.tooltipped:focus:before,
.tooltipped:focus:after {
display: inline-block;
text-decoration: none
}
.tooltipped-s:after {
top: 100%;
right: 50%;
margin-top: 5px
}
.tooltipped-s:before {
top: auto;
right: 50%;
bottom: -5px;
margin-right: -5px;
border-bottom-color: rgba(0, 0, 0, 0.8)
}
pre.sf-dump {
padding: 0px !important;
margin: 0px !important;
}
.search-for-help {
width: 85%;
padding: 0;
margin: 10px 0;
list-style-type: none;
display: inline-block;
}
.search-for-help li {
display: inline-block;
margin-right: 5px;
}
.search-for-help li:last-child {
margin-right: 0;
}
.search-for-help li a {
}
.search-for-help li a i {
width: 16px;
height: 16px;
overflow: hidden;
display: block;
}
.search-for-help li a svg {
fill: #fff;
}
.search-for-help li a svg path {
background-size: contain;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,188 +0,0 @@
Zepto(function($) {
var $leftPanel = $('.left-panel');
var $frameContainer = $('.frames-container');
var $appFramesTab = $('#application-frames-tab');
var $allFramesTab = $('#all-frames-tab');
var $container = $('.details-container');
var $activeLine = $frameContainer.find('.frame.active');
var $activeFrame = $container.find('.frame-code.active');
var $ajaxEditors = $('.editor-link[data-ajax]');
var $header = $('header');
$header.on('mouseenter', function () {
if ($header.find('.exception').height() >= 145) {
$header.addClass('header-expand');
}
});
$header.on('mouseleave', function () {
$header.removeClass('header-expand');
});
/*
* add prettyprint classes to our current active codeblock
* run prettyPrint() to highlight the active code
* scroll to the line when prettyprint is done
* highlight the current line
*/
var renderCurrentCodeblock = function(id) {
Prism.highlightAllUnder(document.querySelector('.frame-code-container .frame-code.active'));
highlightCurrentLine();
}
/*
* Highlight the active and neighboring lines for the current frame
* Adjust the offset to make sure that line is veritcally centered
*/
var highlightCurrentLine = function() {
// We show more code than needed, purely for proper syntax highlighting
// Lets hide a big chunk of that code and then scroll the remaining block
$activeFrame.find('.code-block').first().css({
maxHeight: 345,
overflow: 'hidden',
});
var line = $activeFrame.find('.code-block .line-highlight').first()[0];
line.scrollIntoView();
line.parentElement.scrollTop -= 180;
$container.scrollTop(0);
}
/*
* click handler for loading codeblocks
*/
$frameContainer.on('click', '.frame', function() {
var $this = $(this);
var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1];
var $codeFrame = $('#frame-code-' + id);
if ($codeFrame) {
$activeLine.removeClass('active');
$activeFrame.removeClass('active');
$this.addClass('active');
$codeFrame.addClass('active');
$activeLine = $this;
$activeFrame = $codeFrame;
renderCurrentCodeblock(id);
}
});
var clipboard = new Clipboard('.clipboard');
var showTooltip = function(elem, msg) {
elem.classList.add('tooltipped', 'tooltipped-s');
elem.setAttribute('aria-label', msg);
};
clipboard.on('success', function(e) {
e.clearSelection();
showTooltip(e.trigger, 'Copied!');
});
clipboard.on('error', function(e) {
showTooltip(e.trigger, fallbackMessage(e.action));
});
var btn = document.querySelector('.clipboard');
btn.addEventListener('mouseleave', function(e) {
e.currentTarget.classList.remove('tooltipped', 'tooltipped-s');
e.currentTarget.removeAttribute('aria-label');
});
function fallbackMessage(action) {
var actionMsg = '';
var actionKey = (action === 'cut' ? 'X' : 'C');
if (/Mac/i.test(navigator.userAgent)) {
actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
} else {
actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
}
return actionMsg;
}
function scrollIntoView($node, $parent) {
var nodeOffset = $node.offset();
var nodeTop = nodeOffset.top;
var nodeBottom = nodeTop + nodeOffset.height;
var parentScrollTop = $parent.scrollTop();
var parentHeight = $parent.height();
if (nodeTop < 0) {
$parent.scrollTop(parentScrollTop + nodeTop);
} else if (nodeBottom > parentHeight) {
$parent.scrollTop(parentScrollTop + nodeBottom - parentHeight);
}
}
$(document).on('keydown', function(e) {
var applicationFrames = $frameContainer.hasClass('frames-container-application'),
frameClass = applicationFrames ? '.frame.frame-application' : '.frame';
if(e.ctrlKey || e.which === 74 || e.which === 75) {
// CTRL+Arrow-UP/k and Arrow-Down/j support:
// 1) select the next/prev element
// 2) make sure the newly selected element is within the view-scope
// 3) focus the (right) container, so arrow-up/down (without ctrl) scroll the details
if (e.which === 38 /* arrow up */ || e.which === 75 /* k */) {
$activeLine.prev(frameClass).click();
scrollIntoView($activeLine, $leftPanel);
$container.focus();
e.preventDefault();
} else if (e.which === 40 /* arrow down */ || e.which === 74 /* j */) {
$activeLine.next(frameClass).click();
scrollIntoView($activeLine, $leftPanel);
$container.focus();
e.preventDefault();
}
} else if (e.which == 78 /* n */) {
if ($appFramesTab.length) {
setActiveFramesTab($('.frames-tab:not(.frames-tab-active)'));
}
}
});
// Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API)
$ajaxEditors.on('click', function(e){
e.preventDefault();
$.get(this.href);
});
// Symfony VarDumper: Close the by default expanded objects
$('.sf-dump-expanded')
.removeClass('sf-dump-expanded')
.addClass('sf-dump-compact');
$('.sf-dump-toggle span').html('&#9654;');
// Make the given frames-tab active
function setActiveFramesTab($tab) {
$tab.addClass('frames-tab-active');
if ($tab.attr('id') == 'application-frames-tab') {
$frameContainer.addClass('frames-container-application');
$allFramesTab.removeClass('frames-tab-active');
} else {
$frameContainer.removeClass('frames-container-application');
$appFramesTab.removeClass('frames-tab-active');
}
}
$('a.frames-tab').on('click', function(e) {
e.preventDefault();
setActiveFramesTab($(this));
});
// Render late enough for highlightCurrentLine to be ready
renderCurrentCodeblock();
});

File diff suppressed because one or more lines are too long

View file

@ -1,42 +0,0 @@
<?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?>
<div class="details">
<h2 class="details-heading">Environment &amp; details:</h2>
<div class="data-table-container" id="data-tables">
<?php foreach ($tables as $label => $data): ?>
<div class="data-table" id="sg-<?php echo $tpl->escape($tpl->slug($label)) ?>">
<?php if (!empty($data)): ?>
<label><?php echo $tpl->escape($label) ?></label>
<table class="data-table">
<thead>
<tr>
<td class="data-table-k">Key</td>
<td class="data-table-v">Value</td>
</tr>
</thead>
<?php foreach ($data as $k => $value): ?>
<tr>
<td><?php echo $tpl->escape($k) ?></td>
<td><?php echo $tpl->dump($value) ?></td>
</tr>
<?php endforeach ?>
</table>
<?php else: ?>
<label class="empty"><?php echo $tpl->escape($label) ?></label>
<span class="empty">empty</span>
<?php endif ?>
</div>
<?php endforeach ?>
</div>
<?php /* List registered handlers, in order of first to last registered */ ?>
<div class="data-table-container" id="handlers">
<label>Registered Handlers</label>
<?php foreach ($handlers as $i => $h): ?>
<div class="handler <?php echo ($h === $handler) ? 'active' : ''?>">
<?php echo $i ?>. <?php echo $tpl->escape(get_class($h)) ?>
</div>
<?php endforeach ?>
</div>
</div>

View file

@ -1,67 +0,0 @@
<?php /* Display a code block for all frames in the stack.
* @todo: This should PROBABLY be done on-demand, lest
* we get 200 frames to process. */ ?>
<div class="frame-code-container <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php foreach ($frames as $i => $frame): ?>
<?php $line = $frame->getLine(); ?>
<div class="frame-code <?php echo ($i == 0 ) ? 'active' : '' ?>" id="frame-code-<?php echo $i ?>">
<div class="frame-file">
<?php $filePath = $frame->getFile(); ?>
<?php if ($filePath && $editorHref = $handler->getEditorHref($filePath, (int) $line)): ?>
<a href="<?php echo $editorHref ?>" class="editor-link"<?php echo ($handler->getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>>
Open:
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
</a>
<?php else: ?>
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
<?php endif ?>
</div>
<?php
// Do nothing if there's no line to work off
if ($line !== null):
// the $line is 1-indexed, we nab -1 where needed to account for this
$range = $frame->getFileLines($line - 20, 40);
// getFileLines can return null if there is no source code
if ($range):
$range = array_map(function ($line) { return empty($line) ? ' ' : $line;}, $range);
$start = key($range) + 1;
$code = join("\n", $range);
?>
<pre class="code-block line-numbers"
data-line="<?php echo $line ?>"
data-line-offset="<?php echo $start ?>"
data-start="<?php echo $start ?>"
><code class="language-php"><?php echo $tpl->escape($code) ?></code></pre>
<?php endif ?>
<?php endif ?>
<?php $frameArgs = $tpl->dumpArgs($frame); ?>
<?php if ($frameArgs): ?>
<div class="frame-file">
Arguments
</div>
<div id="frame-code-args-<?=$i?>" class="code-block frame-args">
<?php echo $frameArgs; ?>
</div>
<?php endif ?>
<?php
// Append comments for this frame
$comments = $frame->getComments();
?>
<div class="frame-comments <?php echo empty($comments) ? 'empty' : '' ?>">
<?php foreach ($comments as $commentNo => $comment): ?>
<?php extract($comment) ?>
<div class="frame-comment" id="comment-<?php echo $i . '-' . $commentNo ?>">
<span class="frame-comment-context"><?php echo $tpl->escape($context) ?></span>
<?php echo $tpl->escapeButPreserveUris($comment) ?>
</div>
<?php endforeach ?>
</div>
</div>
<?php endforeach ?>
</div>

View file

@ -1,17 +0,0 @@
<?php /* List file names & line numbers for all stack frames;
clicking these links/buttons will display the code view
for that particular frame */ ?>
<?php foreach ($frames as $i => $frame): ?>
<div class="frame <?php echo ($i == 0 ? 'active' : '') ?> <?php echo ($frame->isApplication() ? 'frame-application' : '') ?>" id="frame-line-<?php echo $i ?>">
<span class="frame-index"><?php echo (count($frames) - $i - 1) ?></span>
<div class="frame-method-info">
<span class="frame-class"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getClass() ?: '')) ?></span>
<span class="frame-function"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getFunction() ?: '')) ?></span>
</div>
<div class="frame-file">
<?php echo $frame->getFile() ? $tpl->breakOnDelimiter('/', $tpl->shorten($tpl->escape($frame->getFile()))) : '<#unknown>' ?><!--
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
</div>
</div>
<?php endforeach;

View file

@ -1,3 +0,0 @@
<div class="frames-container <?php echo $active_frames_tab == 'application' ? 'frames-container-application' : '' ?>">
<?php $tpl->render($frame_list) ?>
</div>

View file

@ -1,14 +0,0 @@
<div class="frames-description <?php echo $has_frames_tabs ? 'frames-description-application' : '' ?>">
<?php if ($has_frames_tabs): ?>
<a href="#" id="application-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'application' ? 'frames-tab-active' : '' ?>">
Application frames (<?php echo $frames->countIsApplication() ?>)
</a>
<a href="#" id="all-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'all' ? 'frames-tab-active' : '' ?>">
All frames (<?php echo count($frames) ?>)
</a>
<?php else: ?>
<span>
Stack frames (<?php echo count($frames) ?>)
</span>
<?php endif; ?>
</div>

View file

@ -1,96 +0,0 @@
<div class="exception">
<div class="exc-title">
<?php foreach ($name as $i => $nameSection): ?>
<?php if ($i == count($name) - 1): ?>
<span class="exc-title-primary"><?php echo $tpl->escape($nameSection) ?></span>
<?php else: ?>
<?php echo $tpl->escape($nameSection) . ' \\' ?>
<?php endif ?>
<?php endforeach ?>
<?php if ($code): ?>
<span title="Exception Code">(<?php echo $tpl->escape($code) ?>)</span>
<?php endif ?>
</div>
<div class="exc-message">
<?php if (!empty($message)): ?>
<span><?php echo $tpl->escape($message) ?></span>
<?php if (count($previousMessages)): ?>
<div class="exc-title prev-exc-title">
<span class="exc-title-secondary">Previous exceptions</span>
</div>
<ul>
<?php foreach ($previousMessages as $i => $previousMessage): ?>
<li>
<?php echo $tpl->escape($previousMessage) ?>
<span class="prev-exc-code">(<?php echo $previousCodes[$i] ?>)</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif ?>
<?php else: ?>
<span class="exc-message-empty-notice">No message</span>
<?php endif ?>
<ul class="search-for-help">
<?php if (!empty($docref_url)): ?>
<li>
<a rel="noopener noreferrer" target="_blank" href="<?php echo $docref_url; ?>" title="Search for help in the PHP manual.">
<!-- PHP icon by Icons Solid -->
<!-- https://www.iconfinder.com/icons/322421/book_icon -->
<!-- Free for commercial use -->
<svg height="16px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(240 0)"><path d="M-211,4v26h-24c-1.104,0-2-0.895-2-2s0.896-2,2-2h22V0h-22c-2.209,0-4,1.791-4,4v24c0,2.209,1.791,4,4,4h26V4H-211z M-235,8V2h20v22h-20V8z M-219,6h-12V4h12V6z M-223,10h-8V8h8V10z M-227,14h-4v-2h4V14z"/></g></svg>
</a>
</li>
<?php endif ?>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google.">
<!-- Google icon by Alfredo H, from https://www.iconfinder.com/alfredoh -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="google" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M457.732 216.625c2.628 14.04 4.063 28.743 4.063 44.098C461.795 380.688 381.48 466 260.205 466c-116.024 0-210-93.977-210-210s93.976-210 210-210c56.703 0 104.076 20.867 140.44 54.73l-59.205 59.197v-.135c-22.046-21.002-50-31.762-81.236-31.762-69.297 0-125.604 58.537-125.604 127.84 0 69.29 56.306 127.97 125.604 127.97 62.87 0 105.653-35.966 114.46-85.313h-114.46v-81.902h197.528z"/>
</svg>
</a>
</li>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://duckduckgo.com/?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on DuckDuckGo.">
<!-- DuckDuckGo icon by IconBaandar Team, from https://www.iconfinder.com/iconbaandar -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="duckduckgo" height="16" viewBox="150 150 1675 1675" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M1792 1024c0 204.364-80.472 398.56-224.955 543.04-144.483 144.48-338.68 224.95-543.044 224.95-204.36 0-398.56-80.47-543.04-224.95-144.48-144.482-224.95-338.676-224.95-543.04 0-204.365 80.47-398.562 224.96-543.045C625.44 336.47 819.64 256 1024 256c204.367 0 398.565 80.47 543.05 224.954C1711.532 625.437 1792 819.634 1792 1024zm-270.206 497.787C1654.256 1389.327 1728 1211.36 1728 1024c0-187.363-73.74-365.332-206.203-497.796C1389.332 393.74 1211.363 320 1024 320s-365.33 73.742-497.795 206.205C393.742 658.67 320 836.637 320 1024c0 187.36 73.744 365.326 206.206 497.787C658.67 1654.25 836.638 1727.99 1024 1727.99c187.362 0 365.33-73.74 497.794-206.203z"/>
<path d="M1438.64 1177.41c0-.03-.005-.017-.01.004l.01-.004z"/>
<path d="M1499.8 976.878c.03-.156-.024-.048-.11.107l.11-.107z"/>
<path d="M1105.19 991.642zm-68.013-376.128c-8.087-10.14-18.028-19.965-29.89-29.408-13.29-10.582-29-20.76-47.223-30.443-35.07-18.624-74.482-31.61-115.265-38.046-39.78-6.28-80.84-6.256-120.39.917l1.37 31.562c1.8.164 7.7 3.9 14.36 8.32-20.68 5.94-39.77 14.447-39.48 39.683l.2 17.48 17.3-1.73c29.38-2.95 60.17-2.06 90.32 2.61 9.21 1.42 18.36 3.2 27.38 5.32l-4.33 1.15c-20.45 5.58-38.93 12.52-54.25 20.61-46.28 24.32-75.51 60.85-90.14 108.37-14.14 45.95-14.27 101.81-2.72 166.51l.06.06c15.14 84.57 64.16 316.39 104.11 505.39 19.78 93.59 37.38 176.83 47.14 224.4 3.26 15.84 5.03 31.02 5.52 45.52.3 9.08.09 17.96-.58 26.62-.45 5.8-1.11 11.51-1.96 17.112l31.62 4.75c.71-4.705 1.3-9.494 1.76-14.373 48.964 10.517 99.78 16.05 151.88 16.05 60.68 0 119.61-7.505 175.91-21.64 3.04 6.08 6.08 12.19 9.11 18.32l28.62-14.128c-2.11-4.27-4.235-8.55-6.37-12.84-23.005-46.124-47.498-93.01-68.67-133.534-15.39-29.466-29.01-55.53-39.046-75.58-26.826-53.618-53.637-119.47-68.28-182.368-8.78-37.705-13.128-74.098-10.308-105.627-15.31-6.28-26.69-11.8-31.968-15.59l-.01.015c-14.22-10.2-31.11-28.12-41.82-49.717-8.618-17.376-13.4-37.246-10.147-57.84 3.17-19.84 27.334-46.714 57.843-67.46v-.063c26.554-18.05 58.75-32.506 86.32-34.31 7.835-.51 16.31-1.008 23.99-1.45 33.45-1.95 50.243-2.93 84.475-11.42 10.88-2.697 26.19-6.56 43.53-11.09 2.364-40.7-5.947-87.596-21.04-133.234-22.004-66.53-58.68-131.25-97.627-170.21-12.543-12.55-28.17-22.79-45.9-30.933-16.88-7.753-35.64-13.615-55.436-17.782zm-10.658 178.553s6.77-42.485 58.39-33.977c27.96 4.654 37.89 29.833 37.89 29.833s-25.31-14.46-44.95-14.198c-40.33.53-51.35 18.342-51.35 18.342zm-240.45-18.802c48.49-19.853 72.11 11.298 72.11 11.298s-35.21-15.928-69.46 5.59c-34.19 21.477-32.92 43.452-32.92 43.452s-18.17-40.5 30.26-60.34zm296.5 95.4c0-6.677 2.68-12.694 7.01-17.02 4.37-4.37 10.42-7.074 17.1-7.074 6.73 0 12.79 2.7 17.15 7.05 4.33 4.33 7.01 10.36 7.01 17.05 0 6.74-2.7 12.81-7.07 17.18-4.33 4.33-10.37 7.01-17.1 7.01-6.68 0-12.72-2.69-17.05-7.03-4.36-4.37-7.07-10.43-7.07-17.16zm-268.42 51.27c0-8.535 3.41-16.22 8.93-21.738 5.55-5.55 13.25-8.982 21.81-8.982 8.51 0 16.18 3.415 21.7 8.934 5.55 5.55 8.98 13.25 8.98 21.78 0 8.53-3.44 16.23-8.98 21.79-5.52 5.52-13.19 8.93-21.71 8.93-8.55 0-16.26-3.43-21.82-8.99-5.52-5.52-8.93-13.2-8.93-21.74z"/>
<path d="M1102.48 986.34zm390.074-64.347c-28.917-11.34-74.89-12.68-93.32-3.778-11.5 5.567-35.743 13.483-63.565 21.707-25.75 7.606-53.9 15.296-78.15 21.702-17.69 4.67-33.3 8.66-44.4 11.435-34.92 8.76-52.05 9.77-86.17 11.78-7.84.46-16.48.97-24.48 1.5-28.12 1.86-60.97 16.77-88.05 35.4v.06c-31.12 21.4-55.77 49.12-59.01 69.59-3.32 21.24 1.56 41.74 10.35 59.67 10.92 22.28 28.15 40.77 42.66 51.29l.01-.02c5.38 3.9 16.98 9.6 32.6 16.08 26.03 10.79 63.2 23.76 101.25 34.23 43.6 11.99 89.11 21.05 121.69 20.41 34.26-.69 77.73-10.52 114.54-24.67 22.15-8.52 42.21-18.71 56.88-29.58 17.85-13.22 28.7-28.42 28.4-44.74-.07-3.89-.72-7.63-1.97-11.21l-.02.01c-11.6-33.06-50.37-23.59-105.53-10.12-46.86 11.445-107.94 26.365-169.01 20.434-32.56-3.167-54.45-10.61-67.88-20.133-5.96-4.224-9.93-8.67-12.18-13.11-1.96-3.865-2.68-7.84-2.33-11.714.39-4.42 2.17-9.048 5.1-13.57l-.05-.03c7.86-12.118 23.082-9.72 43.93-6.43 25.91 4.08 58.2 9.172 99.013-3.61 39.63-12.378 87.76-29.9 131.184-47.39 42.405-17.08 80.08-34.078 100.74-46.18 25.46-14.87 37.57-29.428 40.59-42.866 2.725-12.152-.89-22.48-8.903-31.07-5.87-6.29-14.254-11.31-23.956-15.115z"/>
</svg>
</a>
</li>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow.">
<!-- Stack Overflow icon by Picons.me, from https://www.iconfinder.com/Picons -->
<!-- Free for commercial use -->
<svg class="stackoverflow" height="16" viewBox="-1163 1657.697 56.693 56.693" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M-1126.04 1689.533l-16.577-9.778 2.088-3.54 16.578 9.778zM-1127.386 1694.635l-18.586-4.996 1.068-3.97 18.586 4.995zM-1127.824 1700.137l-19.165-1.767.378-4.093 19.165 1.767zM-1147.263 1701.293h19.247v4.11h-19.247z"/>
<path d="M-1121.458 1710.947s0 .96-.032.96v.016h-30.796s-.96 0-.96-.016h-.032v-20.03h3.288v16.805h25.244v-16.804h3.288v19.07zM-1130.667 1667.04l10.844 15.903-3.396 2.316-10.843-15.903zM-1118.313 1663.044l3.29 18.963-4.05.703-3.29-18.963z"/>
</svg>
</a>
</li>
</ul>
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
<button id="copy-button" class="rightButton clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipboard">
COPY
</button>
<button id="hide-error" class="rightButton" title="Hide error message" onclick="document.getElementsByClassName('Whoops')[0].style.display = 'none';">
HIDE
</button>
</div>
</div>

View file

@ -1,3 +0,0 @@
<header>
<?php $tpl->render($header) ?>
</header>

View file

@ -1,34 +0,0 @@
<?php
/**
* Layout template file for Whoops's pretty error output.
*/
?>
<!DOCTYPE html><?php echo $preface; ?>
<html>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<title><?php echo $tpl->escape($page_title) ?></title>
<style><?php echo $stylesheet ?></style>
<style><?php echo $prismCss ?></style>
</head>
<body>
<div class="Whoops container">
<div class="stack-container">
<?php $tpl->render($panel_left_outer) ?>
<?php $tpl->render($panel_details_outer) ?>
</div>
</div>
<script data-manual><?php echo $prismJs ?></script>
<script><?php echo $zepto ?></script>
<script><?php echo $clipboard ?></script>
<script><?php echo $javascript ?></script>
</body>
</html>

View file

@ -1,2 +0,0 @@
<?php $tpl->render($frame_code) ?>
<?php $tpl->render($env_details) ?>

View file

@ -1,3 +0,0 @@
<div class="panel details-container cf">
<?php $tpl->render($panel_details) ?>
</div>

View file

@ -1,4 +0,0 @@
<?php
$tpl->render($header_outer);
$tpl->render($frames_description);
$tpl->render($frames_container);

View file

@ -1,3 +0,0 @@
<div class="panel left-panel cf <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php $tpl->render($panel_left) ?>
</div>

View file

@ -1,597 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use InvalidArgumentException;
use Throwable;
use Whoops\Exception\ErrorException;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\Handler;
use Whoops\Handler\HandlerInterface;
use Whoops\Inspector\CallableInspectorFactory;
use Whoops\Inspector\InspectorFactory;
use Whoops\Inspector\InspectorFactoryInterface;
use Whoops\Inspector\InspectorInterface;
use Whoops\Util\Misc;
use Whoops\Util\SystemFacade;
final class Run implements RunInterface
{
/**
* @var bool
*/
private $isRegistered;
/**
* @var bool
*/
private $allowQuit = true;
/**
* @var bool
*/
private $sendOutput = true;
/**
* @var integer|false
*/
private $sendHttpCode = 500;
/**
* @var integer|false
*/
private $sendExitCode = 1;
/**
* @var HandlerInterface[]
*/
private $handlerStack = [];
/**
* @var array
* @psalm-var list<array{patterns: string, levels: int}>
*/
private $silencedPatterns = [];
/**
* @var SystemFacade
*/
private $system;
/**
* In certain scenarios, like in shutdown handler, we can not throw exceptions.
*
* @var bool
*/
private $canThrowExceptions = true;
/**
* The inspector factory to create inspectors.
*
* @var InspectorFactoryInterface
*/
private $inspectorFactory;
/**
* @var array<callable>
*/
private $frameFilters = [];
public function __construct(SystemFacade $system = null)
{
$this->system = $system ?: new SystemFacade;
$this->inspectorFactory = new InspectorFactory();
}
/**
* Explicitly request your handler runs as the last of all currently registered handlers.
*
* @param callable|HandlerInterface $handler
*
* @return Run
*/
public function appendHandler($handler)
{
array_unshift($this->handlerStack, $this->resolveHandler($handler));
return $this;
}
/**
* Explicitly request your handler runs as the first of all currently registered handlers.
*
* @param callable|HandlerInterface $handler
*
* @return Run
*/
public function prependHandler($handler)
{
return $this->pushHandler($handler);
}
/**
* Register your handler as the last of all currently registered handlers (to be executed first).
* Prefer using appendHandler and prependHandler for clarity.
*
* @param callable|HandlerInterface $handler
*
* @return Run
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
*/
public function pushHandler($handler)
{
$this->handlerStack[] = $this->resolveHandler($handler);
return $this;
}
/**
* Removes and returns the last handler pushed to the handler stack.
*
* @see Run::removeFirstHandler(), Run::removeLastHandler()
*
* @return HandlerInterface|null
*/
public function popHandler()
{
return array_pop($this->handlerStack);
}
/**
* Removes the first handler.
*
* @return void
*/
public function removeFirstHandler()
{
array_pop($this->handlerStack);
}
/**
* Removes the last handler.
*
* @return void
*/
public function removeLastHandler()
{
array_shift($this->handlerStack);
}
/**
* Returns an array with all handlers, in the order they were added to the stack.
*
* @return array
*/
public function getHandlers()
{
return $this->handlerStack;
}
/**
* Clears all handlers in the handlerStack, including the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers()
{
$this->handlerStack = [];
return $this;
}
public function getFrameFilters()
{
return $this->frameFilters;
}
public function clearFrameFilters()
{
$this->frameFilters = [];
return $this;
}
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register()
{
if (!$this->isRegistered) {
// Workaround PHP bug 42098
// https://bugs.php.net/bug.php?id=42098
class_exists("\\Whoops\\Exception\\ErrorException");
class_exists("\\Whoops\\Exception\\FrameCollection");
class_exists("\\Whoops\\Exception\\Frame");
class_exists("\\Whoops\\Exception\\Inspector");
class_exists("\\Whoops\\Inspector\\InspectorFactory");
$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
$this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
$this->isRegistered = true;
}
return $this;
}
/**
* Unregisters all handlers registered by this Whoops\Run instance.
*
* @return Run
*/
public function unregister()
{
if ($this->isRegistered) {
$this->system->restoreExceptionHandler();
$this->system->restoreErrorHandler();
$this->isRegistered = false;
}
return $this;
}
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
*
* @return bool
*/
public function allowQuit($exit = null)
{
if (func_num_args() == 0) {
return $this->allowQuit;
}
return $this->allowQuit = (bool) $exit;
}
/**
* Silence particular errors in particular files.
*
* @param array|string $patterns List or a single regex pattern to match.
* @param int $levels Defaults to E_STRICT | E_DEPRECATED.
*
* @return Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240)
{
$this->silencedPatterns = array_merge(
$this->silencedPatterns,
array_map(
function ($pattern) use ($levels) {
return [
"pattern" => $pattern,
"levels" => $levels,
];
},
(array) $patterns
)
);
return $this;
}
/**
* Returns an array with silent errors in path configuration.
*
* @return array
*/
public function getSilenceErrorsInPaths()
{
return $this->silencedPatterns;
}
/**
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
*
* @return int|false
*
* @throws InvalidArgumentException
*/
public function sendHttpCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendHttpCode;
}
if (!$code) {
return $this->sendHttpCode = false;
}
if ($code === true) {
$code = 500;
}
if ($code < 400 || 600 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be 4xx or 5xx"
);
}
return $this->sendHttpCode = $code;
}
/**
* Should Whoops exit with a specific code on the CLI if possible?
* Whoops will exit with 1 by default, but you can specify something else.
*
* @param int $code
*
* @return int
*
* @throws InvalidArgumentException
*/
public function sendExitCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendExitCode;
}
if ($code < 0 || 255 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be between 0 and 254"
);
}
return $this->sendExitCode = (int) $code;
}
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException.
*
* @param bool|int $send
*
* @return bool
*/
public function writeToOutput($send = null)
{
if (func_num_args() == 0) {
return $this->sendOutput;
}
return $this->sendOutput = (bool) $send;
}
/**
* Handles an exception, ultimately generating a Whoops error page.
*
* @param Throwable $exception
*
* @return string Output generated by handlers.
*/
public function handleException($exception)
{
// Walk the registered handlers in the reverse order
// they were registered, and pass off the exception
$inspector = $this->getInspector($exception);
// Capture output produced while handling the exception,
// we might want to send it straight away to the client,
// or return it silently.
$this->system->startOutputBuffering();
// Just in case there are no handlers:
$handlerResponse = null;
$handlerContentType = null;
try {
foreach (array_reverse($this->handlerStack) as $handler) {
$handler->setRun($this);
$handler->setInspector($inspector);
$handler->setException($exception);
// The HandlerInterface does not require an Exception passed to handle()
// and neither of our bundled handlers use it.
// However, 3rd party handlers may have already relied on this parameter,
// and removing it would be possibly breaking for users.
$handlerResponse = $handler->handle($exception);
// Collect the content type for possible sending in the headers.
$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) {
// The Handler has handled the exception in some way, and
// wishes to quit execution (Handler::QUIT), or skip any
// other handlers (Handler::LAST_HANDLER). If $this->allowQuit
// is false, Handler::QUIT behaves like Handler::LAST_HANDLER
break;
}
}
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
} finally {
$output = $this->system->cleanOutputBuffer();
}
// If we're allowed to, send output generated by handlers directly
// to the output, otherwise, and if the script doesn't quit, return
// it so that it may be used by the caller
if ($this->writeToOutput()) {
// @todo Might be able to clean this up a bit better
if ($willQuit) {
// Cleanup all other output buffers before sending our output:
while ($this->system->getOutputBufferLevel() > 0) {
$this->system->endOutputBuffering();
}
// Send any headers if needed:
if (Misc::canSendHeaders() && $handlerContentType) {
header("Content-Type: {$handlerContentType}");
}
}
$this->writeToOutputNow($output);
}
if ($willQuit) {
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
$this->system->flushOutputBuffer();
$this->system->stopExecution(
$this->sendExitCode()
);
}
return $output;
}
/**
* Converts generic PHP errors to \ErrorException instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string|null $file
* @param int|null $line
*
* @return bool
*
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null)
{
if ($level & $this->system->getErrorReportingLevel()) {
foreach ($this->silencedPatterns as $entry) {
$pathMatches = (bool) preg_match($entry["pattern"], $file);
$levelMatches = $level & $entry["levels"];
if ($pathMatches && $levelMatches) {
// Ignore the error, abort handling
// See https://github.com/filp/whoops/issues/418
return true;
}
}
// XXX we pass $level for the "code" param only for BC reasons.
// see https://github.com/filp/whoops/issues/267
$exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
if ($this->canThrowExceptions) {
throw $exception;
} else {
$this->handleException($exception);
}
// Do not propagate errors which were already handled by Whoops.
return true;
}
// Propagate error to the next handler, allows error_get_last() to
// work on silenced errors.
return false;
}
/**
* Special case to deal with Fatal errors and the like.
*
* @return void
*/
public function handleShutdown()
{
// If we reached this step, we are in shutdown handler.
// An exception thrown in a shutdown handler will not be propagated
// to the exception handler. Pass that information along.
$this->canThrowExceptions = false;
$error = $this->system->getLastError();
if ($error && Misc::isLevelFatal($error['type'])) {
// If there was a fatal error,
// it was not handled in handleError yet.
$this->allowQuit = false;
$this->handleError(
$error['type'],
$error['message'],
$error['file'],
$error['line']
);
}
}
/**
* @param InspectorFactoryInterface $factory
*
* @return void
*/
public function setInspectorFactory(InspectorFactoryInterface $factory)
{
$this->inspectorFactory = $factory;
}
public function addFrameFilter($filterCallback)
{
if (!is_callable($filterCallback)) {
throw new \InvalidArgumentException(sprintf(
"A frame filter must be of type callable, %s type given.",
gettype($filterCallback)
));
}
$this->frameFilters[] = $filterCallback;
return $this;
}
/**
* @param Throwable $exception
*
* @return InspectorInterface
*/
private function getInspector($exception)
{
return $this->inspectorFactory->create($exception);
}
/**
* Resolves the giving handler.
*
* @param callable|HandlerInterface $handler
*
* @return HandlerInterface
*
* @throws InvalidArgumentException
*/
private function resolveHandler($handler)
{
if (is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
"Handler must be a callable, or instance of "
. "Whoops\\Handler\\HandlerInterface"
);
}
return $handler;
}
/**
* Echo something to the browser.
*
* @param string $output
*
* @return Run
*/
private function writeToOutputNow($output)
{
if ($this->sendHttpCode() && Misc::canSendHeaders()) {
$this->system->setHttpResponseCode(
$this->sendHttpCode()
);
}
echo $output;
return $this;
}
}

View file

@ -1,158 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use InvalidArgumentException;
use Whoops\Exception\ErrorException;
use Whoops\Handler\HandlerInterface;
interface RunInterface
{
const EXCEPTION_HANDLER = "handleException";
const ERROR_HANDLER = "handleError";
const SHUTDOWN_HANDLER = "handleShutdown";
/**
* Pushes a handler to the end of the stack
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface
* @param Callable|HandlerInterface $handler
* @return Run
*/
public function pushHandler($handler);
/**
* Removes the last handler in the stack and returns it.
* Returns null if there"s nothing else to pop.
*
* @return null|HandlerInterface
*/
public function popHandler();
/**
* Returns an array with all handlers, in the
* order they were added to the stack.
*
* @return array
*/
public function getHandlers();
/**
* Clears all handlers in the handlerStack, including
* the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers();
/**
* @return array<callable>
*/
public function getFrameFilters();
/**
* @return Run
*/
public function clearFrameFilters();
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register();
/**
* Unregisters all handlers registered by this Whoops\Run instance
*
* @return Run
*/
public function unregister();
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
* @return bool
*/
public function allowQuit($exit = null);
/**
* Silence particular errors in particular files
*
* @param array|string $patterns List or a single regex pattern to match
* @param int $levels Defaults to E_STRICT | E_DEPRECATED
* @return \Whoops\Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240);
/**
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
* @return int|false
*/
public function sendHttpCode($code = null);
/**
* Should Whoops exit with a specific code on the CLI if possible?
* Whoops will exit with 1 by default, but you can specify something else.
*
* @param int $code
* @return int
*/
public function sendExitCode($code = null);
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException
*
* @param bool|int $send
* @return bool
*/
public function writeToOutput($send = null);
/**
* Handles an exception, ultimately generating a Whoops error
* page.
*
* @param \Throwable $exception
* @return string Output generated by handlers
*/
public function handleException($exception);
/**
* Converts generic PHP errors to \ErrorException
* instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string $file
* @param int $line
*
* @return bool
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null);
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown();
/**
* Registers a filter callback in the frame filters stack.
*
* @param callable $filterCallback
* @return \Whoops\Run
*/
public function addFrameFilter($filterCallback);
}

View file

@ -1,36 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
/**
* Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump()
*
* @see TemplateHelper::dump()
*/
class HtmlDumperOutput
{
private $output;
public function __invoke($line, $depth)
{
// A negative depth means "end of dump"
if ($depth >= 0) {
// Adds a two spaces indentation to the line
$this->output .= str_repeat(' ', $depth) . $line . "\n";
}
}
public function getOutput()
{
return $this->output;
}
public function clear()
{
$this->output = null;
}
}

View file

@ -1,77 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
class Misc
{
/**
* Can we at this point in time send HTTP headers?
*
* Currently this checks if we are even serving an HTTP request,
* as opposed to running from a command line.
*
* If we are serving an HTTP request, we check if it's not too late.
*
* @return bool
*/
public static function canSendHeaders()
{
return isset($_SERVER["REQUEST_URI"]) && !headers_sent();
}
public static function isAjaxRequest()
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
/**
* Check, if possible, that this execution was triggered by a command line.
* @return bool
*/
public static function isCommandLine()
{
return PHP_SAPI == 'cli';
}
/**
* Translate ErrorException code into the represented constant.
*
* @param int $error_code
* @return string
*/
public static function translateErrorCode($error_code)
{
$constants = get_defined_constants(true);
if (array_key_exists('Core', $constants)) {
foreach ($constants['Core'] as $constant => $value) {
if (substr($constant, 0, 2) == 'E_' && $value == $error_code) {
return $constant;
}
}
}
return "E_UNKNOWN";
}
/**
* Determine if an error level is fatal (halts execution)
*
* @param int $level
* @return bool
*/
public static function isLevelFatal($level)
{
$errors = E_ERROR;
$errors |= E_PARSE;
$errors |= E_CORE_ERROR;
$errors |= E_CORE_WARNING;
$errors |= E_COMPILE_ERROR;
$errors |= E_COMPILE_WARNING;
return ($level & $errors) > 0;
}
}

View file

@ -1,144 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
class SystemFacade
{
/**
* Turns on output buffering.
*
* @return bool
*/
public function startOutputBuffering()
{
return ob_start();
}
/**
* @param callable $handler
* @param int $types
*
* @return callable|null
*/
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
{
// Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT)
if ($types === 'use-php-defaults') {
$types = E_ALL;
}
return set_error_handler($handler, $types);
}
/**
* @param callable $handler
*
* @return callable|null
*/
public function setExceptionHandler(callable $handler)
{
return set_exception_handler($handler);
}
/**
* @return void
*/
public function restoreExceptionHandler()
{
restore_exception_handler();
}
/**
* @return void
*/
public function restoreErrorHandler()
{
restore_error_handler();
}
/**
* @param callable $function
*
* @return void
*/
public function registerShutdownFunction(callable $function)
{
register_shutdown_function($function);
}
/**
* @return string|false
*/
public function cleanOutputBuffer()
{
return ob_get_clean();
}
/**
* @return int
*/
public function getOutputBufferLevel()
{
return ob_get_level();
}
/**
* @return bool
*/
public function endOutputBuffering()
{
return ob_end_clean();
}
/**
* @return void
*/
public function flushOutputBuffer()
{
flush();
}
/**
* @return int
*/
public function getErrorReportingLevel()
{
return error_reporting();
}
/**
* @return array|null
*/
public function getLastError()
{
return error_get_last();
}
/**
* @param int $httpCode
*
* @return int
*/
public function setHttpResponseCode($httpCode)
{
if (!headers_sent()) {
// Ensure that no 'location' header is present as otherwise this
// will override the HTTP code being set here, and mask the
// expected error page.
header_remove('location');
}
return http_response_code($httpCode);
}
/**
* @param int $exitStatus
*/
public function stopExecution($exitStatus)
{
exit($exitStatus);
}
}

View file

@ -1,349 +0,0 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Whoops\Exception\Frame;
/**
* Exposes useful tools for working with/in templates
*/
class TemplateHelper
{
/**
* An array of variables to be passed to all templates
* @var array
*/
private $variables = [];
/**
* @var HtmlDumper
*/
private $htmlDumper;
/**
* @var HtmlDumperOutput
*/
private $htmlDumperOutput;
/**
* @var AbstractCloner
*/
private $cloner;
/**
* @var string
*/
private $applicationRootPath;
public function __construct()
{
// root path for ordinary composer projects
$this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
}
/**
* Escapes a string for output in an HTML document
*
* @param string $raw
* @return string
*/
public function escape($raw)
{
$flags = ENT_QUOTES;
// HHVM has all constants defined, but only ENT_IGNORE
// works at the moment
if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) {
$flags |= ENT_SUBSTITUTE;
} else {
// This is for 5.3.
// The documentation warns of a potential security issue,
// but it seems it does not apply in our case, because
// we do not blacklist anything anywhere.
$flags |= ENT_IGNORE;
}
$raw = str_replace(chr(9), ' ', $raw);
return htmlspecialchars($raw, $flags, "UTF-8");
}
/**
* Escapes a string for output in an HTML document, but preserves
* URIs within it, and converts them to clickable anchor elements.
*
* @param string $raw
* @return string
*/
public function escapeButPreserveUris($raw)
{
$escaped = $this->escape($raw);
return preg_replace(
"@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@",
"<a href=\"$1\" target=\"_blank\" rel=\"noreferrer noopener\">$1</a>",
$escaped
);
}
/**
* Makes sure that the given string breaks on the delimiter.
*
* @param string $delimiter
* @param string $s
* @return string
*/
public function breakOnDelimiter($delimiter, $s)
{
$parts = explode($delimiter, $s);
foreach ($parts as &$part) {
$part = '<span class="delimiter">' . $part . '</span>';
}
return implode($delimiter, $parts);
}
/**
* Replace the part of the path that all files have in common.
*
* @param string $path
* @return string
*/
public function shorten($path)
{
if ($this->applicationRootPath != "/") {
$path = str_replace($this->applicationRootPath, '&hellip;', $path);
}
return $path;
}
private function getDumper()
{
if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$this->htmlDumperOutput = new HtmlDumperOutput();
// re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump.
$this->htmlDumper = new HtmlDumper($this->htmlDumperOutput);
$styles = [
'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
'num' => 'color:#BCD42A',
'const' => 'color: #4bb1b1;',
'str' => 'color:#BCD42A',
'note' => 'color:#ef7c61',
'ref' => 'color:#A0A0A0',
'public' => 'color:#FFFFFF',
'protected' => 'color:#FFFFFF',
'private' => 'color:#FFFFFF',
'meta' => 'color:#FFFFFF',
'key' => 'color:#BCD42A',
'index' => 'color:#ef7c61',
];
$this->htmlDumper->setStyles($styles);
}
return $this->htmlDumper;
}
/**
* Format the given value into a human readable string.
*
* @param mixed $value
* @return string
*/
public function dump($value)
{
$dumper = $this->getDumper();
if ($dumper) {
// re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump.
// exclude verbose information (e.g. exception stack traces)
if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) {
$cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE);
// Symfony VarDumper 2.6 Caster class dont exist.
} else {
$cloneVar = $this->getCloner()->cloneVar($value);
}
$dumper->dump(
$cloneVar,
$this->htmlDumperOutput
);
$output = $this->htmlDumperOutput->getOutput();
$this->htmlDumperOutput->clear();
return $output;
}
return htmlspecialchars(print_r($value, true));
}
/**
* Format the args of the given Frame as a human readable html string
*
* @param Frame $frame
* @return string the rendered html
*/
public function dumpArgs(Frame $frame)
{
// we support frame args only when the optional dumper is available
if (!$this->getDumper()) {
return '';
}
$html = '';
$numFrames = count($frame->getArgs());
if ($numFrames > 0) {
$html = '<ol class="linenums">';
foreach ($frame->getArgs() as $j => $frameArg) {
$html .= '<li>'. $this->dump($frameArg) .'</li>';
}
$html .= '</ol>';
}
return $html;
}
/**
* Convert a string to a slug version of itself
*
* @param string $original
* @return string
*/
public function slug($original)
{
$slug = str_replace(" ", "-", $original);
$slug = preg_replace('/[^\w\d\-\_]/i', '', $slug);
return strtolower($slug);
}
/**
* Given a template path, render it within its own scope. This
* method also accepts an array of additional variables to be
* passed to the template.
*
* @param string $template
*/
public function render($template, array $additionalVariables = null)
{
$variables = $this->getVariables();
// Pass the helper to the template:
$variables["tpl"] = $this;
if ($additionalVariables !== null) {
$variables = array_replace($variables, $additionalVariables);
}
call_user_func(function () {
extract(func_get_arg(1));
require func_get_arg(0);
}, $template, $variables);
}
/**
* Sets the variables to be passed to all templates rendered
* by this template helper.
*/
public function setVariables(array $variables)
{
$this->variables = $variables;
}
/**
* Sets a single template variable, by its name:
*
* @param string $variableName
* @param mixed $variableValue
*/
public function setVariable($variableName, $variableValue)
{
$this->variables[$variableName] = $variableValue;
}
/**
* Gets a single template variable, by its name, or
* $defaultValue if the variable does not exist
*
* @param string $variableName
* @param mixed $defaultValue
* @return mixed
*/
public function getVariable($variableName, $defaultValue = null)
{
return isset($this->variables[$variableName]) ?
$this->variables[$variableName] : $defaultValue;
}
/**
* Unsets a single template variable, by its name
*
* @param string $variableName
*/
public function delVariable($variableName)
{
unset($this->variables[$variableName]);
}
/**
* Returns all variables for this helper
*
* @return array
*/
public function getVariables()
{
return $this->variables;
}
/**
* Set the cloner used for dumping variables.
*
* @param AbstractCloner $cloner
*/
public function setCloner($cloner)
{
$this->cloner = $cloner;
}
/**
* Get the cloner used for dumping variables.
*
* @return AbstractCloner
*/
public function getCloner()
{
if (!$this->cloner) {
$this->cloner = new VarCloner();
}
return $this->cloner;
}
/**
* Set the application root path.
*
* @param string $applicationRootPath
*/
public function setApplicationRootPath($applicationRootPath)
{
$this->applicationRootPath = $applicationRootPath;
}
/**
* Return the application root path.
*
* @return string
*/
public function getApplicationRootPath()
{
return $this->applicationRootPath;
}
}

View file

@ -1,629 +0,0 @@
### 2.9.2 (2023-10-27)
* Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)
* Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)
* Fixed normalization error when normalizing incomplete classes (#1833)
### 2.9.1 (2023-02-06)
* Fixed Logger not being serializable anymore (#1792)
### 2.9.0 (2023-02-05)
* Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748)
* Added support for enum context values in PsrLogMessageProcessor (#1773)
* Added graylog2/gelf-php 2.x support (#1747)
* Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739)
* Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791)
* Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758)
* Fixed infinite loop detection within Fibers (#1753)
* Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781)
### 2.8.0 (2022-07-24)
* Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)
* Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723)
* Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733)
* Added `GoogleCloudLoggingFormatter` (#1719)
* Added support for Predis 2.x (#1732)
* Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724)
* Fixed serialization/unserialization of handlers to make sure private properties are included (#1727)
* Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720)
* Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726)
* Fixed PHP 8.2 deprecation warnings (#1722)
* Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678)
### 2.7.0 (2022-06-09)
* Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682)
* Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681)
* Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670)
* Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677)
* Fixed RotatingFileHandler issue when the date format contained slashes (#1671)
### 2.6.0 (2022-05-10)
* Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead
* Added `SymfonyMailerHandler` (#1663)
* Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662)
* Added a way to filter/modify stack traces in LineFormatter (#1665)
* Fixed UdpSocket not being able to reopen/reconnect after close()
* Fixed infinite loops if a Handler is triggering logging while handling log records
### 2.5.0 (2022-04-08)
* Added `callType` to IntrospectionProcessor (#1612)
* Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651)
### 2.4.0 (2022-03-14)
* Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes
* Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603)
* Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600)
* Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637)
* Added support for keeping native BSON types as is in MongoDBFormatter (#1620)
* Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613)
* Added support for username/userIcon in SlackWebhookHandler (#1617)
* Added extension points to BrowserConsoleHandler (#1593)
* Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630)
* Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614)
* Fixed a few setter methods not returning `self` (#1609)
* Fixed handling of records going over the max Telegram message length (#1616)
### 2.3.5 (2021-10-01)
* Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592)
### 2.3.4 (2021-09-15)
* Fixed support for psr/log 3.x (#1589)
### 2.3.3 (2021-09-14)
* Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577)
* Fixed support for psr/log 2.x (#1587)
* Fixed some type annotations
### 2.3.2 (2021-07-23)
* Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568)
### 2.3.1 (2021-07-14)
* Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563)
* Fixed some `@inheritDoc` annotations having the wrong case
### 2.3.0 (2021-07-05)
* Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557)
* Added ability to customize date format when using JsonFormatter (#1561)
* Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531)
* Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540)
* Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553)
### 2.2.0 (2020-12-14)
* Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere
* Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation
* Added RedisPubSubHandler to log records to a Redis channel using PUBLISH
* Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7
* Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms
* Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket)
* Added handleBatch support for TelegramBotHandler
* Added RFC5424e extended date format including milliseconds to SyslogUdpHandler
* Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars)
* Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters
* Fixed PHP 8 issues in SyslogUdpHandler
* Fixed internal type error when mbstring is missing
### 2.1.1 (2020-07-23)
* Fixed removing of json encoding options
* Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler
* Fixed SwiftMailerHandler not accepting email templates with an empty subject
* Fixed array access on null in RavenHandler
* Fixed unique_id in WebProcessor not being disableable
### 2.1.0 (2020-05-22)
* Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [<EFBFBD>](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution
* Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output
* Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler
* Added tentative support for PHP 8
* NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags
* Fixed GitProcessor type error when there is no git repo present
* Fixed normalization of SoapFault objects containing deeply nested objects as "detail"
* Fixed support for relative paths in RotatingFileHandler
### 2.0.2 (2019-12-20)
* Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records
* Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter
* Fixed formatting of resources in JsonFormatter
* Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services)
* Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it
* Fixed Turkish locale messing up the conversion of level names to their constant values
### 2.0.1 (2019-11-13)
* Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable
* Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler
* Fixed BrowserConsoleHandler formatting when using multiple styles
* Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings
* Fixed normalization of SoapFault objects containing non-strings as "detail"
* Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding
* Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB).
* Fixed type error in BrowserConsoleHandler when the context array of log records was not associative.
### 2.0.0 (2019-08-30)
* BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release
* BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types
* Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it
* Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half
* Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases
* Fixed date timezone handling in SyslogUdpHandler
### 2.0.0-beta2 (2019-07-06)
* BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release
* BC Break: PHP 7.2 is now the minimum required PHP version.
* BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details
* Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad)
* Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account
* Added support for JsonSerializable when normalizing exceptions
* Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler
* Added SoapFault details to formatted exceptions
* Fixed DeduplicationHandler silently failing to start when file could not be opened
* Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records
* Fixed GelfFormatter losing some data when one attachment was too long
* Fixed issue in SignalHandler restarting syscalls functionality
* Improved performance of LogglyHandler when sending multiple logs in a single request
### 2.0.0-beta1 (2018-12-08)
* BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release
* BC Break: PHP 7.1 is now the minimum required PHP version.
* BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters
* BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn`
* BC Break: The record timezone is now set per Logger instance and not statically anymore
* BC Break: There is no more default handler configured on empty Logger instances
* BC Break: ElasticSearchHandler renamed to ElasticaHandler
* BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details
* Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability.
* Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled)
* Added timezone and microseconds to the default date format
* Added SendGridHandler to use the SendGrid API to send emails
* Added LogmaticHandler to use the Logmatic.io API to store log records
* Added SqsHandler to send log records to an AWS SQS queue
* Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler
* Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files
* Added ProcessHandler to write log output to the STDIN of a given process
* Added HostnameProcessor that adds the machine's hostname to log records
* Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely
* Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler
* Fixed many minor issues in various handlers, and probably added a few regressions too
### 1.26.1 (2021-05-28)
* Fixed PHP 8.1 deprecation warning
### 1.26.0 (2020-12-14)
* Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x)
### 1.25.5 (2020-07-23)
* Fixed array access on null in RavenHandler
* Fixed unique_id in WebProcessor not being disableable
### 1.25.4 (2020-05-22)
* Fixed GitProcessor type error when there is no git repo present
* Fixed normalization of SoapFault objects containing deeply nested objects as "detail"
* Fixed support for relative paths in RotatingFileHandler
### 1.25.3 (2019-12-20)
* Fixed formatting of resources in JsonFormatter
* Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services)
* Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it
* Fixed Turkish locale messing up the conversion of level names to their constant values
### 1.25.2 (2019-11-13)
* Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable
* Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler
* Fixed BrowserConsoleHandler formatting when using multiple styles
* Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings
* Fixed normalization of SoapFault objects containing non-strings as "detail"
* Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding
### 1.25.1 (2019-09-06)
* Fixed forward-compatible interfaces to be compatible with Monolog 1.x too.
### 1.25.0 (2019-09-06)
* Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead
* Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead
* Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead
* Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though.
* Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler
* Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records
* Fixed issue in SignalHandler restarting syscalls functionality
* Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases
* Fixed ZendMonitorHandler to work with the latest Zend Server versions
* Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB).
### 1.24.0 (2018-11-05)
* BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings.
* Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors
* Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers)
* Added a way to log signals being received using Monolog\SignalHandler
* Added ability to customize error handling at the Logger level using Logger::setExceptionHandler
* Added InsightOpsHandler to migrate users of the LogEntriesHandler
* Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9
* Added capture of stack traces to ErrorHandler when logging PHP errors
* Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts
* Added forwarding of context info to FluentdFormatter
* Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example
* Added ability to extend/override BrowserConsoleHandler
* Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility
* Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility
* Dropped official support for HHVM in test builds
* Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain
* Fixed naming of fields in Slack handler, all field names are now capitalized in all cases
* Fixed HipChatHandler bug where slack dropped messages randomly
* Fixed normalization of objects in Slack handlers
* Fixed support for PHP7's Throwable in NewRelicHandler
* Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory
* Fixed table row styling issues in HtmlFormatter
* Fixed RavenHandler dropping the message when logging exception
* Fixed WhatFailureGroupHandler skipping processors when using handleBatch
and implement it where possible
* Fixed display of anonymous class names
### 1.23.0 (2017-06-19)
* Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
* Fixed GelfHandler truncation to be per field and not per message
* Fixed compatibility issue with PHP <5.3.6
* Fixed support for headless Chrome in ChromePHPHandler
* Fixed support for latest Aws SDK in DynamoDbHandler
* Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler
### 1.22.1 (2017-03-13)
* Fixed lots of minor issues in the new Slack integrations
* Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces
### 1.22.0 (2016-11-26)
* Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily
* Added MercurialProcessor to add mercurial revision and branch names to log records
* Added support for AWS SDK v3 in DynamoDbHandler
* Fixed fatal errors occurring when normalizing generators that have been fully consumed
* Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix)
* Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore
* Fixed SyslogUdpHandler to avoid sending empty frames
* Fixed a few PHP 7.0 and 7.1 compatibility issues
### 1.21.0 (2016-07-29)
* Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues
* Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order
* Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler
* Added information about SoapFault instances in NormalizerFormatter
* Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level
### 1.20.0 (2016-07-02)
* Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy
* Added StreamHandler::getUrl to retrieve the stream's URL
* Added ability to override addRow/addTitle in HtmlFormatter
* Added the $context to context information when the ErrorHandler handles a regular php error
* Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d
* Fixed WhatFailureGroupHandler to work with PHP7 throwables
* Fixed a few minor bugs
### 1.19.0 (2016-04-12)
* Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed
* Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors
* Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler
* Fixed HipChatHandler handling of long messages
### 1.18.2 (2016-04-02)
* Fixed ElasticaFormatter to use more precise dates
* Fixed GelfMessageFormatter sending too long messages
### 1.18.1 (2016-03-13)
* Fixed SlackHandler bug where slack dropped messages randomly
* Fixed RedisHandler issue when using with the PHPRedis extension
* Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension
* Fixed BrowserConsoleHandler regression
### 1.18.0 (2016-03-01)
* Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond
* Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames
* Added `Logger->withName` to clone a logger (keeping all handlers) with a new name
* Added FluentdFormatter for the Fluentd unix socket protocol
* Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed
* Added support for replacing context sub-keys using `%context.*%` in LineFormatter
* Added support for `payload` context value in RollbarHandler
* Added setRelease to RavenHandler to describe the application version, sent with every log
* Added support for `fingerprint` context value in RavenHandler
* Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed
* Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`
* Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places
### 1.17.2 (2015-10-14)
* Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers
* Fixed SlackHandler handling to use slack functionalities better
* Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id
* Fixed 5.3 compatibility regression
### 1.17.1 (2015-08-31)
* Fixed RollbarHandler triggering PHP notices
### 1.17.0 (2015-08-30)
* Added support for `checksum` and `release` context/extra values in RavenHandler
* Added better support for exceptions in RollbarHandler
* Added UidProcessor::getUid
* Added support for showing the resource type in NormalizedFormatter
* Fixed IntrospectionProcessor triggering PHP notices
### 1.16.0 (2015-08-09)
* Added IFTTTHandler to notify ifttt.com triggers
* Added Logger::setHandlers() to allow setting/replacing all handlers
* Added $capSize in RedisHandler to cap the log size
* Fixed StreamHandler creation of directory to only trigger when the first log write happens
* Fixed bug in the handling of curl failures
* Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler
* Fixed missing fatal errors records with handlers that need to be closed to flush log records
* Fixed TagProcessor::addTags support for associative arrays
### 1.15.0 (2015-07-12)
* Added addTags and setTags methods to change a TagProcessor
* Added automatic creation of directories if they are missing for a StreamHandler to open a log file
* Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure
* Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used
* Fixed HTML/JS escaping in BrowserConsoleHandler
* Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only)
### 1.14.0 (2015-06-19)
* Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library
* Added support for objects implementing __toString in the NormalizerFormatter
* Added support for HipChat's v2 API in HipChatHandler
* Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app
* Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true)
* Fixed curl errors being silently suppressed
### 1.13.1 (2015-03-09)
* Fixed regression in HipChat requiring a new token to be created
### 1.13.0 (2015-03-05)
* Added Registry::hasLogger to check for the presence of a logger instance
* Added context.user support to RavenHandler
* Added HipChat API v2 support in the HipChatHandler
* Added NativeMailerHandler::addParameter to pass params to the mail() process
* Added context data to SlackHandler when $includeContextAndExtra is true
* Added ability to customize the Swift_Message per-email in SwiftMailerHandler
* Fixed SwiftMailerHandler to lazily create message instances if a callback is provided
* Fixed serialization of INF and NaN values in Normalizer and LineFormatter
### 1.12.0 (2014-12-29)
* Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers.
* Added PsrHandler to forward records to another PSR-3 logger
* Added SamplingHandler to wrap around a handler and include only every Nth record
* Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now)
* Added exception codes in the output of most formatters
* Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line)
* Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data
* Added $host to HipChatHandler for users of private instances
* Added $transactionName to NewRelicHandler and support for a transaction_name context value
* Fixed MandrillHandler to avoid outputting API call responses
* Fixed some non-standard behaviors in SyslogUdpHandler
### 1.11.0 (2014-09-30)
* Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names
* Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails
* Added MandrillHandler to send emails via the Mandrillapp.com API
* Added SlackHandler to log records to a Slack.com account
* Added FleepHookHandler to log records to a Fleep.io account
* Added LogglyHandler::addTag to allow adding tags to an existing handler
* Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end
* Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing
* Added support for PhpAmqpLib in the AmqpHandler
* Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs
* Added support for adding extra fields from $_SERVER in the WebProcessor
* Fixed support for non-string values in PrsLogMessageProcessor
* Fixed SwiftMailer messages being sent with the wrong date in long running scripts
* Fixed minor PHP 5.6 compatibility issues
* Fixed BufferHandler::close being called twice
### 1.10.0 (2014-06-04)
* Added Logger::getHandlers() and Logger::getProcessors() methods
* Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached
* Added support for extra data in NewRelicHandler
* Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines
### 1.9.1 (2014-04-24)
* Fixed regression in RotatingFileHandler file permissions
* Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records
* Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative
### 1.9.0 (2014-04-20)
* Added LogEntriesHandler to send logs to a LogEntries account
* Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler
* Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes
* Added support for table formatting in FirePHPHandler via the table context key
* Added a TagProcessor to add tags to records, and support for tags in RavenHandler
* Added $appendNewline flag to the JsonFormatter to enable using it when logging to files
* Added sound support to the PushoverHandler
* Fixed multi-threading support in StreamHandler
* Fixed empty headers issue when ChromePHPHandler received no records
* Fixed default format of the ErrorLogHandler
### 1.8.0 (2014-03-23)
* Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them
* Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output
* Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler
* Added FlowdockHandler to send logs to a Flowdock account
* Added RollbarHandler to send logs to a Rollbar account
* Added HtmlFormatter to send prettier log emails with colors for each log level
* Added GitProcessor to add the current branch/commit to extra record data
* Added a Monolog\Registry class to allow easier global access to pre-configured loggers
* Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement
* Added support for HHVM
* Added support for Loggly batch uploads
* Added support for tweaking the content type and encoding in NativeMailerHandler
* Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor
* Fixed batch request support in GelfHandler
### 1.7.0 (2013-11-14)
* Added ElasticSearchHandler to send logs to an Elastic Search server
* Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB
* Added SyslogUdpHandler to send logs to a remote syslogd server
* Added LogglyHandler to send logs to a Loggly account
* Added $level to IntrospectionProcessor so it only adds backtraces when needed
* Added $version to LogstashFormatter to allow using the new v1 Logstash format
* Added $appName to NewRelicHandler
* Added configuration of Pushover notification retries/expiry
* Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default
* Added chainability to most setters for all handlers
* Fixed RavenHandler batch processing so it takes the message from the record with highest priority
* Fixed HipChatHandler batch processing so it sends all messages at once
* Fixed issues with eAccelerator
* Fixed and improved many small things
### 1.6.0 (2013-07-29)
* Added HipChatHandler to send logs to a HipChat chat room
* Added ErrorLogHandler to send logs to PHP's error_log function
* Added NewRelicHandler to send logs to NewRelic's service
* Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler
* Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel
* Added stack traces output when normalizing exceptions (json output & co)
* Added Monolog\Logger::API constant (currently 1)
* Added support for ChromePHP's v4.0 extension
* Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel
* Added support for sending messages to multiple users at once with the PushoverHandler
* Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)
* Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now
* Fixed issue in RotatingFileHandler when an open_basedir restriction is active
* Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0
* Fixed SyslogHandler issue when many were used concurrently with different facilities
### 1.5.0 (2013-04-23)
* Added ProcessIdProcessor to inject the PID in log records
* Added UidProcessor to inject a unique identifier to all log records of one request/run
* Added support for previous exceptions in the LineFormatter exception serialization
* Added Monolog\Logger::getLevels() to get all available levels
* Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle
### 1.4.1 (2013-04-01)
* Fixed exception formatting in the LineFormatter to be more minimalistic
* Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0
* Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days
* Fixed WebProcessor array access so it checks for data presence
* Fixed Buffer, Group and FingersCrossed handlers to make use of their processors
### 1.4.0 (2013-02-13)
* Added RedisHandler to log to Redis via the Predis library or the phpredis extension
* Added ZendMonitorHandler to log to the Zend Server monitor
* Added the possibility to pass arrays of handlers and processors directly in the Logger constructor
* Added `$useSSL` option to the PushoverHandler which is enabled by default
* Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously
* Fixed header injection capability in the NativeMailHandler
### 1.3.1 (2013-01-11)
* Fixed LogstashFormatter to be usable with stream handlers
* Fixed GelfMessageFormatter levels on Windows
### 1.3.0 (2013-01-08)
* Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface`
* Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance
* Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)
* Added PushoverHandler to send mobile notifications
* Added CouchDBHandler and DoctrineCouchDBHandler
* Added RavenHandler to send data to Sentry servers
* Added support for the new MongoClient class in MongoDBHandler
* Added microsecond precision to log records' timestamps
* Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing
the oldest entries
* Fixed normalization of objects with cyclic references
### 1.2.1 (2012-08-29)
* Added new $logopts arg to SyslogHandler to provide custom openlog options
* Fixed fatal error in SyslogHandler
### 1.2.0 (2012-08-18)
* Added AmqpHandler (for use with AMQP servers)
* Added CubeHandler
* Added NativeMailerHandler::addHeader() to send custom headers in mails
* Added the possibility to specify more than one recipient in NativeMailerHandler
* Added the possibility to specify float timeouts in SocketHandler
* Added NOTICE and EMERGENCY levels to conform with RFC 5424
* Fixed the log records to use the php default timezone instead of UTC
* Fixed BufferHandler not being flushed properly on PHP fatal errors
* Fixed normalization of exotic resource types
* Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog
### 1.1.0 (2012-04-23)
* Added Monolog\Logger::isHandling() to check if a handler will
handle the given log level
* Added ChromePHPHandler
* Added MongoDBHandler
* Added GelfHandler (for use with Graylog2 servers)
* Added SocketHandler (for use with syslog-ng for example)
* Added NormalizerFormatter
* Added the possibility to change the activation strategy of the FingersCrossedHandler
* Added possibility to show microseconds in logs
* Added `server` and `referer` to WebProcessor output
### 1.0.2 (2011-10-24)
* Fixed bug in IE with large response headers and FirePHPHandler
### 1.0.1 (2011-08-25)
* Added MemoryPeakUsageProcessor and MemoryUsageProcessor
* Added Monolog\Logger::getName() to get a logger's channel name
### 1.0.0 (2011-07-06)
* Added IntrospectionProcessor to get info from where the logger was called
* Fixed WebProcessor in CLI
### 1.0.0-RC1 (2011-07-01)
* Initial release

View file

@ -1,19 +0,0 @@
Copyright (c) 2011-2020 Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,112 +0,0 @@
# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions)
[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
Monolog sends your logs to files, sockets, inboxes, databases and various
web services. See the complete list of handlers below. Special handlers
allow you to build advanced logging strategies.
This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
interface that you can type-hint against in your own libraries to keep
a maximum of interoperability. You can also use it in your applications to
make sure you can always use another compatible logger at a later time.
As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels.
Internally Monolog still uses its own level scheme since it predates PSR-3.
## Installation
Install the latest version with
```bash
$ composer require monolog/monolog
```
## Basic Usage
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// add records to the log
$log->warning('Foo');
$log->error('Bar');
```
## Documentation
- [Usage Instructions](doc/01-usage.md)
- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md)
- [Utility Classes](doc/03-utilities.md)
- [Extending Monolog](doc/04-extending.md)
- [Log Record Structure](doc/message-structure.md)
## Support Monolog Financially
Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek).
Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.
## Third Party Packages
Third party handlers, formatters and processors are
[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
can also add your own there if you publish one.
## About
### Requirements
- Monolog `^2.0` works with PHP 7.2 or above, use Monolog `^1.25` for PHP 5.3+ support.
### Support
Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes.
### Submitting bugs and feature requests
Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)
### Framework Integrations
- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
can be used very easily with Monolog since it implements the interface.
- [Symfony](http://symfony.com) comes out of the box with Monolog.
- [Laravel](http://laravel.com/) comes out of the box with Monolog.
- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog.
- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog.
- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.
- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer.
- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog.
- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog.
- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions.
- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.
- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog.
- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog.
- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins.
- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog.
- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog.
- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module.
- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension.
- [Magento](https://magento.com/) comes out of the box with Monolog.
### Author
Jordi Boggiano - <j.boggiano@seld.be> - <http://twitter.com/seldaek><br />
See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project.
### License
Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
### Acknowledgements
This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/)
library, although most concepts have been adjusted to fit to the PHP world.

View file

@ -1,72 +0,0 @@
### 2.0.0
- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2`
install of Monolog when writing integration code.
- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`)
methods as well as `emerg`, `crit`, `err` and `warn`.
- DateTime are now formatted with a timezone and microseconds (unless disabled).
Various formatters and log output might be affected, which may mess with log parsing
in some cases.
- The `datetime` in every record array is now a DateTimeImmutable, not that you
should have been modifying these anyway.
- The timezone is now set per Logger instance and not statically, either
via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone
should be converted.
- `HandlerInterface` has been split off and two new interfaces now exist for
more granular controls: `ProcessableHandlerInterface` and
`FormattableHandlerInterface`. Handlers not extending `AbstractHandler`
should make sure to implement the relevant interfaces.
- `HandlerInterface` now requires the `close` method to be implemented. This
only impacts you if you implement the interface yourself, but you can extend
the new `Monolog\Handler\Handler` base class too.
- There is no more default handler configured on empty Logger instances, if
you were relying on that you will not get any output anymore, make sure to
configure the handler you need.
#### LogglyFormatter
- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly.
#### AmqpHandler
- Log levels are not shortened to 4 characters anymore. e.g. a warning record
will be sent using the `warning.channel` routing key instead of `warn.channel`
as in 1.x.
- The exchange name does not default to 'log' anymore, and it is completely ignored
now for the AMQP extension users. Only PHPAmqpLib uses it if provided.
#### RotatingFileHandler
- The file name format must now contain `{date}` and the date format must be set
to one of the predefined FILE_PER_* constants to avoid issues with file rotation.
See `setFilenameFormat`.
#### LogstashFormatter
- Removed Logstash V0 support
- Context/extra prefix has been removed in favor of letting users configure the exact key being sent
- Context/extra data are now sent as an object instead of single keys
#### HipChatHandler
- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead
#### SlackbotHandler
- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead
#### RavenHandler
- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead
#### ElasticSearchHandler
- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been
renamed to ElasticaHandler and the new one added as ElasticsearchHandler.

View file

@ -1,81 +0,0 @@
{
"name": "monolog/monolog",
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"keywords": ["log", "logging", "psr-3"],
"homepage": "https://github.com/Seldaek/monolog",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"ext-json": "*",
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^0.12.91",
"phpunit/phpunit": "^8.5.14",
"predis/predis": "^1.1 || ^2.0",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-openssl": "Required to send log messages using SSL"
},
"autoload": {
"psr-4": {"Monolog\\": "src/Monolog"}
},
"autoload-dev": {
"psr-4": {"Monolog\\": "tests/Monolog"}
},
"provide": {
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
}
},
"scripts": {
"test": "@php vendor/bin/phpunit",
"phpstan": "@php vendor/bin/phpstan analyse"
},
"config": {
"lock": false,
"sort-packages": true,
"platform-check": false,
"allow-plugins": {
"composer/package-versions-deprecated": true
}
}
}

View file

@ -1,46 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Attribute;
/**
* A reusable attribute to help configure a class or a method as a processor.
*
* Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer.
*
* Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if
* needed and manually pushed to the loggers and to the processable handlers.
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AsMonologProcessor
{
/** @var string|null */
public $channel = null;
/** @var string|null */
public $handler = null;
/** @var string|null */
public $method = null;
/**
* @param string|null $channel The logging channel the processor should be pushed to.
* @param string|null $handler The handler the processor should be pushed to.
* @param string|null $method The method that processes the records (if the attribute is used at the class level).
*/
public function __construct(
?string $channel = null,
?string $handler = null,
?string $method = null
) {
$this->channel = $channel;
$this->handler = $handler;
$this->method = $method;
}
}

View file

@ -1,49 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use DateTimeZone;
/**
* Overrides default json encoding of date time objects
*
* @author Menno Holtkamp
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
{
/**
* @var bool
*/
private $useMicroseconds;
public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null)
{
$this->useMicroseconds = $useMicroseconds;
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();
}
}

View file

@ -1,307 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Monolog error handler
*
* A facility to enable logging of runtime errors, exceptions and fatal errors.
*
* Quick setup: <code>ErrorHandler::register($logger);</code>
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ErrorHandler
{
/** @var LoggerInterface */
private $logger;
/** @var ?callable */
private $previousExceptionHandler = null;
/** @var array<class-string, LogLevel::*> an array of class name to LogLevel::* constant mapping */
private $uncaughtExceptionLevelMap = [];
/** @var callable|true|null */
private $previousErrorHandler = null;
/** @var array<int, LogLevel::*> an array of E_* constant to LogLevel::* constant mapping */
private $errorLevelMap = [];
/** @var bool */
private $handleOnlyReportedErrors = true;
/** @var bool */
private $hasFatalErrorHandler = false;
/** @var LogLevel::* */
private $fatalLevel = LogLevel::ALERT;
/** @var ?string */
private $reservedMemory = null;
/** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */
private $lastFatalData = null;
/** @var int[] */
private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Registers a new ErrorHandler for a given Logger
*
* By default it will handle errors, exceptions and fatal errors
*
* @param LoggerInterface $logger
* @param array<int, LogLevel::*>|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
* @param array<class-string, LogLevel::*>|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
* @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
* @return ErrorHandler
*/
public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self
{
/** @phpstan-ignore-next-line */
$handler = new static($logger);
if ($errorLevelMap !== false) {
$handler->registerErrorHandler($errorLevelMap);
}
if ($exceptionLevelMap !== false) {
$handler->registerExceptionHandler($exceptionLevelMap);
}
if ($fatalLevel !== false) {
$handler->registerFatalHandler($fatalLevel);
}
return $handler;
}
/**
* @param array<class-string, LogLevel::*> $levelMap an array of class name to LogLevel::* constant mapping
* @return $this
*/
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;
}
/**
* @param array<int, LogLevel::*> $levelMap an array of E_* constant to LogLevel::* constant mapping
* @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;
}
/**
* @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT
* @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done
*/
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;
}
/**
* @return array<class-string, LogLevel::*>
*/
protected function defaultExceptionLevelMap(): array
{
return [
'ParseError' => LogLevel::CRITICAL,
'Throwable' => LogLevel::ERROR,
];
}
/**
* @return array<int, LogLevel::*>
*/
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,
];
}
/**
* @phpstan-return never
*/
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);
}
/**
* @private
*
* @param mixed[] $context
*/
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;
}
/**
* @private
*/
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();
}
}
}
}
/**
* @param int $code
*/
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';
}
}

View file

@ -1,83 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Formats a log message according to the ChromePHP array format
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPFormatter implements FormatterInterface
{
/**
* Translates Monolog log levels to Wildfire levels.
*
* @var array<int, 'log'|'info'|'warn'|'error'>
*/
private $logLevels = [
Logger::DEBUG => 'log',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
];
/**
* {@inheritDoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$backtrace = 'unknown';
if (isset($record['extra']['file'], $record['extra']['line'])) {
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
unset($record['extra']['file'], $record['extra']['line']);
}
$message = ['message' => $record['message']];
if ($record['context']) {
$message['context'] = $record['context'];
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
}
if (count($message) === 1) {
$message = reset($message);
}
return [
$record['channel'],
$message,
$backtrace,
$this->logLevels[$record['level']],
];
}
/**
* {@inheritDoc}
*/
public function formatBatch(array $records)
{
$formatted = [];
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
}

View file

@ -1,89 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Elastica\Document;
/**
* Format a log message into an Elastica Document
*
* @author Jelle Vink <jelle.vink@gmail.com>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class ElasticaFormatter extends NormalizerFormatter
{
/**
* @var string Elastic search index name
*/
protected $index;
/**
* @var ?string Elastic search document type
*/
protected $type;
/**
* @param string $index Elastic Search index name
* @param ?string $type Elastic Search document type, deprecated as of Elastica 7
*/
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');
$this->index = $index;
$this->type = $type;
}
/**
* {@inheritDoc}
*/
public function format(array $record)
{
$record = parent::format($record);
return $this->getDocument($record);
}
public function getIndex(): string
{
return $this->index;
}
/**
* @deprecated since Elastica 7 type has no effect
*/
public function getType(): string
{
/** @phpstan-ignore-next-line */
return $this->type;
}
/**
* Convert a log message into an Elastica Document
*
* @phpstan-param Record $record
*/
protected function getDocument(array $record): Document
{
$document = new Document();
$document->setData($record);
if (method_exists($document, 'setType')) {
/** @phpstan-ignore-next-line */
$document->setType($this->type);
}
$document->setIndex($this->index);
return $document;
}
}

View file

@ -1,89 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use DateTimeInterface;
/**
* Format a log message into an Elasticsearch record
*
* @author Avtandil Kikabidze <akalongman@gmail.com>
*/
class ElasticsearchFormatter extends NormalizerFormatter
{
/**
* @var string Elasticsearch index name
*/
protected $index;
/**
* @var string Elasticsearch record type
*/
protected $type;
/**
* @param string $index Elasticsearch index name
* @param string $type Elasticsearch record type
*/
public function __construct(string $index, string $type)
{
// Elasticsearch requires an ISO 8601 format date with optional millisecond precision.
parent::__construct(DateTimeInterface::ISO8601);
$this->index = $index;
$this->type = $type;
}
/**
* {@inheritDoc}
*/
public function format(array $record)
{
$record = parent::format($record);
return $this->getDocument($record);
}
/**
* Getter index
*
* @return string
*/
public function getIndex(): string
{
return $this->index;
}
/**
* Getter type
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Convert a log message into an Elasticsearch record
*
* @param mixed[] $record Log message
* @return mixed[]
*/
protected function getDocument(array $record): array
{
$record['_index'] = $this->index;
$record['_type'] = $this->type;
return $record;
}
}

View file

@ -1,112 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* formats the record to be used in the FlowdockHandler
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
* @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4
*/
class FlowdockFormatter implements FormatterInterface
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $sourceEmail;
public function __construct(string $source, string $sourceEmail)
{
$this->source = $source;
$this->sourceEmail = $sourceEmail;
}
/**
* {@inheritDoc}
*
* @return mixed[]
*/
public function format(array $record): array
{
$tags = [
'#logs',
'#' . strtolower($record['level_name']),
'#' . $record['channel'],
];
foreach ($record['extra'] as $value) {
$tags[] = '#' . $value;
}
$subject = sprintf(
'in %s: %s - %s',
$this->source,
$record['level_name'],
$this->getShortMessage($record['message'])
);
$record['flowdock'] = [
'source' => $this->source,
'from_address' => $this->sourceEmail,
'subject' => $subject,
'content' => $record['message'],
'tags' => $tags,
'project' => $this->source,
];
return $record;
}
/**
* {@inheritDoc}
*
* @return mixed[][]
*/
public function formatBatch(array $records): array
{
$formatted = [];
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
public function getShortMessage(string $message): string
{
static $hasMbString;
if (null === $hasMbString) {
$hasMbString = function_exists('mb_strlen');
}
$maxLength = 45;
if ($hasMbString) {
if (mb_strlen($message, 'UTF-8') > $maxLength) {
$message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
}
} else {
if (strlen($message) > $maxLength) {
$message = substr($message, 0, $maxLength - 4) . ' ...';
}
}
return $message;
}
}

View file

@ -1,88 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Utils;
/**
* Class FluentdFormatter
*
* Serializes a log message to Fluentd unix socket protocol
*
* Fluentd config:
*
* <source>
* type unix
* path /var/run/td-agent/td-agent.sock
* </source>
*
* Monolog setup:
*
* $logger = new Monolog\Logger('fluent.tag');
* $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
* $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
* $logger->pushHandler($fluentHandler);
*
* @author Andrius Putna <fordnox@gmail.com>
*/
class FluentdFormatter implements FormatterInterface
{
/**
* @var bool $levelTag should message level be a part of the fluentd tag
*/
protected $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 = $levelTag;
}
public function isUsingLevelsInTag(): bool
{
return $this->levelTag;
}
public function format(array $record): string
{
$tag = $record['channel'];
if ($this->levelTag) {
$tag .= '.' . strtolower($record['level_name']);
}
$message = [
'message' => $record['message'],
'context' => $record['context'],
'extra' => $record['extra'],
];
if (!$this->levelTag) {
$message['level'] = $record['level'];
$message['level_name'] = $record['level_name'];
}
return Utils::jsonEncode([$tag, $record['datetime']->getTimestamp(), $message]);
}
public function formatBatch(array $records): string
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
}

View file

@ -1,42 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Interface for formatters
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
interface FormatterInterface
{
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*
* @phpstan-param Record $record
*/
public function format(array $record);
/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*
* @phpstan-param Record[] $records
*/
public function formatBatch(array $records);
}

View file

@ -1,175 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Gelf\Message;
use Monolog\Utils;
/**
* Serializes a log message to GELF
* @see http://docs.graylog.org/en/latest/pages/gelf.html
*
* @author Matt Lehner <mlehner@gmail.com>
*
* @phpstan-import-type Level from \Monolog\Logger
*/
class GelfMessageFormatter extends NormalizerFormatter
{
protected const DEFAULT_MAX_LENGTH = 32766;
/**
* @var string the name of the system for the Gelf log message
*/
protected $systemName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* @var int max length per field
*/
protected $maxLength;
/**
* @var int
*/
private $gelfVersion = 2;
/**
* Translates Monolog log levels to Graylog2 log priorities.
*
* @var array<int, int>
*
* @phpstan-var array<Level, int>
*/
private $logLevels = [
Logger::DEBUG => 7,
Logger::INFO => 6,
Logger::NOTICE => 5,
Logger::WARNING => 4,
Logger::ERROR => 3,
Logger::CRITICAL => 2,
Logger::ALERT => 1,
Logger::EMERGENCY => 0,
];
public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null)
{
if (!class_exists(Message::class)) {
throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter');
}
parent::__construct('U.u');
$this->systemName = (is_null($systemName) || $systemName === '') ? (string) gethostname() : $systemName;
$this->extraPrefix = is_null($extraPrefix) ? '' : $extraPrefix;
$this->contextPrefix = $contextPrefix;
$this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength;
if (method_exists(Message::class, 'setFacility')) {
$this->gelfVersion = 1;
}
}
/**
* {@inheritDoc}
*/
public function format(array $record): Message
{
$context = $extra = [];
if (isset($record['context'])) {
/** @var mixed[] $context */
$context = parent::normalize($record['context']);
}
if (isset($record['extra'])) {
/** @var mixed[] $extra */
$extra = parent::normalize($record['extra']);
}
if (!isset($record['datetime'], $record['message'], $record['level'])) {
throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
}
$message = new Message();
$message
->setTimestamp($record['datetime'])
->setShortMessage((string) $record['message'])
->setHost($this->systemName)
->setLevel($this->logLevels[$record['level']]);
// message length + system name length + 200 for padding / metadata
$len = 200 + strlen((string) $record['message']) + strlen($this->systemName);
if ($len > $this->maxLength) {
$message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength));
}
if ($this->gelfVersion === 1) {
if (isset($record['channel'])) {
$message->setFacility($record['channel']);
}
if (isset($extra['line'])) {
$message->setLine($extra['line']);
unset($extra['line']);
}
if (isset($extra['file'])) {
$message->setFile($extra['file']);
unset($extra['file']);
}
} else {
$message->setAdditional('facility', $record['channel']);
}
foreach ($extra as $key => $val) {
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
$len = strlen($this->extraPrefix . $key . $val);
if ($len > $this->maxLength) {
$message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength));
continue;
}
$message->setAdditional($this->extraPrefix . $key, $val);
}
foreach ($context as $key => $val) {
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
$len = strlen($this->contextPrefix . $key . $val);
if ($len > $this->maxLength) {
$message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength));
continue;
}
$message->setAdditional($this->contextPrefix . $key, $val);
}
if ($this->gelfVersion === 1) {
/** @phpstan-ignore-next-line */
if (null === $message->getFile() && isset($context['exception']['file'])) {
if (preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) {
$message->setFile($matches[1]);
$message->setLine($matches[2]);
}
}
}
return $message;
}
}

View file

@ -1,40 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use DateTimeInterface;
use Monolog\LogRecord;
/**
* Encodes message information into JSON in a format compatible with Cloud logging.
*
* @see https://cloud.google.com/logging/docs/structured-logging
* @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
*
* @author Luís Cobucci <lcobucci@gmail.com>
*/
final class GoogleCloudLoggingFormatter extends JsonFormatter
{
/** {@inheritdoc} **/
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);
}
}

View file

@ -1,142 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Monolog\Utils;
/**
* Formats incoming records into an HTML table
*
* This is especially useful for html email logging
*
* @author Tiago Brito <tlfbrito@gmail.com>
*/
class HtmlFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to html color priorities.
*
* @var array<int, string>
*/
protected $logLevels = [
Logger::DEBUG => '#CCCCCC',
Logger::INFO => '#28A745',
Logger::NOTICE => '#17A2B8',
Logger::WARNING => '#FFC107',
Logger::ERROR => '#FD7E14',
Logger::CRITICAL => '#DC3545',
Logger::ALERT => '#821722',
Logger::EMERGENCY => '#000000',
];
/**
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct(?string $dateFormat = null)
{
parent::__construct($dateFormat);
}
/**
* Creates an HTML table row
*
* @param string $th Row header content
* @param string $td Row standard cell content
* @param bool $escapeTd false if td content must not be html escaped
*/
protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string
{
$th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
if ($escapeTd) {
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
}
return "<tr style=\"padding: 4px;text-align: left;\">\n<th style=\"vertical-align: top;background: #ccc;color: #000\" width=\"100\">$th:</th>\n<td style=\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\">".$td."</td>\n</tr>";
}
/**
* Create a HTML h1 tag
*
* @param string $title Text to be in the h1
* @param int $level Error level
* @return string
*/
protected function addTitle(string $title, int $level): string
{
$title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
}
/**
* Formats a log record.
*
* @return string The formatted record
*/
public function format(array $record): string
{
$output = $this->addTitle($record['level_name'], $record['level']);
$output .= '<table cellspacing="1" width="100%" class="monolog-output">';
$output .= $this->addRow('Message', (string) $record['message']);
$output .= $this->addRow('Time', $this->formatDate($record['datetime']));
$output .= $this->addRow('Channel', $record['channel']);
if ($record['context']) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['context'] as $key => $value) {
$embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Context', $embeddedTable, false);
}
if ($record['extra']) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['extra'] as $key => $value) {
$embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Extra', $embeddedTable, false);
}
return $output.'</table>';
}
/**
* Formats a set of log records.
*
* @return string The formatted set of records
*/
public function formatBatch(array $records): string
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
/**
* @param mixed $data
*/
protected function convertToString($data): string
{
if (null === $data || is_scalar($data)) {
return (string) $data;
}
$data = $this->normalize($data);
return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true);
}
}

View file

@ -1,224 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Throwable;
/**
* Encodes whatever record data is passed to it as json
*
* This can be useful to log to databases or remote APIs
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class JsonFormatter extends NormalizerFormatter
{
public const BATCH_MODE_JSON = 1;
public const BATCH_MODE_NEWLINES = 2;
/** @var self::BATCH_MODE_* */
protected $batchMode;
/** @var bool */
protected $appendNewline;
/** @var bool */
protected $ignoreEmptyContextAndExtra;
/** @var bool */
protected $includeStacktraces = false;
/**
* @param self::BATCH_MODE_* $batchMode
*/
public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)
{
$this->batchMode = $batchMode;
$this->appendNewline = $appendNewline;
$this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
$this->includeStacktraces = $includeStacktraces;
parent::__construct();
}
/**
* The batch mode option configures the formatting style for
* multiple records. By default, multiple records will be
* formatted as a JSON-encoded array. However, for
* compatibility with some API endpoints, alternative styles
* are available.
*/
public function getBatchMode(): int
{
return $this->batchMode;
}
/**
* True if newlines are appended to every formatted record
*/
public function isAppendingNewlines(): bool
{
return $this->appendNewline;
}
/**
* {@inheritDoc}
*/
public function format(array $record): string
{
$normalized = $this->normalize($record);
if (isset($normalized['context']) && $normalized['context'] === []) {
if ($this->ignoreEmptyContextAndExtra) {
unset($normalized['context']);
} else {
$normalized['context'] = new \stdClass;
}
}
if (isset($normalized['extra']) && $normalized['extra'] === []) {
if ($this->ignoreEmptyContextAndExtra) {
unset($normalized['extra']);
} else {
$normalized['extra'] = new \stdClass;
}
}
return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : '');
}
/**
* {@inheritDoc}
*/
public function formatBatch(array $records): string
{
switch ($this->batchMode) {
case static::BATCH_MODE_NEWLINES:
return $this->formatBatchNewlines($records);
case static::BATCH_MODE_JSON:
default:
return $this->formatBatchJson($records);
}
}
/**
* @return self
*/
public function includeStacktraces(bool $include = true): self
{
$this->includeStacktraces = $include;
return $this;
}
/**
* Return a JSON-encoded array of records.
*
* @phpstan-param Record[] $records
*/
protected function formatBatchJson(array $records): string
{
return $this->toJson($this->normalize($records), true);
}
/**
* Use new lines to separate records instead of a
* JSON-encoded array.
*
* @phpstan-param Record[] $records
*/
protected function formatBatchNewlines(array $records): string
{
$instance = $this;
$oldNewline = $this->appendNewline;
$this->appendNewline = false;
array_walk($records, function (&$value, $key) use ($instance) {
$value = $instance->format($value);
});
$this->appendNewline = $oldNewline;
return implode("\n", $records);
}
/**
* Normalizes given $data.
*
* @param mixed $data
*
* @return mixed
*/
protected function normalize($data, int $depth = 0)
{
if ($depth > $this->maxNormalizeDepth) {
return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';
}
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 (is_object($data)) {
if ($data instanceof \DateTimeInterface) {
return $this->formatDate($data);
}
if ($data instanceof Throwable) {
return $this->normalizeException($data, $depth);
}
// if the object has specific json serializability we want to make sure we skip the __toString treatment below
if ($data instanceof \JsonSerializable) {
return $data;
}
if (method_exists($data, '__toString')) {
return $data->__toString();
}
return $data;
}
if (is_resource($data)) {
return parent::normalize($data);
}
return $data;
}
/**
* Normalizes given exception with or without its own stack trace based on
* `includeStacktraces` property.
*
* {@inheritDoc}
*/
protected function normalizeException(Throwable $e, int $depth = 0): array
{
$data = parent::normalizeException($e, $depth);
if (!$this->includeStacktraces) {
unset($data['trace']);
}
return $data;
}
}

View file

@ -1,246 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Utils;
/**
* Formats incoming records into a one-line string
*
* This is especially useful for logging to files
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
class LineFormatter extends NormalizerFormatter
{
public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
/** @var string */
protected $format;
/** @var bool */
protected $allowInlineLineBreaks;
/** @var bool */
protected $ignoreEmptyContextAndExtra;
/** @var bool */
protected $includeStacktraces;
/** @var ?callable */
protected $stacktracesParser;
/**
* @param string|null $format The format of the message
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
* @param bool $ignoreEmptyContextAndExtra
*/
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;
}
/**
* {@inheritDoc}
*/
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;
}
/**
* @param mixed $value
*/
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;
}
/**
* @param mixed $data
*/
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))));
}
}

View file

@ -1,45 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Encodes message information into JSON in a format compatible with Loggly.
*
* @author Adam Pancutt <adam@pancutt.com>
*/
class LogglyFormatter extends JsonFormatter
{
/**
* Overrides the default batch mode to new lines for compatibility with the
* Loggly bulk API.
*/
public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false)
{
parent::__construct($batchMode, $appendNewline);
}
/**
* Appends the 'timestamp' parameter for indexing by Loggly.
*
* @see https://www.loggly.com/docs/automated-parsing/#json
* @see \Monolog\Formatter\JsonFormatter::format()
*/
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");
unset($record["datetime"]);
}
return parent::format($record);
}
}

View file

@ -1,66 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Encodes message information into JSON in a format compatible with Logmatic.
*
* @author Julien Breux <julien.breux@gmail.com>
*/
class LogmaticFormatter extends JsonFormatter
{
protected const MARKERS = ["sourcecode", "php"];
/**
* @var string
*/
protected $hostname = '';
/**
* @var string
*/
protected $appname = '';
public function setHostname(string $hostname): self
{
$this->hostname = $hostname;
return $this;
}
public function setAppname(string $appname): self
{
$this->appname = $appname;
return $this;
}
/**
* Appends the 'hostname' and 'appname' parameter for indexing by Logmatic.
*
* @see http://doc.logmatic.io/docs/basics-to-send-data
* @see \Monolog\Formatter\JsonFormatter::format()
*/
public function format(array $record): string
{
if (!empty($this->hostname)) {
$record["hostname"] = $this->hostname;
}
if (!empty($this->appname)) {
$record["appname"] = $this->appname;
}
$record["@marker"] = static::MARKERS;
return parent::format($record);
}
}

View file

@ -1,101 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Serializes a log message to Logstash Event Format
*
* @see https://www.elastic.co/products/logstash
* @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java
*
* @author Tim Mower <timothy.mower@gmail.com>
*/
class LogstashFormatter extends NormalizerFormatter
{
/**
* @var string the name of the system for the Logstash log message, used to fill the @source field
*/
protected $systemName;
/**
* @var string an application name for the Logstash log message, used to fill the @type field
*/
protected $applicationName;
/**
* @var string the key for 'extra' fields from the Monolog record
*/
protected $extraKey;
/**
* @var string the key for 'context' fields from the Monolog record
*/
protected $contextKey;
/**
* @param string $applicationName The application that sends the data, used as the "type" field of logstash
* @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
* @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra
* @param string $contextKey The key for context keys inside logstash "fields", defaults to context
*/
public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context')
{
// logstash requires a ISO 8601 format date with optional millisecond precision.
parent::__construct('Y-m-d\TH:i:s.uP');
$this->systemName = $systemName === null ? (string) gethostname() : $systemName;
$this->applicationName = $applicationName;
$this->extraKey = $extraKey;
$this->contextKey = $contextKey;
}
/**
* {@inheritDoc}
*/
public function format(array $record): string
{
$record = parent::format($record);
if (empty($record['datetime'])) {
$record['datetime'] = gmdate('c');
}
$message = [
'@timestamp' => $record['datetime'],
'@version' => 1,
'host' => $this->systemName,
];
if (isset($record['message'])) {
$message['message'] = $record['message'];
}
if (isset($record['channel'])) {
$message['type'] = $record['channel'];
$message['channel'] = $record['channel'];
}
if (isset($record['level_name'])) {
$message['level'] = $record['level_name'];
}
if (isset($record['level'])) {
$message['monolog_level'] = $record['level'];
}
if ($this->applicationName) {
$message['type'] = $this->applicationName;
}
if (!empty($record['extra'])) {
$message[$this->extraKey] = $record['extra'];
}
if (!empty($record['context'])) {
$message[$this->contextKey] = $record['context'];
}
return $this->toJson($message) . "\n";
}
}

View file

@ -1,162 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use MongoDB\BSON\Type;
use MongoDB\BSON\UTCDateTime;
use Monolog\Utils;
/**
* Formats a record for use with the MongoDBHandler.
*
* @author Florian Plattner <me@florianplattner.de>
*/
class MongoDBFormatter implements FormatterInterface
{
/** @var bool */
private $exceptionTraceAsString;
/** @var int */
private $maxNestingLevel;
/** @var bool */
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(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true)
{
$this->maxNestingLevel = max($maxNestingLevel, 0);
$this->exceptionTraceAsString = $exceptionTraceAsString;
$this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<=');
}
/**
* {@inheritDoc}
*
* @return mixed[]
*/
public function format(array $record): array
{
/** @var mixed[] $res */
$res = $this->formatArray($record);
return $res;
}
/**
* {@inheritDoc}
*
* @return array<mixed[]>
*/
public function formatBatch(array $records): array
{
$formatted = [];
foreach ($records as $key => $record) {
$formatted[$key] = $this->format($record);
}
return $formatted;
}
/**
* @param mixed[] $array
* @return mixed[]|string Array except when max nesting level is reached then a string "[...]"
*/
protected function formatArray(array $array, int $nestingLevel = 0)
{
if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) {
return '[...]';
}
foreach ($array as $name => $value) {
if ($value instanceof \DateTimeInterface) {
$array[$name] = $this->formatDate($value, $nestingLevel + 1);
} elseif ($value instanceof \Throwable) {
$array[$name] = $this->formatException($value, $nestingLevel + 1);
} elseif (is_array($value)) {
$array[$name] = $this->formatArray($value, $nestingLevel + 1);
} elseif (is_object($value) && !$value instanceof Type) {
$array[$name] = $this->formatObject($value, $nestingLevel + 1);
}
}
return $array;
}
/**
* @param mixed $value
* @return mixed[]|string
*/
protected function formatObject($value, int $nestingLevel)
{
$objectVars = get_object_vars($value);
$objectVars['class'] = Utils::getClass($value);
return $this->formatArray($objectVars, $nestingLevel);
}
/**
* @return mixed[]|string
*/
protected function formatException(\Throwable $exception, int $nestingLevel)
{
$formattedException = [
'class' => Utils::getClass($exception),
'message' => $exception->getMessage(),
'code' => (int) $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
];
if ($this->exceptionTraceAsString === true) {
$formattedException['trace'] = $exception->getTraceAsString();
} else {
$formattedException['trace'] = $exception->getTrace();
}
return $this->formatArray($formattedException, $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) floor(((float) $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(((float) $value->format('U.u')) * 1000);
$milliseconds = (PHP_INT_SIZE == 8) //64-bit OS?
? (int) $milliseconds
: (string) $milliseconds;
// @phpstan-ignore-next-line
return new UTCDateTime($milliseconds);
}
}

View file

@ -1,290 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\DateTimeImmutable;
use Monolog\Utils;
use Throwable;
/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class NormalizerFormatter implements FormatterInterface
{
public const SIMPLE_DATE = "Y-m-d\TH:i:sP";
/** @var string */
protected $dateFormat;
/** @var int */
protected $maxNormalizeDepth = 9;
/** @var int */
protected $maxNormalizeItemCount = 1000;
/** @var int */
private $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;
/**
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct(?string $dateFormat = null)
{
$this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat;
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
}
}
/**
* {@inheritDoc}
*
* @param mixed[] $record
*/
public function format(array $record)
{
return $this->normalize($record);
}
/**
* {@inheritDoc}
*/
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;
}
/**
* The maximum number of normalization levels to go through
*/
public function getMaxNormalizeDepth(): int
{
return $this->maxNormalizeDepth;
}
public function setMaxNormalizeDepth(int $maxNormalizeDepth): self
{
$this->maxNormalizeDepth = $maxNormalizeDepth;
return $this;
}
/**
* The maximum number of items to normalize per level
*/
public function getMaxNormalizeItemCount(): int
{
return $this->maxNormalizeItemCount;
}
public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self
{
$this->maxNormalizeItemCount = $maxNormalizeItemCount;
return $this;
}
/**
* Enables `json_encode` pretty print.
*/
public function setJsonPrettyPrint(bool $enable): self
{
if ($enable) {
$this->jsonEncodeOptions |= JSON_PRETTY_PRINT;
} else {
$this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT;
}
return $this;
}
/**
* @param mixed $data
* @return null|scalar|array<array|scalar|null>
*/
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) {
/** @var null|scalar|array<array|scalar|null> $value */
$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')) {
/** @var string $value */
$value = $data->__toString();
} else {
// the rest is normalized by json encoding and decoding it
/** @var null|scalar|array<array|scalar|null> $value */
$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).')]';
}
/**
* @return mixed[]
*/
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;
}
/**
* Return the JSON representation of a value
*
* @param mixed $data
* @throws \RuntimeException if encoding fails and errors are not ignored
* @return string if encoding fails and ignoreErrors is true 'null' is returned
*/
protected function toJson($data, bool $ignoreErrors = false): string
{
return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors);
}
/**
* @return string
*/
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;
}
}

View file

@ -1,51 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats data into an associative array of scalar values.
* Objects and arrays will be JSON encoded.
*
* @author Andrew Lawson <adlawson@gmail.com>
*/
class ScalarFormatter extends NormalizerFormatter
{
/**
* {@inheritDoc}
*
* @phpstan-return array<string, scalar|null> $record
*/
public function format(array $record): array
{
$result = [];
foreach ($record as $key => $value) {
$result[$key] = $this->normalizeValue($value);
}
return $result;
}
/**
* @param mixed $value
* @return scalar|null
*/
protected function normalizeValue($value)
{
$normalized = $this->normalize($value);
if (is_array($normalized)) {
return $this->toJson($normalized, true);
}
return $normalized;
}
}

View file

@ -1,139 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Serializes a log message according to Wildfire's header requirements
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Kirill chEbba Chebunin <iam@chebba.org>
*
* @phpstan-import-type Level from \Monolog\Logger
*/
class WildfireFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to Wildfire levels.
*
* @var array<Level, string>
*/
private $logLevels = [
Logger::DEBUG => 'LOG',
Logger::INFO => 'INFO',
Logger::NOTICE => 'INFO',
Logger::WARNING => 'WARN',
Logger::ERROR => 'ERROR',
Logger::CRITICAL => 'ERROR',
Logger::ALERT => 'ERROR',
Logger::EMERGENCY => 'ERROR',
];
/**
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct(?string $dateFormat = null)
{
parent::__construct($dateFormat);
// http headers do not like non-ISO-8559-1 characters
$this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE);
}
/**
* {@inheritDoc}
*
* @return string
*/
public function format(array $record): string
{
// Retrieve the line and file if set and remove them from the formatted extra
$file = $line = '';
if (isset($record['extra']['file'])) {
$file = $record['extra']['file'];
unset($record['extra']['file']);
}
if (isset($record['extra']['line'])) {
$line = $record['extra']['line'];
unset($record['extra']['line']);
}
/** @var mixed[] $record */
$record = $this->normalize($record);
$message = ['message' => $record['message']];
$handleError = false;
if ($record['context']) {
$message['context'] = $record['context'];
$handleError = true;
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
$handleError = true;
}
if (count($message) === 1) {
$message = reset($message);
}
if (isset($record['context']['table'])) {
$type = 'TABLE';
$label = $record['channel'] .': '. $record['message'];
$message = $record['context']['table'];
} else {
$type = $this->logLevels[$record['level']];
$label = $record['channel'];
}
// Create JSON object describing the appearance of the message in the console
$json = $this->toJson([
[
'Type' => $type,
'File' => $file,
'Line' => $line,
'Label' => $label,
],
$message,
], $handleError);
// The message itself is a serialization of the above JSON object + it's length
return sprintf(
'%d|%s|',
strlen($json),
$json
);
}
/**
* {@inheritDoc}
*
* @phpstan-return never
*/
public function formatBatch(array $records)
{
throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
}
/**
* {@inheritDoc}
*
* @return null|scalar|array<array|scalar|null>|object
*/
protected function normalize($data, int $depth = 0)
{
if (is_object($data) && !$data instanceof \DateTimeInterface) {
return $data;
}
return parent::normalize($data, $depth);
}
}

View file

@ -1,112 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\ResettableInterface;
use Psr\Log\LogLevel;
/**
* Base Handler class providing basic level/bubble support
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Level from \Monolog\Logger
* @phpstan-import-type LevelName from \Monolog\Logger
*/
abstract class AbstractHandler extends Handler implements ResettableInterface
{
/**
* @var int
* @phpstan-var Level
*/
protected $level = Logger::DEBUG;
/** @var bool */
protected $bubble = true;
/**
* @param int|string $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*
* @phpstan-param Level|LevelName|LogLevel::* $level
*/
public function __construct($level = Logger::DEBUG, bool $bubble = true)
{
$this->setLevel($level);
$this->bubble = $bubble;
}
/**
* {@inheritDoc}
*/
public function isHandling(array $record): bool
{
return $record['level'] >= $this->level;
}
/**
* Sets minimum logging level at which this handler will be triggered.
*
* @param Level|LevelName|LogLevel::* $level Level or level name
* @return self
*/
public function setLevel($level): self
{
$this->level = Logger::toMonologLevel($level);
return $this;
}
/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return int
*
* @phpstan-return Level
*/
public function getLevel(): int
{
return $this->level;
}
/**
* Sets the bubbling behavior.
*
* @param bool $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
*/
public function setBubble(bool $bubble): self
{
$this->bubble = $bubble;
return $this;
}
/**
* Gets the bubbling behavior.
*
* @return bool true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function getBubble(): bool
{
return $this->bubble;
}
/**
* {@inheritDoc}
*/
public function reset()
{
}
}

View file

@ -1,69 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base Handler class providing the Handler structure, including processors and formatters
*
* Classes extending it should (in most cases) only implement write($record)
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*
* @phpstan-import-type LevelName from \Monolog\Logger
* @phpstan-import-type Level from \Monolog\Logger
* @phpstan-import-type Record from \Monolog\Logger
* @phpstan-type FormattedRecord array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[], formatted: mixed}
*/
abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
{
use ProcessableHandlerTrait;
use FormattableHandlerTrait;
/**
* {@inheritDoc}
*/
public function handle(array $record): bool
{
if (!$this->isHandling($record)) {
return false;
}
if ($this->processors) {
/** @var Record $record */
$record = $this->processRecord($record);
}
$record['formatted'] = $this->getFormatter()->format($record);
$this->write($record);
return false === $this->bubble;
}
/**
* Writes the record down to the log of the implementing handler
*
* @phpstan-param FormattedRecord $record
*/
abstract protected function write(array $record): void;
/**
* @return void
*/
public function reset()
{
parent::reset();
$this->resetProcessors();
}
}

View file

@ -1,106 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
/**
* Common syslog functionality
*
* @phpstan-import-type Level from \Monolog\Logger
*/
abstract class AbstractSyslogHandler extends AbstractProcessingHandler
{
/** @var int */
protected $facility;
/**
* Translates Monolog log levels to syslog log priorities.
* @var array
* @phpstan-var array<Level, int>
*/
protected $logLevels = [
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::NOTICE => LOG_NOTICE,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
Logger::EMERGENCY => LOG_EMERG,
];
/**
* List of valid log facility names.
* @var array<string, int>
*/
protected $facilities = [
'auth' => LOG_AUTH,
'authpriv' => LOG_AUTHPRIV,
'cron' => LOG_CRON,
'daemon' => LOG_DAEMON,
'kern' => LOG_KERN,
'lpr' => LOG_LPR,
'mail' => LOG_MAIL,
'news' => LOG_NEWS,
'syslog' => LOG_SYSLOG,
'user' => LOG_USER,
'uucp' => LOG_UUCP,
];
/**
* @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant
*/
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true)
{
parent::__construct($level, $bubble);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->facilities['local0'] = LOG_LOCAL0;
$this->facilities['local1'] = LOG_LOCAL1;
$this->facilities['local2'] = LOG_LOCAL2;
$this->facilities['local3'] = LOG_LOCAL3;
$this->facilities['local4'] = LOG_LOCAL4;
$this->facilities['local5'] = LOG_LOCAL5;
$this->facilities['local6'] = LOG_LOCAL6;
$this->facilities['local7'] = LOG_LOCAL7;
} else {
$this->facilities['local0'] = 128; // LOG_LOCAL0
$this->facilities['local1'] = 136; // LOG_LOCAL1
$this->facilities['local2'] = 144; // LOG_LOCAL2
$this->facilities['local3'] = 152; // LOG_LOCAL3
$this->facilities['local4'] = 160; // LOG_LOCAL4
$this->facilities['local5'] = 168; // LOG_LOCAL5
$this->facilities['local6'] = 176; // LOG_LOCAL6
$this->facilities['local7'] = 184; // LOG_LOCAL7
}
// convert textual description of facility to syslog constant
if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) {
$facility = $this->facilities[strtolower($facility)];
} elseif (!in_array($facility, array_values($this->facilities), true)) {
throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
}
$this->facility = $facility;
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
}
}

View file

@ -1,171 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\JsonFormatter;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;
use AMQPExchange;
/**
* @phpstan-import-type Record from \Monolog\Logger
*/
class AmqpHandler extends AbstractProcessingHandler
{
/**
* @var AMQPExchange|AMQPChannel $exchange
*/
protected $exchange;
/** @var array<string, mixed> */
private $extraAttributes = [];
/**
* @return array<string, mixed>
*/
public function getExtraAttributes(): array
{
return $this->extraAttributes;
}
/**
* Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension)
*
* @param array<string, mixed> $extraAttributes One of content_type, content_encoding,
* message_id, user_id, app_id, delivery_mode,
* priority, timestamp, expiration, type
* or reply_to, headers.
* @return AmqpHandler
*/
public function setExtraAttributes(array $extraAttributes): self
{
$this->extraAttributes = $extraAttributes;
return $this;
}
/**
* @var string
*/
protected $exchangeName;
/**
* @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
* @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only
*/
public function __construct($exchange, ?string $exchangeName = null, $level = Logger::DEBUG, bool $bubble = true)
{
if ($exchange instanceof AMQPChannel) {
$this->exchangeName = (string) $exchangeName;
} elseif (!$exchange instanceof AMQPExchange) {
throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required');
} elseif ($exchangeName) {
@trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED);
}
$this->exchange = $exchange;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$data = $record["formatted"];
$routingKey = $this->getRoutingKey($record);
if ($this->exchange instanceof AMQPExchange) {
$attributes = [
'delivery_mode' => 2,
'content_type' => 'application/json',
];
if ($this->extraAttributes) {
$attributes = array_merge($attributes, $this->extraAttributes);
}
$this->exchange->publish(
$data,
$routingKey,
0,
$attributes
);
} else {
$this->exchange->basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$routingKey
);
}
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records): void
{
if ($this->exchange instanceof AMQPExchange) {
parent::handleBatch($records);
return;
}
foreach ($records as $record) {
if (!$this->isHandling($record)) {
continue;
}
/** @var Record $record */
$record = $this->processRecord($record);
$data = $this->getFormatter()->format($record);
$this->exchange->batch_basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$this->getRoutingKey($record)
);
}
$this->exchange->publish_batch();
}
/**
* Gets the routing key for the AMQP exchange
*
* @phpstan-param Record $record
*/
protected function getRoutingKey(array $record): string
{
$routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']);
return strtolower($routingKey);
}
private function createAmqpMessage(string $data): AMQPMessage
{
$attributes = [
'delivery_mode' => 2,
'content_type' => 'application/json',
];
if ($this->extraAttributes) {
$attributes = array_merge($attributes, $this->extraAttributes);
}
return new AMQPMessage($data, $attributes);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
}
}

View file

@ -1,308 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Utils;
use Monolog\Logger;
use function count;
use function headers_list;
use function stripos;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Handler sending logs to browser's javascript console with no browser extension required
*
* @author Olivier Poitrey <rs@dailymotion.com>
*
* @phpstan-import-type FormattedRecord from AbstractProcessingHandler
*/
class BrowserConsoleHandler extends AbstractProcessingHandler
{
/** @var bool */
protected static $initialized = false;
/** @var FormattedRecord[] */
protected static $records = [];
protected const FORMAT_HTML = 'html';
protected const FORMAT_JS = 'js';
protected const FORMAT_UNKNOWN = 'unknown';
/**
* {@inheritDoc}
*
* Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
*
* Example of formatted string:
*
* You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
// Accumulate records
static::$records[] = $record;
// Register shutdown handler if not already done
if (!static::$initialized) {
static::$initialized = true;
$this->registerShutdownFunction();
}
}
/**
* Convert records to javascript console commands and send it to the browser.
* This method is automatically called on PHP shutdown if output is HTML or Javascript.
*/
public static function send(): void
{
$format = static::getResponseFormat();
if ($format === self::FORMAT_UNKNOWN) {
return;
}
if (count(static::$records)) {
if ($format === self::FORMAT_HTML) {
static::writeOutput('<script>' . static::generateScript() . '</script>');
} elseif ($format === self::FORMAT_JS) {
static::writeOutput(static::generateScript());
}
static::resetStatic();
}
}
public function close(): void
{
self::resetStatic();
}
public function reset()
{
parent::reset();
self::resetStatic();
}
/**
* Forget all logged records
*/
public static function resetStatic(): void
{
static::$records = [];
}
/**
* Wrapper for register_shutdown_function to allow overriding
*/
protected function registerShutdownFunction(): void
{
if (PHP_SAPI !== 'cli') {
register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']);
}
}
/**
* Wrapper for echo to allow overriding
*/
protected static function writeOutput(string $str): void
{
echo $str;
}
/**
* Checks the format of the response
*
* If Content-Type is set to application/javascript or text/javascript -> js
* If Content-Type is set to text/html, or is unset -> html
* If Content-Type is anything else -> unknown
*
* @return string One of 'js', 'html' or 'unknown'
* @phpstan-return self::FORMAT_*
*/
protected static function getResponseFormat(): string
{
// Check content type
foreach (headers_list() as $header) {
if (stripos($header, 'content-type:') === 0) {
return static::getResponseFormatFromContentType($header);
}
}
return self::FORMAT_HTML;
}
/**
* @return string One of 'js', 'html' or 'unknown'
* @phpstan-return self::FORMAT_*
*/
protected static function getResponseFormatFromContentType(string $contentType): string
{
// This handler only works with HTML and javascript outputs
// text/javascript is obsolete in favour of application/javascript, but still used
if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) {
return self::FORMAT_JS;
}
if (stripos($contentType, 'text/html') !== false) {
return self::FORMAT_HTML;
}
return self::FORMAT_UNKNOWN;
}
private static function generateScript(): string
{
$script = [];
foreach (static::$records as $record) {
$context = static::dump('Context', $record['context']);
$extra = static::dump('Extra', $record['extra']);
if (empty($context) && empty($extra)) {
$script[] = static::call_array(static::getConsoleMethodForLevel($record['level']), static::handleStyles($record['formatted']));
} else {
$script = array_merge(
$script,
[static::call_array('groupCollapsed', static::handleStyles($record['formatted']))],
$context,
$extra,
[static::call('groupEnd')]
);
}
}
return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
}
private static function getConsoleMethodForLevel(int $level): string
{
return [
Logger::DEBUG => 'debug',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
][$level] ?? 'log';
}
/**
* @return string[]
*/
private static function handleStyles(string $formatted): array
{
$args = [];
$format = '%c' . $formatted;
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
foreach (array_reverse($matches) as $match) {
$args[] = '"font-weight: normal"';
$args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
$pos = $match[0][1];
$format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0]));
}
$args[] = static::quote('font-weight: normal');
$args[] = static::quote($format);
return array_reverse($args);
}
private static function handleCustomStyles(string $style, string $string): string
{
static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'];
static $labels = [];
$style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) {
if (trim($m[1]) === 'autolabel') {
// Format the string as a label with consistent auto assigned background color
if (!isset($labels[$string])) {
$labels[$string] = $colors[count($labels) % count($colors)];
}
$color = $labels[$string];
return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
}
return $m[1];
}, $style);
if (null === $style) {
$pcreErrorCode = preg_last_error();
throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
}
return $style;
}
/**
* @param mixed[] $dict
* @return mixed[]
*/
private static function dump(string $title, array $dict): array
{
$script = [];
$dict = array_filter($dict);
if (empty($dict)) {
return $script;
}
$script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
foreach ($dict as $key => $value) {
$value = json_encode($value);
if (empty($value)) {
$value = static::quote('');
}
$script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value);
}
return $script;
}
private static function quote(string $arg): string
{
return '"' . addcslashes($arg, "\"\n\\") . '"';
}
/**
* @param mixed $args
*/
private static function call(...$args): string
{
$method = array_shift($args);
if (!is_string($method)) {
throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true));
}
return static::call_array($method, $args);
}
/**
* @param mixed[] $args
*/
private static function call_array(string $method, array $args): string
{
return 'c.' . $method . '(' . implode(', ', $args) . ');';
}
}

View file

@ -1,167 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;
/**
* Buffers all records until closing the handler and then pass them as batch.
*
* This is useful for a MailHandler to send only one mail per request instead of
* sending one per log message.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
{
use ProcessableHandlerTrait;
/** @var HandlerInterface */
protected $handler;
/** @var int */
protected $bufferSize = 0;
/** @var int */
protected $bufferLimit;
/** @var bool */
protected $flushOnOverflow;
/** @var Record[] */
protected $buffer = [];
/** @var bool */
protected $initialized = false;
/**
* @param HandlerInterface $handler Handler.
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
*/
public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false)
{
parent::__construct($level, $bubble);
$this->handler = $handler;
$this->bufferLimit = $bufferLimit;
$this->flushOnOverflow = $flushOnOverflow;
}
/**
* {@inheritDoc}
*/
public function handle(array $record): bool
{
if ($record['level'] < $this->level) {
return false;
}
if (!$this->initialized) {
// __destructor() doesn't get called on Fatal errors
register_shutdown_function([$this, 'close']);
$this->initialized = true;
}
if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
if ($this->flushOnOverflow) {
$this->flush();
} else {
array_shift($this->buffer);
$this->bufferSize--;
}
}
if ($this->processors) {
/** @var Record $record */
$record = $this->processRecord($record);
}
$this->buffer[] = $record;
$this->bufferSize++;
return false === $this->bubble;
}
public function flush(): void
{
if ($this->bufferSize === 0) {
return;
}
$this->handler->handleBatch($this->buffer);
$this->clear();
}
public function __destruct()
{
// suppress the parent behavior since we already have register_shutdown_function()
// to call close(), and the reference contained there will prevent this from being
// GC'd until the end of the request
}
/**
* {@inheritDoc}
*/
public function close(): void
{
$this->flush();
$this->handler->close();
}
/**
* Clears the buffer without flushing any messages down to the wrapped handler.
*/
public function clear(): void
{
$this->bufferSize = 0;
$this->buffer = [];
}
public function reset()
{
$this->flush();
parent::reset();
$this->resetProcessors();
if ($this->handler instanceof ResettableInterface) {
$this->handler->reset();
}
}
/**
* {@inheritDoc}
*/
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.');
}
/**
* {@inheritDoc}
*/
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.');
}
}

View file

@ -1,196 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Utils;
/**
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
*
* This also works out of the box with Firefox 43+
*
* @author Christophe Coevoet <stof@notk.org>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class ChromePHPHandler extends AbstractProcessingHandler
{
use WebRequestRecognizerTrait;
/**
* Version of the extension
*/
protected const VERSION = '4.0';
/**
* Header name
*/
protected const HEADER_NAME = 'X-ChromeLogger-Data';
/**
* Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
*/
protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';
/** @var bool */
protected static $initialized = false;
/**
* Tracks whether we sent too much data
*
* Chrome limits the headers to 4KB, so when we sent 3KB we stop sending
*
* @var bool
*/
protected static $overflowed = false;
/** @var mixed[] */
protected static $json = [
'version' => self::VERSION,
'columns' => ['label', 'log', 'backtrace', 'type'],
'rows' => [],
];
/** @var bool */
protected static $sendHeaders = true;
public function __construct($level = Logger::DEBUG, bool $bubble = true)
{
parent::__construct($level, $bubble);
if (!function_exists('json_encode')) {
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler');
}
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records): void
{
if (!$this->isWebRequest()) {
return;
}
$messages = [];
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
/** @var Record $message */
$message = $this->processRecord($record);
$messages[] = $message;
}
if (!empty($messages)) {
$messages = $this->getFormatter()->formatBatch($messages);
self::$json['rows'] = array_merge(self::$json['rows'], $messages);
$this->send();
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new ChromePHPFormatter();
}
/**
* Creates & sends header for a record
*
* @see sendHeader()
* @see send()
*/
protected function write(array $record): void
{
if (!$this->isWebRequest()) {
return;
}
self::$json['rows'][] = $record['formatted'];
$this->send();
}
/**
* Sends the log header
*
* @see sendHeader()
*/
protected function send(): void
{
if (self::$overflowed || !self::$sendHeaders) {
return;
}
if (!self::$initialized) {
self::$initialized = true;
self::$sendHeaders = $this->headersAccepted();
if (!self::$sendHeaders) {
return;
}
self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? '';
}
$json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);
$data = base64_encode($json);
if (strlen($data) > 3 * 1024) {
self::$overflowed = true;
$record = [
'message' => 'Incomplete logs, chrome header size limit reached',
'context' => [],
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),
'channel' => 'monolog',
'datetime' => new \DateTimeImmutable(),
'extra' => [],
];
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
$json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);
$data = base64_encode($json);
}
if (trim($data) !== '') {
$this->sendHeader(static::HEADER_NAME, $data);
}
}
/**
* Send header string to the client
*/
protected function sendHeader(string $header, string $content): void
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Verifies if the headers are accepted by the current user agent
*/
protected function headersAccepted(): bool
{
if (empty($_SERVER['HTTP_USER_AGENT'])) {
return false;
}
return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1;
}
}

View file

@ -1,77 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;
/**
* CouchDB handler
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class CouchDBHandler extends AbstractProcessingHandler
{
/** @var mixed[] */
private $options;
/**
* @param mixed[] $options
*/
public function __construct(array $options = [], $level = Logger::DEBUG, bool $bubble = true)
{
$this->options = array_merge([
'host' => 'localhost',
'port' => 5984,
'dbname' => 'logger',
'username' => null,
'password' => null,
], $options);
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$basicAuth = null;
if ($this->options['username']) {
$basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
}
$url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
$context = stream_context_create([
'http' => [
'method' => 'POST',
'content' => $record['formatted'],
'ignore_errors' => true,
'max_redirects' => 0,
'header' => 'Content-type: application/json',
],
]);
if (false === @file_get_contents($url, false, $context)) {
throw new \RuntimeException(sprintf('Could not connect to %s', $url));
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
}
}

View file

@ -1,167 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Utils;
/**
* Logs to Cube.
*
* @link https://github.com/square/cube/wiki
* @author Wan Chen <kami@kamisama.me>
* @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4
*/
class CubeHandler extends AbstractProcessingHandler
{
/** @var resource|\Socket|null */
private $udpConnection = null;
/** @var resource|\CurlHandle|null */
private $httpConnection = null;
/** @var string */
private $scheme;
/** @var string */
private $host;
/** @var int */
private $port;
/** @var string[] */
private $acceptedSchemes = ['http', 'udp'];
/**
* Create a Cube handler
*
* @throws \UnexpectedValueException when given url is not a valid url.
* A valid url must consist of three parts : protocol://host:port
* Only valid protocols used by Cube are http and udp
*/
public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true)
{
$urlInfo = parse_url($url);
if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
}
if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException(
'Invalid protocol (' . $urlInfo['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes)
);
}
$this->scheme = $urlInfo['scheme'];
$this->host = $urlInfo['host'];
$this->port = (int) $urlInfo['port'];
parent::__construct($level, $bubble);
}
/**
* Establish a connection to an UDP socket
*
* @throws \LogicException when unable to connect to the socket
* @throws MissingExtensionException when there is no socket extension
*/
protected function connectUdp(): void
{
if (!extension_loaded('sockets')) {
throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
}
$udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
if (false === $udpConnection) {
throw new \LogicException('Unable to create a socket');
}
$this->udpConnection = $udpConnection;
if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
}
}
/**
* Establish a connection to an http server
*
* @throws \LogicException when unable to connect to the socket
* @throws MissingExtensionException when no curl extension
*/
protected function connectHttp(): void
{
if (!extension_loaded('curl')) {
throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler');
}
$httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
if (false === $httpConnection) {
throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
}
$this->httpConnection = $httpConnection;
curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$date = $record['datetime'];
$data = ['time' => $date->format('Y-m-d\TH:i:s.uO')];
unset($record['datetime']);
if (isset($record['context']['type'])) {
$data['type'] = $record['context']['type'];
unset($record['context']['type']);
} else {
$data['type'] = $record['channel'];
}
$data['data'] = $record['context'];
$data['data']['level'] = $record['level'];
if ($this->scheme === 'http') {
$this->writeHttp(Utils::jsonEncode($data));
} else {
$this->writeUdp(Utils::jsonEncode($data));
}
}
private function writeUdp(string $data): void
{
if (!$this->udpConnection) {
$this->connectUdp();
}
socket_send($this->udpConnection, $data, strlen($data), 0);
}
private function writeHttp(string $data): void
{
if (!$this->httpConnection) {
$this->connectHttp();
}
if (null === $this->httpConnection) {
throw new \LogicException('No connection could be established');
}
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$data.']'),
]);
Curl\Util::execute($this->httpConnection, 5, false);
}
}

View file

@ -1,71 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\Curl;
use CurlHandle;
/**
* This class is marked as internal and it is not under the BC promise of the package.
*
* @internal
*/
final class Util
{
/** @var array<int> */
private static $retriableErrorCodes = [
CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT,
CURLE_HTTP_NOT_FOUND,
CURLE_READ_ERROR,
CURLE_OPERATION_TIMEOUTED,
CURLE_HTTP_POST_ERROR,
CURLE_SSL_CONNECT_ERROR,
];
/**
* Executes a CURL request with optional retries and exception on failure
*
* @param resource|CurlHandle $ch curl handler
* @param int $retries
* @param bool $closeAfterDone
* @return bool|string @see curl_exec
*/
public static function execute($ch, int $retries = 5, bool $closeAfterDone = true)
{
while ($retries--) {
$curlResponse = curl_exec($ch);
if ($curlResponse === false) {
$curlErrno = curl_errno($ch);
if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) {
$curlError = curl_error($ch);
if ($closeAfterDone) {
curl_close($ch);
}
throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError));
}
continue;
}
if ($closeAfterDone) {
curl_close($ch);
}
return $curlResponse;
}
return false;
}
}

View file

@ -1,186 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Psr\Log\LogLevel;
/**
* Simple handler wrapper that deduplicates log records across multiple requests
*
* It also includes the BufferHandler functionality and will buffer
* all messages until the end of the request or flush() is called.
*
* This works by storing all log records' messages above $deduplicationLevel
* to the file specified by $deduplicationStore. When further logs come in at the end of the
* request (or when flush() is called), all those above $deduplicationLevel are checked
* against the existing stored logs. If they match and the timestamps in the stored log is
* not older than $time seconds, the new log record is discarded. If no log record is new, the
* whole data set is discarded.
*
* This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
* that send messages to people, to avoid spamming with the same message over and over in case of
* a major component failure like a database server being down which makes all requests fail in the
* same way.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Record from \Monolog\Logger
* @phpstan-import-type LevelName from \Monolog\Logger
* @phpstan-import-type Level from \Monolog\Logger
*/
class DeduplicationHandler extends BufferHandler
{
/**
* @var string
*/
protected $deduplicationStore;
/**
* @var Level
*/
protected $deduplicationLevel;
/**
* @var int
*/
protected $time;
/**
* @var bool
*/
private $gc = false;
/**
* @param HandlerInterface $handler Handler.
* @param string $deduplicationStore The file/path where the deduplication log should be kept
* @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
* @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*
* @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel
*/
public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true)
{
parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);
$this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
$this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
$this->time = $time;
}
public function flush(): void
{
if ($this->bufferSize === 0) {
return;
}
$passthru = null;
foreach ($this->buffer as $record) {
if ($record['level'] >= $this->deduplicationLevel) {
$passthru = $passthru || !$this->isDuplicate($record);
if ($passthru) {
$this->appendRecord($record);
}
}
}
// default of null is valid as well as if no record matches duplicationLevel we just pass through
if ($passthru === true || $passthru === null) {
$this->handler->handleBatch($this->buffer);
}
$this->clear();
if ($this->gc) {
$this->collectLogs();
}
}
/**
* @phpstan-param Record $record
*/
private function isDuplicate(array $record): bool
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!is_array($store)) {
return false;
}
$yesterday = time() - 86400;
$timestampValidity = $record['datetime']->getTimestamp() - $this->time;
$expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);
for ($i = count($store) - 1; $i >= 0; $i--) {
list($timestamp, $level, $message) = explode(':', $store[$i], 3);
if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
return true;
}
if ($timestamp < $yesterday) {
$this->gc = true;
}
}
return false;
}
private function collectLogs(): void
{
if (!file_exists($this->deduplicationStore)) {
return;
}
$handle = fopen($this->deduplicationStore, 'rw+');
if (!$handle) {
throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore);
}
flock($handle, LOCK_EX);
$validLogs = [];
$timestampValidity = time() - $this->time;
while (!feof($handle)) {
$log = fgets($handle);
if ($log && substr($log, 0, 10) >= $timestampValidity) {
$validLogs[] = $log;
}
}
ftruncate($handle, 0);
rewind($handle);
foreach ($validLogs as $log) {
fwrite($handle, $log);
}
flock($handle, LOCK_UN);
fclose($handle);
$this->gc = false;
}
/**
* @phpstan-param Record $record
*/
private function appendRecord(array $record): void
{
file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
}
}

View file

@ -1,47 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Monolog\Formatter\FormatterInterface;
use Doctrine\CouchDB\CouchDBClient;
/**
* CouchDB handler for Doctrine CouchDB ODM
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class DoctrineCouchDBHandler extends AbstractProcessingHandler
{
/** @var CouchDBClient */
private $client;
public function __construct(CouchDBClient $client, $level = Logger::DEBUG, bool $bubble = true)
{
$this->client = $client;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$this->client->postDocument($record['formatted']);
}
protected function getDefaultFormatter(): FormatterInterface
{
return new NormalizerFormatter;
}
}

View file

@ -1,104 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Aws\Sdk;
use Aws\DynamoDb\DynamoDbClient;
use Monolog\Formatter\FormatterInterface;
use Aws\DynamoDb\Marshaler;
use Monolog\Formatter\ScalarFormatter;
use Monolog\Logger;
/**
* Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
*
* @link https://github.com/aws/aws-sdk-php/
* @author Andrew Lawson <adlawson@gmail.com>
*/
class DynamoDbHandler extends AbstractProcessingHandler
{
public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';
/**
* @var DynamoDbClient
*/
protected $client;
/**
* @var string
*/
protected $table;
/**
* @var int
*/
protected $version;
/**
* @var Marshaler
*/
protected $marshaler;
public function __construct(DynamoDbClient $client, string $table, $level = Logger::DEBUG, bool $bubble = true)
{
/** @phpstan-ignore-next-line */
if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) {
$this->version = 3;
$this->marshaler = new Marshaler;
} else {
$this->version = 2;
}
$this->client = $client;
$this->table = $table;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$filtered = $this->filterEmptyFields($record['formatted']);
if ($this->version === 3) {
$formatted = $this->marshaler->marshalItem($filtered);
} else {
/** @phpstan-ignore-next-line */
$formatted = $this->client->formatAttributes($filtered);
}
$this->client->putItem([
'TableName' => $this->table,
'Item' => $formatted,
]);
}
/**
* @param mixed[] $record
* @return mixed[]
*/
protected function filterEmptyFields(array $record): array
{
return array_filter($record, function ($value) {
return !empty($value) || false === $value || 0 === $value;
});
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new ScalarFormatter(self::DATE_FORMAT);
}
}

View file

@ -1,129 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Elastica\Document;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Logger;
use Elastica\Client;
use Elastica\Exception\ExceptionInterface;
/**
* Elastic Search handler
*
* Usage example:
*
* $client = new \Elastica\Client();
* $options = array(
* 'index' => 'elastic_index_name',
* 'type' => 'elastic_doc_type', Types have been removed in Elastica 7
* );
* $handler = new ElasticaHandler($client, $options);
* $log = new Logger('application');
* $log->pushHandler($handler);
*
* @author Jelle Vink <jelle.vink@gmail.com>
*/
class ElasticaHandler extends AbstractProcessingHandler
{
/**
* @var Client
*/
protected $client;
/**
* @var mixed[] Handler config options
*/
protected $options = [];
/**
* @param Client $client Elastica Client object
* @param mixed[] $options Handler configuration
*/
public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true)
{
parent::__construct($level, $bubble);
$this->client = $client;
$this->options = array_merge(
[
'index' => 'monolog', // Elastic index name
'type' => 'record', // Elastic document type
'ignore_error' => false, // Suppress Elastica exceptions
],
$options
);
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$this->bulkSend([$record['formatted']]);
}
/**
* {@inheritDoc}
*/
public function setFormatter(FormatterInterface $formatter): HandlerInterface
{
if ($formatter instanceof ElasticaFormatter) {
return parent::setFormatter($formatter);
}
throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter');
}
/**
* @return mixed[]
*/
public function getOptions(): array
{
return $this->options;
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new ElasticaFormatter($this->options['index'], $this->options['type']);
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records): void
{
$documents = $this->getFormatter()->formatBatch($records);
$this->bulkSend($documents);
}
/**
* Use Elasticsearch bulk API to send list of documents
*
* @param Document[] $documents
*
* @throws \RuntimeException
*/
protected function bulkSend(array $documents): void
{
try {
$this->client->addDocuments($documents);
} catch (ExceptionInterface $e) {
if (!$this->options['ignore_error']) {
throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
}
}
}
}

View file

@ -1,218 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Elastic\Elasticsearch\Response\Elasticsearch;
use Throwable;
use RuntimeException;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\ElasticsearchFormatter;
use InvalidArgumentException;
use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException;
use Elasticsearch\Client;
use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException;
use Elastic\Elasticsearch\Client as Client8;
/**
* Elasticsearch handler
*
* @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html
*
* Simple usage example:
*
* $client = \Elasticsearch\ClientBuilder::create()
* ->setHosts($hosts)
* ->build();
*
* $options = array(
* 'index' => 'elastic_index_name',
* 'type' => 'elastic_doc_type',
* );
* $handler = new ElasticsearchHandler($client, $options);
* $log = new Logger('application');
* $log->pushHandler($handler);
*
* @author Avtandil Kikabidze <akalongman@gmail.com>
*/
class ElasticsearchHandler extends AbstractProcessingHandler
{
/**
* @var Client|Client8
*/
protected $client;
/**
* @var mixed[] Handler config options
*/
protected $options = [];
/**
* @var bool
*/
private $needsType;
/**
* @param Client|Client8 $client Elasticsearch Client object
* @param mixed[] $options Handler configuration
*/
public function __construct($client, array $options = [], $level = Logger::DEBUG, bool $bubble = true)
{
if (!$client instanceof Client && !$client instanceof Client8) {
throw new \TypeError('Elasticsearch\Client or Elastic\Elasticsearch\Client instance required');
}
parent::__construct($level, $bubble);
$this->client = $client;
$this->options = array_merge(
[
'index' => 'monolog', // Elastic index name
'type' => '_doc', // Elastic document type
'ignore_error' => false, // Suppress Elasticsearch exceptions
],
$options
);
if ($client instanceof Client8 || $client::VERSION[0] === '7') {
$this->needsType = false;
// force the type to _doc for ES8/ES7
$this->options['type'] = '_doc';
} else {
$this->needsType = true;
}
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
$this->bulkSend([$record['formatted']]);
}
/**
* {@inheritDoc}
*/
public function setFormatter(FormatterInterface $formatter): HandlerInterface
{
if ($formatter instanceof ElasticsearchFormatter) {
return parent::setFormatter($formatter);
}
throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter');
}
/**
* Getter options
*
* @return mixed[]
*/
public function getOptions(): array
{
return $this->options;
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new ElasticsearchFormatter($this->options['index'], $this->options['type']);
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records): void
{
$documents = $this->getFormatter()->formatBatch($records);
$this->bulkSend($documents);
}
/**
* Use Elasticsearch bulk API to send list of documents
*
* @param array[] $records Records + _index/_type keys
* @throws \RuntimeException
*/
protected function bulkSend(array $records): void
{
try {
$params = [
'body' => [],
];
foreach ($records as $record) {
$params['body'][] = [
'index' => $this->needsType ? [
'_index' => $record['_index'],
'_type' => $record['_type'],
] : [
'_index' => $record['_index'],
],
];
unset($record['_index'], $record['_type']);
$params['body'][] = $record;
}
/** @var Elasticsearch */
$responses = $this->client->bulk($params);
if ($responses['errors'] === true) {
throw $this->createExceptionFromResponses($responses);
}
} catch (Throwable $e) {
if (! $this->options['ignore_error']) {
throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e);
}
}
}
/**
* Creates elasticsearch exception from responses array
*
* Only the first error is converted into an exception.
*
* @param mixed[]|Elasticsearch $responses returned by $this->client->bulk()
*/
protected function createExceptionFromResponses($responses): Throwable
{
foreach ($responses['items'] ?? [] as $item) {
if (isset($item['index']['error'])) {
return $this->createExceptionFromError($item['index']['error']);
}
}
if (class_exists(ElasticInvalidArgumentException::class)) {
return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.');
}
return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.');
}
/**
* Creates elasticsearch exception from error array
*
* @param mixed[] $error
*/
protected function createExceptionFromError(array $error): Throwable
{
$previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null;
if (class_exists(ElasticInvalidArgumentException::class)) {
return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous);
}
return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous);
}
}

View file

@ -1,91 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Utils;
/**
* Stores to PHP error_log() handler.
*
* @author Elan Ruusamäe <glen@delfi.ee>
*/
class ErrorLogHandler extends AbstractProcessingHandler
{
public const OPERATING_SYSTEM = 0;
public const SAPI = 4;
/** @var int */
protected $messageType;
/** @var bool */
protected $expandNewlines;
/**
* @param int $messageType Says where the error should go.
* @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
*/
public function __construct(int $messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, bool $bubble = true, bool $expandNewlines = false)
{
parent::__construct($level, $bubble);
if (false === in_array($messageType, self::getAvailableTypes(), true)) {
$message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
throw new \InvalidArgumentException($message);
}
$this->messageType = $messageType;
$this->expandNewlines = $expandNewlines;
}
/**
* @return int[] With all available types
*/
public static function getAvailableTypes(): array
{
return [
self::OPERATING_SYSTEM,
self::SAPI,
];
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
if (!$this->expandNewlines) {
error_log((string) $record['formatted'], $this->messageType);
return;
}
$lines = preg_split('{[\r\n]+}', (string) $record['formatted']);
if ($lines === false) {
$pcreErrorCode = preg_last_error();
throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode));
}
foreach ($lines as $line) {
error_log($line, $this->messageType);
}
}
}

View file

@ -1,71 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Throwable;
/**
* Forwards records to at most one handler
*
* If a handler fails, the exception is suppressed and the record is forwarded to the next handler.
*
* As soon as one handler handles a record successfully, the handling stops there.
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class FallbackGroupHandler extends GroupHandler
{
/**
* {@inheritDoc}
*/
public function handle(array $record): bool
{
if ($this->processors) {
/** @var Record $record */
$record = $this->processRecord($record);
}
foreach ($this->handlers as $handler) {
try {
$handler->handle($record);
break;
} catch (Throwable $e) {
// What throwable?
}
}
return false === $this->bubble;
}
/**
* {@inheritDoc}
*/
public function handleBatch(array $records): void
{
if ($this->processors) {
$processed = [];
foreach ($records as $record) {
$processed[] = $this->processRecord($record);
}
/** @var Record[] $records */
$records = $processed;
}
foreach ($this->handlers as $handler) {
try {
$handler->handleBatch($records);
break;
} catch (Throwable $e) {
// What throwable?
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more