/vendor/monolog/monolog/src/Monolog/ErrorHandler.php |
@@ -0,0 +1,230 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog; |
|
use Psr\Log\LoggerInterface; |
use Psr\Log\LogLevel; |
use Monolog\Handler\AbstractHandler; |
|
/** |
* 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 |
{ |
private $logger; |
|
private $previousExceptionHandler; |
private $uncaughtExceptionLevel; |
|
private $previousErrorHandler; |
private $errorLevelMap; |
private $handleOnlyReportedErrors; |
|
private $hasFatalErrorHandler; |
private $fatalLevel; |
private $reservedMemory; |
private static $fatalErrors = array(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|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling |
* @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling |
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling |
* @return ErrorHandler |
*/ |
public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) |
{ |
//Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 |
class_exists('\\Psr\\Log\\LogLevel', true); |
|
$handler = new static($logger); |
if ($errorLevelMap !== false) { |
$handler->registerErrorHandler($errorLevelMap); |
} |
if ($exceptionLevel !== false) { |
$handler->registerExceptionHandler($exceptionLevel); |
} |
if ($fatalLevel !== false) { |
$handler->registerFatalHandler($fatalLevel); |
} |
|
return $handler; |
} |
|
public function registerExceptionHandler($level = null, $callPrevious = true) |
{ |
$prev = set_exception_handler(array($this, 'handleException')); |
$this->uncaughtExceptionLevel = $level; |
if ($callPrevious && $prev) { |
$this->previousExceptionHandler = $prev; |
} |
} |
|
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) |
{ |
$prev = set_error_handler(array($this, 'handleError'), $errorTypes); |
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); |
if ($callPrevious) { |
$this->previousErrorHandler = $prev ?: true; |
} |
|
$this->handleOnlyReportedErrors = $handleOnlyReportedErrors; |
} |
|
public function registerFatalHandler($level = null, $reservedMemorySize = 20) |
{ |
register_shutdown_function(array($this, 'handleFatalError')); |
|
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); |
$this->fatalLevel = $level; |
$this->hasFatalErrorHandler = true; |
} |
|
protected function defaultErrorLevelMap() |
{ |
return array( |
E_ERROR => LogLevel::CRITICAL, |
E_WARNING => LogLevel::WARNING, |
E_PARSE => LogLevel::ALERT, |
E_NOTICE => LogLevel::NOTICE, |
E_CORE_ERROR => LogLevel::CRITICAL, |
E_CORE_WARNING => LogLevel::WARNING, |
E_COMPILE_ERROR => LogLevel::ALERT, |
E_COMPILE_WARNING => LogLevel::WARNING, |
E_USER_ERROR => LogLevel::ERROR, |
E_USER_WARNING => LogLevel::WARNING, |
E_USER_NOTICE => LogLevel::NOTICE, |
E_STRICT => LogLevel::NOTICE, |
E_RECOVERABLE_ERROR => LogLevel::ERROR, |
E_DEPRECATED => LogLevel::NOTICE, |
E_USER_DEPRECATED => LogLevel::NOTICE, |
); |
} |
|
/** |
* @private |
*/ |
public function handleException($e) |
{ |
$this->logger->log( |
$this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, |
sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), |
array('exception' => $e) |
); |
|
if ($this->previousExceptionHandler) { |
call_user_func($this->previousExceptionHandler, $e); |
} |
|
exit(255); |
} |
|
/** |
* @private |
*/ |
public function handleError($code, $message, $file = '', $line = 0, $context = array()) |
{ |
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { |
return; |
} |
|
// fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries |
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { |
$level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; |
$this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); |
} |
|
if ($this->previousErrorHandler === true) { |
return false; |
} elseif ($this->previousErrorHandler) { |
return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); |
} |
} |
|
/** |
* @private |
*/ |
public function handleFatalError() |
{ |
$this->reservedMemory = null; |
|
$lastError = error_get_last(); |
if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { |
$this->logger->log( |
$this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, |
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], |
array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']) |
); |
|
if ($this->logger instanceof Logger) { |
foreach ($this->logger->getHandlers() as $handler) { |
if ($handler instanceof AbstractHandler) { |
$handler->close(); |
} |
} |
} |
} |
} |
|
private static function codeToString($code) |
{ |
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'; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php |
@@ -0,0 +1,138 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Formatter; |
|
use Monolog\Logger; |
use Gelf\Message; |
|
/** |
* Serializes a log message to GELF |
* @see http://www.graylog2.org/about/gelf |
* |
* @author Matt Lehner <mlehner@gmail.com> |
*/ |
class GelfMessageFormatter extends NormalizerFormatter |
{ |
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; |
|
/** |
* Translates Monolog log levels to Graylog2 log priorities. |
*/ |
private $logLevels = array( |
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($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null) |
{ |
parent::__construct('U.u'); |
|
$this->systemName = $systemName ?: gethostname(); |
|
$this->extraPrefix = $extraPrefix; |
$this->contextPrefix = $contextPrefix; |
$this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function format(array $record) |
{ |
$record = parent::format($record); |
|
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(substr($record['message'], 0, $this->maxLength)); |
} |
|
if (isset($record['channel'])) { |
$message->setFacility($record['channel']); |
} |
if (isset($record['extra']['line'])) { |
$message->setLine($record['extra']['line']); |
unset($record['extra']['line']); |
} |
if (isset($record['extra']['file'])) { |
$message->setFile($record['extra']['file']); |
unset($record['extra']['file']); |
} |
|
foreach ($record['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, substr($val, 0, $this->maxLength)); |
break; |
} |
$message->setAdditional($this->extraPrefix . $key, $val); |
} |
|
foreach ($record['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, substr($val, 0, $this->maxLength)); |
break; |
} |
$message->setAdditional($this->contextPrefix . $key, $val); |
} |
|
if (null === $message->getFile() && isset($record['context']['exception']['file'])) { |
if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { |
$message->setFile($matches[1]); |
$message->setLine($matches[2]); |
} |
} |
|
return $message; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php |
@@ -0,0 +1,141 @@ |
<?php |
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Formatter; |
|
use Monolog\Logger; |
|
/** |
* 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. |
*/ |
protected $logLevels = array( |
Logger::DEBUG => '#cccccc', |
Logger::INFO => '#468847', |
Logger::NOTICE => '#3a87ad', |
Logger::WARNING => '#c09853', |
Logger::ERROR => '#f0ad4e', |
Logger::CRITICAL => '#FF7708', |
Logger::ALERT => '#C12A19', |
Logger::EMERGENCY => '#000000', |
); |
|
/** |
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format |
*/ |
public function __construct($dateFormat = null) |
{ |
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 |
* @return string |
*/ |
protected function addRow($th, $td = ' ', $escapeTd = true) |
{ |
$th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); |
if ($escapeTd) { |
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>'; |
} |
|
return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background: #cccccc\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">".$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($title, $level) |
{ |
$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. |
* |
* @param array $record A record to format |
* @return mixed The formatted record |
*/ |
public function format(array $record) |
{ |
$output = $this->addTitle($record['level_name'], $record['level']); |
$output .= '<table cellspacing="1" width="100%" class="monolog-output">'; |
|
$output .= $this->addRow('Message', (string) $record['message']); |
$output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); |
$output .= $this->addRow('Channel', $record['channel']); |
if ($record['context']) { |
$embeddedTable = '<table cellspacing="1" width="100%">'; |
foreach ($record['context'] as $key => $value) { |
$embeddedTable .= $this->addRow($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($key, $this->convertToString($value)); |
} |
$embeddedTable .= '</table>'; |
$output .= $this->addRow('Extra', $embeddedTable, false); |
} |
|
return $output.'</table>'; |
} |
|
/** |
* Formats a set of log records. |
* |
* @param array $records A set of records to format |
* @return mixed The formatted set of records |
*/ |
public function formatBatch(array $records) |
{ |
$message = ''; |
foreach ($records as $record) { |
$message .= $this->format($record); |
} |
|
return $message; |
} |
|
protected function convertToString($data) |
{ |
if (null === $data || is_scalar($data)) { |
return (string) $data; |
} |
|
$data = $this->normalize($data); |
if (version_compare(PHP_VERSION, '5.4.0', '>=')) { |
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); |
} |
|
return str_replace('\\/', '/', json_encode($data)); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php |
@@ -0,0 +1,208 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Formatter; |
|
use Exception; |
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> |
*/ |
class JsonFormatter extends NormalizerFormatter |
{ |
const BATCH_MODE_JSON = 1; |
const BATCH_MODE_NEWLINES = 2; |
|
protected $batchMode; |
protected $appendNewline; |
|
/** |
* @var bool |
*/ |
protected $includeStacktraces = false; |
|
/** |
* @param int $batchMode |
* @param bool $appendNewline |
*/ |
public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true) |
{ |
$this->batchMode = $batchMode; |
$this->appendNewline = $appendNewline; |
} |
|
/** |
* 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. |
* |
* @return int |
*/ |
public function getBatchMode() |
{ |
return $this->batchMode; |
} |
|
/** |
* True if newlines are appended to every formatted record |
* |
* @return bool |
*/ |
public function isAppendingNewlines() |
{ |
return $this->appendNewline; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function format(array $record) |
{ |
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function formatBatch(array $records) |
{ |
switch ($this->batchMode) { |
case static::BATCH_MODE_NEWLINES: |
return $this->formatBatchNewlines($records); |
|
case static::BATCH_MODE_JSON: |
default: |
return $this->formatBatchJson($records); |
} |
} |
|
/** |
* @param bool $include |
*/ |
public function includeStacktraces($include = true) |
{ |
$this->includeStacktraces = $include; |
} |
|
/** |
* Return a JSON-encoded array of records. |
* |
* @param array $records |
* @return string |
*/ |
protected function formatBatchJson(array $records) |
{ |
return $this->toJson($this->normalize($records), true); |
} |
|
/** |
* Use new lines to separate records instead of a |
* JSON-encoded array. |
* |
* @param array $records |
* @return string |
*/ |
protected function formatBatchNewlines(array $records) |
{ |
$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) |
{ |
if (is_array($data) || $data instanceof \Traversable) { |
$normalized = array(); |
|
$count = 1; |
foreach ($data as $key => $value) { |
if ($count++ >= 1000) { |
$normalized['...'] = 'Over 1000 items, aborting normalization'; |
break; |
} |
$normalized[$key] = $this->normalize($value); |
} |
|
return $normalized; |
} |
|
if ($data instanceof Exception || $data instanceof Throwable) { |
return $this->normalizeException($data); |
} |
|
return $data; |
} |
|
/** |
* Normalizes given exception with or without its own stack trace based on |
* `includeStacktraces` property. |
* |
* @param Exception|Throwable $e |
* |
* @return array |
*/ |
protected function normalizeException($e) |
{ |
// TODO 2.0 only check for Throwable |
if (!$e instanceof Exception && !$e instanceof Throwable) { |
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); |
} |
|
$data = array( |
'class' => get_class($e), |
'message' => $e->getMessage(), |
'code' => $e->getCode(), |
'file' => $e->getFile().':'.$e->getLine(), |
); |
|
if ($this->includeStacktraces) { |
$trace = $e->getTrace(); |
foreach ($trace as $frame) { |
if (isset($frame['file'])) { |
$data['trace'][] = $frame['file'].':'.$frame['line']; |
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') { |
// We should again normalize the frames, because it might contain invalid items |
$data['trace'][] = $frame['function']; |
} else { |
// We should again normalize the frames, because it might contain invalid items |
$data['trace'][] = $this->normalize($frame); |
} |
} |
} |
|
if ($previous = $e->getPrevious()) { |
$data['previous'] = $this->normalizeException($previous); |
} |
|
return $data; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php |
@@ -0,0 +1,179 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Formatter; |
|
/** |
* 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 |
{ |
const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; |
|
protected $format; |
protected $allowInlineLineBreaks; |
protected $ignoreEmptyContextAndExtra; |
protected $includeStacktraces; |
|
/** |
* @param string $format The format of the message |
* @param string $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($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) |
{ |
$this->format = $format ?: static::SIMPLE_FORMAT; |
$this->allowInlineLineBreaks = $allowInlineLineBreaks; |
$this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; |
parent::__construct($dateFormat); |
} |
|
public function includeStacktraces($include = true) |
{ |
$this->includeStacktraces = $include; |
if ($this->includeStacktraces) { |
$this->allowInlineLineBreaks = true; |
} |
} |
|
public function allowInlineLineBreaks($allow = true) |
{ |
$this->allowInlineLineBreaks = $allow; |
} |
|
public function ignoreEmptyContextAndExtra($ignore = true) |
{ |
$this->ignoreEmptyContextAndExtra = $ignore; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function format(array $record) |
{ |
$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); |
} |
|
return $output; |
} |
|
public function formatBatch(array $records) |
{ |
$message = ''; |
foreach ($records as $record) { |
$message .= $this->format($record); |
} |
|
return $message; |
} |
|
public function stringify($value) |
{ |
return $this->replaceNewlines($this->convertToString($value)); |
} |
|
protected function normalizeException($e) |
{ |
// TODO 2.0 only check for Throwable |
if (!$e instanceof \Exception && !$e instanceof \Throwable) { |
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); |
} |
|
$previousText = ''; |
if ($previous = $e->getPrevious()) { |
do { |
$previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); |
} while ($previous = $previous->getPrevious()); |
} |
|
$str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; |
if ($this->includeStacktraces) { |
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; |
} |
|
return $str; |
} |
|
protected function convertToString($data) |
{ |
if (null === $data || is_bool($data)) { |
return var_export($data, true); |
} |
|
if (is_scalar($data)) { |
return (string) $data; |
} |
|
if (version_compare(PHP_VERSION, '5.4.0', '>=')) { |
return $this->toJson($data, true); |
} |
|
return str_replace('\\/', '/', @json_encode($data)); |
} |
|
protected function replaceNewlines($str) |
{ |
if ($this->allowInlineLineBreaks) { |
if (0 === strpos($str, '{')) { |
return str_replace(array('\r', '\n'), array("\r", "\n"), $str); |
} |
|
return $str; |
} |
|
return str_replace(array("\r\n", "\r", "\n"), ' ', $str); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php |
@@ -0,0 +1,166 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Formatter; |
|
/** |
* Serializes a log message to Logstash Event Format |
* |
* @see http://logstash.net/ |
* @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb |
* |
* @author Tim Mower <timothy.mower@gmail.com> |
*/ |
class LogstashFormatter extends NormalizerFormatter |
{ |
const V0 = 0; |
const V1 = 1; |
|
/** |
* @var string the name of the system for the Logstash log message, used to fill the @source field |
*/ |
protected $systemName; |
|
/** |
* @var string an application name for the Logstash log message, used to fill the @type field |
*/ |
protected $applicationName; |
|
/** |
* @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 logstash format version to use |
*/ |
protected $version; |
|
/** |
* @param string $applicationName the application that sends the data, used as the "type" field of logstash |
* @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine |
* @param string $extraPrefix prefix for extra keys inside logstash "fields" |
* @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ |
* @param int $version the logstash format version to use, defaults to 0 |
*/ |
public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) |
{ |
// logstash requires a ISO 8601 format date with optional millisecond precision. |
parent::__construct('Y-m-d\TH:i:s.uP'); |
|
$this->systemName = $systemName ?: gethostname(); |
$this->applicationName = $applicationName; |
$this->extraPrefix = $extraPrefix; |
$this->contextPrefix = $contextPrefix; |
$this->version = $version; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function format(array $record) |
{ |
$record = parent::format($record); |
|
if ($this->version === self::V1) { |
$message = $this->formatV1($record); |
} else { |
$message = $this->formatV0($record); |
} |
|
return $this->toJson($message) . "\n"; |
} |
|
protected function formatV0(array $record) |
{ |
if (empty($record['datetime'])) { |
$record['datetime'] = gmdate('c'); |
} |
$message = array( |
'@timestamp' => $record['datetime'], |
'@source' => $this->systemName, |
'@fields' => array(), |
); |
if (isset($record['message'])) { |
$message['@message'] = $record['message']; |
} |
if (isset($record['channel'])) { |
$message['@tags'] = array($record['channel']); |
$message['@fields']['channel'] = $record['channel']; |
} |
if (isset($record['level'])) { |
$message['@fields']['level'] = $record['level']; |
} |
if ($this->applicationName) { |
$message['@type'] = $this->applicationName; |
} |
if (isset($record['extra']['server'])) { |
$message['@source_host'] = $record['extra']['server']; |
} |
if (isset($record['extra']['url'])) { |
$message['@source_path'] = $record['extra']['url']; |
} |
if (!empty($record['extra'])) { |
foreach ($record['extra'] as $key => $val) { |
$message['@fields'][$this->extraPrefix . $key] = $val; |
} |
} |
if (!empty($record['context'])) { |
foreach ($record['context'] as $key => $val) { |
$message['@fields'][$this->contextPrefix . $key] = $val; |
} |
} |
|
return $message; |
} |
|
protected function formatV1(array $record) |
{ |
if (empty($record['datetime'])) { |
$record['datetime'] = gmdate('c'); |
} |
$message = array( |
'@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 ($this->applicationName) { |
$message['type'] = $this->applicationName; |
} |
if (!empty($record['extra'])) { |
foreach ($record['extra'] as $key => $val) { |
$message[$this->extraPrefix . $key] = $val; |
} |
} |
if (!empty($record['context'])) { |
foreach ($record['context'] as $key => $val) { |
$message[$this->contextPrefix . $key] = $val; |
} |
} |
|
return $message; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php |
@@ -0,0 +1,297 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Formatter; |
|
use Exception; |
|
/** |
* 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 |
{ |
const SIMPLE_DATE = "Y-m-d H:i:s"; |
|
protected $dateFormat; |
|
/** |
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format |
*/ |
public function __construct($dateFormat = null) |
{ |
$this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; |
if (!function_exists('json_encode')) { |
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); |
} |
} |
|
/** |
* {@inheritdoc} |
*/ |
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; |
} |
|
protected function normalize($data) |
{ |
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 = array(); |
|
$count = 1; |
foreach ($data as $key => $value) { |
if ($count++ >= 1000) { |
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; |
break; |
} |
$normalized[$key] = $this->normalize($value); |
} |
|
return $normalized; |
} |
|
if ($data instanceof \DateTime) { |
return $data->format($this->dateFormat); |
} |
|
if (is_object($data)) { |
// TODO 2.0 only check for Throwable |
if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { |
return $this->normalizeException($data); |
} |
|
// non-serializable objects that implement __toString stringified |
if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { |
$value = $data->__toString(); |
} else { |
// the rest is json-serialized in some way |
$value = $this->toJson($data, true); |
} |
|
return sprintf("[object] (%s: %s)", get_class($data), $value); |
} |
|
if (is_resource($data)) { |
return sprintf('[resource] (%s)', get_resource_type($data)); |
} |
|
return '[unknown('.gettype($data).')]'; |
} |
|
protected function normalizeException($e) |
{ |
// TODO 2.0 only check for Throwable |
if (!$e instanceof Exception && !$e instanceof \Throwable) { |
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); |
} |
|
$data = array( |
'class' => get_class($e), |
'message' => $e->getMessage(), |
'code' => $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)) { |
$data['detail'] = $e->detail; |
} |
} |
|
$trace = $e->getTrace(); |
foreach ($trace as $frame) { |
if (isset($frame['file'])) { |
$data['trace'][] = $frame['file'].':'.$frame['line']; |
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') { |
// We should again normalize the frames, because it might contain invalid items |
$data['trace'][] = $frame['function']; |
} else { |
// We should again normalize the frames, because it might contain invalid items |
$data['trace'][] = $this->toJson($this->normalize($frame), true); |
} |
} |
|
if ($previous = $e->getPrevious()) { |
$data['previous'] = $this->normalizeException($previous); |
} |
|
return $data; |
} |
|
/** |
* Return the JSON representation of a value |
* |
* @param mixed $data |
* @param bool $ignoreErrors |
* @throws \RuntimeException if encoding fails and errors are not ignored |
* @return string |
*/ |
protected function toJson($data, $ignoreErrors = false) |
{ |
// suppress json_encode errors since it's twitchy with some inputs |
if ($ignoreErrors) { |
return @$this->jsonEncode($data); |
} |
|
$json = $this->jsonEncode($data); |
|
if ($json === false) { |
$json = $this->handleJsonError(json_last_error(), $data); |
} |
|
return $json; |
} |
|
/** |
* @param mixed $data |
* @return string JSON encoded data or null on failure |
*/ |
private function jsonEncode($data) |
{ |
if (version_compare(PHP_VERSION, '5.4.0', '>=')) { |
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); |
} |
|
return json_encode($data); |
} |
|
/** |
* Handle a json_encode failure. |
* |
* If the failure is due to invalid string encoding, try to clean the |
* input and encode again. If the second encoding attempt fails, the |
* inital error is not encoding related or the input can't be cleaned then |
* raise a descriptive exception. |
* |
* @param int $code return code of json_last_error function |
* @param mixed $data data that was meant to be encoded |
* @throws \RuntimeException if failure can't be corrected |
* @return string JSON encoded data after error correction |
*/ |
private function handleJsonError($code, $data) |
{ |
if ($code !== JSON_ERROR_UTF8) { |
$this->throwEncodeError($code, $data); |
} |
|
if (is_string($data)) { |
$this->detectAndCleanUtf8($data); |
} elseif (is_array($data)) { |
array_walk_recursive($data, array($this, 'detectAndCleanUtf8')); |
} else { |
$this->throwEncodeError($code, $data); |
} |
|
$json = $this->jsonEncode($data); |
|
if ($json === false) { |
$this->throwEncodeError(json_last_error(), $data); |
} |
|
return $json; |
} |
|
/** |
* Throws an exception according to a given code with a customized message |
* |
* @param int $code return code of json_last_error function |
* @param mixed $data data that was meant to be encoded |
* @throws \RuntimeException |
*/ |
private function throwEncodeError($code, $data) |
{ |
switch ($code) { |
case JSON_ERROR_DEPTH: |
$msg = 'Maximum stack depth exceeded'; |
break; |
case JSON_ERROR_STATE_MISMATCH: |
$msg = 'Underflow or the modes mismatch'; |
break; |
case JSON_ERROR_CTRL_CHAR: |
$msg = 'Unexpected control character found'; |
break; |
case JSON_ERROR_UTF8: |
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; |
break; |
default: |
$msg = 'Unknown error'; |
} |
|
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); |
} |
|
/** |
* Detect invalid UTF-8 string characters and convert to valid UTF-8. |
* |
* Valid UTF-8 input will be left unmodified, but strings containing |
* invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed |
* original encoding of ISO-8859-15. This conversion may result in |
* incorrect output if the actual encoding was not ISO-8859-15, but it |
* will be clean UTF-8 output and will not rely on expensive and fragile |
* detection algorithms. |
* |
* Function converts the input in place in the passed variable so that it |
* can be used as a callback for array_walk_recursive. |
* |
* @param mixed &$data Input to check and convert if needed |
* @private |
*/ |
public function detectAndCleanUtf8(&$data) |
{ |
if (is_string($data) && !preg_match('//u', $data)) { |
$data = preg_replace_callback( |
'/[\x80-\xFF]+/', |
function ($m) { return utf8_encode($m[0]); }, |
$data |
); |
$data = str_replace( |
array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), |
array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), |
$data |
); |
} |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php |
@@ -0,0 +1,230 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Formatter\LineFormatter; |
|
/** |
* Handler sending logs to browser's javascript console with no browser extension required |
* |
* @author Olivier Poitrey <rs@dailymotion.com> |
*/ |
class BrowserConsoleHandler extends AbstractProcessingHandler |
{ |
protected static $initialized = false; |
protected static $records = array(); |
|
/** |
* {@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() |
{ |
return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function write(array $record) |
{ |
// Accumulate records |
self::$records[] = $record; |
|
// Register shutdown handler if not already done |
if (!self::$initialized) { |
self::$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() |
{ |
$format = self::getResponseFormat(); |
if ($format === 'unknown') { |
return; |
} |
|
if (count(self::$records)) { |
if ($format === 'html') { |
self::writeOutput('<script>' . self::generateScript() . '</script>'); |
} elseif ($format === 'js') { |
self::writeOutput(self::generateScript()); |
} |
self::reset(); |
} |
} |
|
/** |
* Forget all logged records |
*/ |
public static function reset() |
{ |
self::$records = array(); |
} |
|
/** |
* Wrapper for register_shutdown_function to allow overriding |
*/ |
protected function registerShutdownFunction() |
{ |
if (PHP_SAPI !== 'cli') { |
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); |
} |
} |
|
/** |
* Wrapper for echo to allow overriding |
* |
* @param string $str |
*/ |
protected static function writeOutput($str) |
{ |
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' |
*/ |
protected static function getResponseFormat() |
{ |
// Check content type |
foreach (headers_list() as $header) { |
if (stripos($header, 'content-type:') === 0) { |
// This handler only works with HTML and javascript outputs |
// text/javascript is obsolete in favour of application/javascript, but still used |
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { |
return 'js'; |
} |
if (stripos($header, 'text/html') === false) { |
return 'unknown'; |
} |
break; |
} |
} |
|
return 'html'; |
} |
|
private static function generateScript() |
{ |
$script = array(); |
foreach (self::$records as $record) { |
$context = self::dump('Context', $record['context']); |
$extra = self::dump('Extra', $record['extra']); |
|
if (empty($context) && empty($extra)) { |
$script[] = self::call_array('log', self::handleStyles($record['formatted'])); |
} else { |
$script = array_merge($script, |
array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))), |
$context, |
$extra, |
array(self::call('groupEnd')) |
); |
} |
} |
|
return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; |
} |
|
private static function handleStyles($formatted) |
{ |
$args = array(self::quote('font-weight: normal')); |
$format = '%c' . $formatted; |
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); |
|
foreach (array_reverse($matches) as $match) { |
$args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); |
$args[] = '"font-weight: normal"'; |
|
$pos = $match[0][1]; |
$format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); |
} |
|
array_unshift($args, self::quote($format)); |
|
return $args; |
} |
|
private static function handleCustomStyles($style, $string) |
{ |
static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); |
static $labels = array(); |
|
return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($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); |
} |
|
private static function dump($title, array $dict) |
{ |
$script = array(); |
$dict = array_filter($dict); |
if (empty($dict)) { |
return $script; |
} |
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); |
foreach ($dict as $key => $value) { |
$value = json_encode($value); |
if (empty($value)) { |
$value = self::quote(''); |
} |
$script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value); |
} |
|
return $script; |
} |
|
private static function quote($arg) |
{ |
return '"' . addcslashes($arg, "\"\n\\") . '"'; |
} |
|
private static function call() |
{ |
$args = func_get_args(); |
$method = array_shift($args); |
|
return self::call_array($method, $args); |
} |
|
private static function call_array($method, array $args) |
{ |
return 'c.' . $method . '(' . implode(', ', $args) . ');'; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php |
@@ -0,0 +1,211 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Formatter\ChromePHPFormatter; |
use Monolog\Logger; |
|
/** |
* 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> |
*/ |
class ChromePHPHandler extends AbstractProcessingHandler |
{ |
/** |
* Version of the extension |
*/ |
const VERSION = '4.0'; |
|
/** |
* Header name |
*/ |
const HEADER_NAME = 'X-ChromeLogger-Data'; |
|
/** |
* Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) |
*/ |
const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; |
|
protected static $initialized = false; |
|
/** |
* Tracks whether we sent too much data |
* |
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending |
* |
* @var Boolean |
*/ |
protected static $overflowed = false; |
|
protected static $json = array( |
'version' => self::VERSION, |
'columns' => array('label', 'log', 'backtrace', 'type'), |
'rows' => array(), |
); |
|
protected static $sendHeaders = true; |
|
/** |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
*/ |
public function __construct($level = Logger::DEBUG, $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) |
{ |
$messages = array(); |
|
foreach ($records as $record) { |
if ($record['level'] < $this->level) { |
continue; |
} |
$messages[] = $this->processRecord($record); |
} |
|
if (!empty($messages)) { |
$messages = $this->getFormatter()->formatBatch($messages); |
self::$json['rows'] = array_merge(self::$json['rows'], $messages); |
$this->send(); |
} |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function getDefaultFormatter() |
{ |
return new ChromePHPFormatter(); |
} |
|
/** |
* Creates & sends header for a record |
* |
* @see sendHeader() |
* @see send() |
* @param array $record |
*/ |
protected function write(array $record) |
{ |
self::$json['rows'][] = $record['formatted']; |
|
$this->send(); |
} |
|
/** |
* Sends the log header |
* |
* @see sendHeader() |
*/ |
protected function send() |
{ |
if (self::$overflowed || !self::$sendHeaders) { |
return; |
} |
|
if (!self::$initialized) { |
self::$initialized = true; |
|
self::$sendHeaders = $this->headersAccepted(); |
if (!self::$sendHeaders) { |
return; |
} |
|
self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; |
} |
|
$json = @json_encode(self::$json); |
$data = base64_encode(utf8_encode($json)); |
if (strlen($data) > 240 * 1024) { |
self::$overflowed = true; |
|
$record = array( |
'message' => 'Incomplete logs, chrome header size limit reached', |
'context' => array(), |
'level' => Logger::WARNING, |
'level_name' => Logger::getLevelName(Logger::WARNING), |
'channel' => 'monolog', |
'datetime' => new \DateTime(), |
'extra' => array(), |
); |
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); |
$json = @json_encode(self::$json); |
$data = base64_encode(utf8_encode($json)); |
} |
|
if (trim($data) !== '') { |
$this->sendHeader(self::HEADER_NAME, $data); |
} |
} |
|
/** |
* Send header string to the client |
* |
* @param string $header |
* @param string $content |
*/ |
protected function sendHeader($header, $content) |
{ |
if (!headers_sent() && self::$sendHeaders) { |
header(sprintf('%s: %s', $header, $content)); |
} |
} |
|
/** |
* Verifies if the headers are accepted by the current user agent |
* |
* @return Boolean |
*/ |
protected function headersAccepted() |
{ |
if (empty($_SERVER['HTTP_USER_AGENT'])) { |
return false; |
} |
|
return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); |
} |
|
/** |
* BC getter for the sendHeaders property that has been made static |
*/ |
public function __get($property) |
{ |
if ('sendHeaders' !== $property) { |
throw new \InvalidArgumentException('Undefined property '.$property); |
} |
|
return static::$sendHeaders; |
} |
|
/** |
* BC setter for the sendHeaders property that has been made static |
*/ |
public function __set($property, $value) |
{ |
if ('sendHeaders' !== $property) { |
throw new \InvalidArgumentException('Undefined property '.$property); |
} |
|
static::$sendHeaders = $value; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php |
@@ -0,0 +1,151 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* Logs to Cube. |
* |
* @link http://square.github.com/cube/ |
* @author Wan Chen <kami@kamisama.me> |
*/ |
class CubeHandler extends AbstractProcessingHandler |
{ |
private $udpConnection; |
private $httpConnection; |
private $scheme; |
private $host; |
private $port; |
private $acceptedSchemes = array('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($url, $level = Logger::DEBUG, $bubble = true) |
{ |
$urlInfo = parse_url($url); |
|
if (!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 = $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() |
{ |
if (!extension_loaded('sockets')) { |
throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); |
} |
|
$this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); |
if (!$this->udpConnection) { |
throw new \LogicException('Unable to create a socket'); |
} |
|
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 a http server |
* @throws \LogicException when no curl extension |
*/ |
protected function connectHttp() |
{ |
if (!extension_loaded('curl')) { |
throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); |
} |
|
$this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); |
|
if (!$this->httpConnection) { |
throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); |
} |
|
curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); |
curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function write(array $record) |
{ |
$date = $record['datetime']; |
|
$data = array('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(json_encode($data)); |
} else { |
$this->writeUdp(json_encode($data)); |
} |
} |
|
private function writeUdp($data) |
{ |
if (!$this->udpConnection) { |
$this->connectUdp(); |
} |
|
socket_send($this->udpConnection, $data, strlen($data), 0); |
} |
|
private function writeHttp($data) |
{ |
if (!$this->httpConnection) { |
$this->connectHttp(); |
} |
|
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); |
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( |
'Content-Type: application/json', |
'Content-Length: ' . strlen('['.$data.']'), |
)); |
|
Curl\Util::execute($this->httpConnection, 5, false); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php |
@@ -0,0 +1,169 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* 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> |
*/ |
class DeduplicationHandler extends BufferHandler |
{ |
/** |
* @var string |
*/ |
protected $deduplicationStore; |
|
/** |
* @var int |
*/ |
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 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 Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
*/ |
public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $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() |
{ |
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(); |
} |
} |
|
private function isDuplicate(array $record) |
{ |
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() |
{ |
if (!file_exists($this->deduplicationStore)) { |
return false; |
} |
|
$handle = fopen($this->deduplicationStore, 'rw+'); |
flock($handle, LOCK_EX); |
$validLogs = array(); |
|
$timestampValidity = time() - $this->time; |
|
while (!feof($handle)) { |
$log = fgets($handle); |
if (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; |
} |
|
private function appendRecord(array $record) |
{ |
file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php |
@@ -0,0 +1,163 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; |
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; |
use Monolog\Logger; |
|
/** |
* Buffers all records until a certain level is reached |
* |
* The advantage of this approach is that you don't get any clutter in your log files. |
* Only requests which actually trigger an error (or whatever your actionLevel is) will be |
* in the logs, but they will contain all records, not only those above the level threshold. |
* |
* You can find the various activation strategies in the |
* Monolog\Handler\FingersCrossed\ namespace. |
* |
* @author Jordi Boggiano <j.boggiano@seld.be> |
*/ |
class FingersCrossedHandler extends AbstractHandler |
{ |
protected $handler; |
protected $activationStrategy; |
protected $buffering = true; |
protected $bufferSize; |
protected $buffer = array(); |
protected $stopBuffering; |
protected $passthruLevel; |
|
/** |
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). |
* @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action |
* @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
* @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) |
* @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered |
*/ |
public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) |
{ |
if (null === $activationStrategy) { |
$activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); |
} |
|
// convert simple int activationStrategy to an object |
if (!$activationStrategy instanceof ActivationStrategyInterface) { |
$activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); |
} |
|
$this->handler = $handler; |
$this->activationStrategy = $activationStrategy; |
$this->bufferSize = $bufferSize; |
$this->bubble = $bubble; |
$this->stopBuffering = $stopBuffering; |
|
if ($passthruLevel !== null) { |
$this->passthruLevel = Logger::toMonologLevel($passthruLevel); |
} |
|
if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { |
throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); |
} |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function isHandling(array $record) |
{ |
return true; |
} |
|
/** |
* Manually activate this logger regardless of the activation strategy |
*/ |
public function activate() |
{ |
if ($this->stopBuffering) { |
$this->buffering = false; |
} |
if (!$this->handler instanceof HandlerInterface) { |
$record = end($this->buffer) ?: null; |
|
$this->handler = call_user_func($this->handler, $record, $this); |
if (!$this->handler instanceof HandlerInterface) { |
throw new \RuntimeException("The factory callable should return a HandlerInterface"); |
} |
} |
$this->handler->handleBatch($this->buffer); |
$this->buffer = array(); |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function handle(array $record) |
{ |
if ($this->processors) { |
foreach ($this->processors as $processor) { |
$record = call_user_func($processor, $record); |
} |
} |
|
if ($this->buffering) { |
$this->buffer[] = $record; |
if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { |
array_shift($this->buffer); |
} |
if ($this->activationStrategy->isHandlerActivated($record)) { |
$this->activate(); |
} |
} else { |
$this->handler->handle($record); |
} |
|
return false === $this->bubble; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function close() |
{ |
if (null !== $this->passthruLevel) { |
$level = $this->passthruLevel; |
$this->buffer = array_filter($this->buffer, function ($record) use ($level) { |
return $record['level'] >= $level; |
}); |
if (count($this->buffer) > 0) { |
$this->handler->handleBatch($this->buffer); |
$this->buffer = array(); |
} |
} |
} |
|
/** |
* Resets the state of the handler. Stops forwarding records to the wrapped handler. |
*/ |
public function reset() |
{ |
$this->buffering = true; |
} |
|
/** |
* Clears the buffer without flushing any messages down to the wrapped handler. |
* |
* It also resets the handler to its initial buffering state. |
*/ |
public function clear() |
{ |
$this->buffer = array(); |
$this->reset(); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php |
@@ -0,0 +1,195 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Formatter\WildfireFormatter; |
|
/** |
* Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. |
* |
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com> |
*/ |
class FirePHPHandler extends AbstractProcessingHandler |
{ |
/** |
* WildFire JSON header message format |
*/ |
const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; |
|
/** |
* FirePHP structure for parsing messages & their presentation |
*/ |
const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; |
|
/** |
* Must reference a "known" plugin, otherwise headers won't display in FirePHP |
*/ |
const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; |
|
/** |
* Header prefix for Wildfire to recognize & parse headers |
*/ |
const HEADER_PREFIX = 'X-Wf'; |
|
/** |
* Whether or not Wildfire vendor-specific headers have been generated & sent yet |
*/ |
protected static $initialized = false; |
|
/** |
* Shared static message index between potentially multiple handlers |
* @var int |
*/ |
protected static $messageIndex = 1; |
|
protected static $sendHeaders = true; |
|
/** |
* Base header creation function used by init headers & record headers |
* |
* @param array $meta Wildfire Plugin, Protocol & Structure Indexes |
* @param string $message Log message |
* @return array Complete header string ready for the client as key and message as value |
*/ |
protected function createHeader(array $meta, $message) |
{ |
$header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); |
|
return array($header => $message); |
} |
|
/** |
* Creates message header from record |
* |
* @see createHeader() |
* @param array $record |
* @return string |
*/ |
protected function createRecordHeader(array $record) |
{ |
// Wildfire is extensible to support multiple protocols & plugins in a single request, |
// but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. |
return $this->createHeader( |
array(1, 1, 1, self::$messageIndex++), |
$record['formatted'] |
); |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function getDefaultFormatter() |
{ |
return new WildfireFormatter(); |
} |
|
/** |
* Wildfire initialization headers to enable message parsing |
* |
* @see createHeader() |
* @see sendHeader() |
* @return array |
*/ |
protected function getInitHeaders() |
{ |
// Initial payload consists of required headers for Wildfire |
return array_merge( |
$this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), |
$this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), |
$this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) |
); |
} |
|
/** |
* Send header string to the client |
* |
* @param string $header |
* @param string $content |
*/ |
protected function sendHeader($header, $content) |
{ |
if (!headers_sent() && self::$sendHeaders) { |
header(sprintf('%s: %s', $header, $content)); |
} |
} |
|
/** |
* Creates & sends header for a record, ensuring init headers have been sent prior |
* |
* @see sendHeader() |
* @see sendInitHeaders() |
* @param array $record |
*/ |
protected function write(array $record) |
{ |
if (!self::$sendHeaders) { |
return; |
} |
|
// WildFire-specific headers must be sent prior to any messages |
if (!self::$initialized) { |
self::$initialized = true; |
|
self::$sendHeaders = $this->headersAccepted(); |
if (!self::$sendHeaders) { |
return; |
} |
|
foreach ($this->getInitHeaders() as $header => $content) { |
$this->sendHeader($header, $content); |
} |
} |
|
$header = $this->createRecordHeader($record); |
if (trim(current($header)) !== '') { |
$this->sendHeader(key($header), current($header)); |
} |
} |
|
/** |
* Verifies if the headers are accepted by the current user agent |
* |
* @return Boolean |
*/ |
protected function headersAccepted() |
{ |
if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { |
return true; |
} |
|
return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); |
} |
|
/** |
* BC getter for the sendHeaders property that has been made static |
*/ |
public function __get($property) |
{ |
if ('sendHeaders' !== $property) { |
throw new \InvalidArgumentException('Undefined property '.$property); |
} |
|
return static::$sendHeaders; |
} |
|
/** |
* BC setter for the sendHeaders property that has been made static |
*/ |
public function __set($property, $value) |
{ |
if ('sendHeaders' !== $property) { |
throw new \InvalidArgumentException('Undefined property '.$property); |
} |
|
static::$sendHeaders = $value; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php |
@@ -0,0 +1,350 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* Sends notifications through the hipchat api to a hipchat room |
* |
* Notes: |
* API token - HipChat API token |
* Room - HipChat Room Id or name, where messages are sent |
* Name - Name used to send the message (from) |
* notify - Should the message trigger a notification in the clients |
* version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) |
* |
* @author Rafael Dohms <rafael@doh.ms> |
* @see https://www.hipchat.com/docs/api |
*/ |
class HipChatHandler extends SocketHandler |
{ |
/** |
* Use API version 1 |
*/ |
const API_V1 = 'v1'; |
|
/** |
* Use API version v2 |
*/ |
const API_V2 = 'v2'; |
|
/** |
* The maximum allowed length for the name used in the "from" field. |
*/ |
const MAXIMUM_NAME_LENGTH = 15; |
|
/** |
* The maximum allowed length for the message. |
*/ |
const MAXIMUM_MESSAGE_LENGTH = 9500; |
|
/** |
* @var string |
*/ |
private $token; |
|
/** |
* @var string |
*/ |
private $room; |
|
/** |
* @var string |
*/ |
private $name; |
|
/** |
* @var bool |
*/ |
private $notify; |
|
/** |
* @var string |
*/ |
private $format; |
|
/** |
* @var string |
*/ |
private $host; |
|
/** |
* @var string |
*/ |
private $version; |
|
/** |
* @param string $token HipChat API Token |
* @param string $room The room that should be alerted of the message (Id or Name) |
* @param string $name Name used in the "from" field. |
* @param bool $notify Trigger a notification in clients or not |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not |
* @param bool $useSSL Whether to connect via SSL. |
* @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) |
* @param string $host The HipChat server hostname. |
* @param string $version The HipChat API version (default HipChatHandler::API_V1) |
*/ |
public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) |
{ |
if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { |
throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); |
} |
|
$connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; |
parent::__construct($connectionString, $level, $bubble); |
|
$this->token = $token; |
$this->name = $name; |
$this->notify = $notify; |
$this->room = $room; |
$this->format = $format; |
$this->host = $host; |
$this->version = $version; |
} |
|
/** |
* {@inheritdoc} |
* |
* @param array $record |
* @return string |
*/ |
protected function generateDataStream($record) |
{ |
$content = $this->buildContent($record); |
|
return $this->buildHeader($content) . $content; |
} |
|
/** |
* Builds the body of API call |
* |
* @param array $record |
* @return string |
*/ |
private function buildContent($record) |
{ |
$dataArray = array( |
'notify' => $this->version == self::API_V1 ? |
($this->notify ? 1 : 0) : |
($this->notify ? 'true' : 'false'), |
'message' => $record['formatted'], |
'message_format' => $this->format, |
'color' => $this->getAlertColor($record['level']), |
); |
|
if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { |
if (function_exists('mb_substr')) { |
$dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; |
} else { |
$dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; |
} |
} |
|
// if we are using the legacy API then we need to send some additional information |
if ($this->version == self::API_V1) { |
$dataArray['room_id'] = $this->room; |
} |
|
// append the sender name if it is set |
// always append it if we use the v1 api (it is required in v1) |
if ($this->version == self::API_V1 || $this->name !== null) { |
$dataArray['from'] = (string) $this->name; |
} |
|
return http_build_query($dataArray); |
} |
|
/** |
* Builds the header of the API Call |
* |
* @param string $content |
* @return string |
*/ |
private function buildHeader($content) |
{ |
if ($this->version == self::API_V1) { |
$header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; |
} else { |
// needed for rooms with special (spaces, etc) characters in the name |
$room = rawurlencode($this->room); |
$header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; |
} |
|
$header .= "Host: {$this->host}\r\n"; |
$header .= "Content-Type: application/x-www-form-urlencoded\r\n"; |
$header .= "Content-Length: " . strlen($content) . "\r\n"; |
$header .= "\r\n"; |
|
return $header; |
} |
|
/** |
* Assigns a color to each level of log records. |
* |
* @param int $level |
* @return string |
*/ |
protected function getAlertColor($level) |
{ |
switch (true) { |
case $level >= Logger::ERROR: |
return 'red'; |
case $level >= Logger::WARNING: |
return 'yellow'; |
case $level >= Logger::INFO: |
return 'green'; |
case $level == Logger::DEBUG: |
return 'gray'; |
default: |
return 'yellow'; |
} |
} |
|
/** |
* {@inheritdoc} |
* |
* @param array $record |
*/ |
protected function write(array $record) |
{ |
parent::write($record); |
$this->closeSocket(); |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function handleBatch(array $records) |
{ |
if (count($records) == 0) { |
return true; |
} |
|
$batchRecords = $this->combineRecords($records); |
|
$handled = false; |
foreach ($batchRecords as $batchRecord) { |
if ($this->isHandling($batchRecord)) { |
$this->write($batchRecord); |
$handled = true; |
} |
} |
|
if (!$handled) { |
return false; |
} |
|
return false === $this->bubble; |
} |
|
/** |
* Combines multiple records into one. Error level of the combined record |
* will be the highest level from the given records. Datetime will be taken |
* from the first record. |
* |
* @param $records |
* @return array |
*/ |
private function combineRecords($records) |
{ |
$batchRecord = null; |
$batchRecords = array(); |
$messages = array(); |
$formattedMessages = array(); |
$level = 0; |
$levelName = null; |
$datetime = null; |
|
foreach ($records as $record) { |
$record = $this->processRecord($record); |
|
if ($record['level'] > $level) { |
$level = $record['level']; |
$levelName = $record['level_name']; |
} |
|
if (null === $datetime) { |
$datetime = $record['datetime']; |
} |
|
$messages[] = $record['message']; |
$messageStr = implode(PHP_EOL, $messages); |
$formattedMessages[] = $this->getFormatter()->format($record); |
$formattedMessageStr = implode('', $formattedMessages); |
|
$batchRecord = array( |
'message' => $messageStr, |
'formatted' => $formattedMessageStr, |
'context' => array(), |
'extra' => array(), |
); |
|
if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { |
// Pop the last message and implode the remaining messages |
$lastMessage = array_pop($messages); |
$lastFormattedMessage = array_pop($formattedMessages); |
$batchRecord['message'] = implode(PHP_EOL, $messages); |
$batchRecord['formatted'] = implode('', $formattedMessages); |
|
$batchRecords[] = $batchRecord; |
$messages = array($lastMessage); |
$formattedMessages = array($lastFormattedMessage); |
|
$batchRecord = null; |
} |
} |
|
if (null !== $batchRecord) { |
$batchRecords[] = $batchRecord; |
} |
|
// Set the max level and datetime for all records |
foreach ($batchRecords as &$batchRecord) { |
$batchRecord = array_merge( |
$batchRecord, |
array( |
'level' => $level, |
'level_name' => $levelName, |
'datetime' => $datetime, |
) |
); |
} |
|
return $batchRecords; |
} |
|
/** |
* Validates the length of a string. |
* |
* If the `mb_strlen()` function is available, it will use that, as HipChat |
* allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. |
* |
* Note that this might cause false failures in the specific case of using |
* a valid name with less than 16 characters, but 16 or more bytes, on a |
* system where `mb_strlen()` is unavailable. |
* |
* @param string $str |
* @param int $length |
* |
* @return bool |
*/ |
private function validateStringLength($str, $length) |
{ |
if (function_exists('mb_strlen')) { |
return (mb_strlen($str) <= $length); |
} |
|
return (strlen($str) <= $length); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php |
@@ -0,0 +1,185 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
use Monolog\Formatter\LineFormatter; |
|
/** |
* NativeMailerHandler uses the mail() function to send the emails |
* |
* @author Christophe Coevoet <stof@notk.org> |
* @author Mark Garrett <mark@moderndeveloperllc.com> |
*/ |
class NativeMailerHandler extends MailHandler |
{ |
/** |
* The email addresses to which the message will be sent |
* @var array |
*/ |
protected $to; |
|
/** |
* The subject of the email |
* @var string |
*/ |
protected $subject; |
|
/** |
* Optional headers for the message |
* @var array |
*/ |
protected $headers = array(); |
|
/** |
* Optional parameters for the message |
* @var array |
*/ |
protected $parameters = array(); |
|
/** |
* The wordwrap length for the message |
* @var int |
*/ |
protected $maxColumnWidth; |
|
/** |
* The Content-type for the message |
* @var string |
*/ |
protected $contentType = 'text/plain'; |
|
/** |
* The encoding for the message |
* @var string |
*/ |
protected $encoding = 'utf-8'; |
|
/** |
* @param string|array $to The receiver of the mail |
* @param string $subject The subject of the mail |
* @param string $from The sender of the mail |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not |
* @param int $maxColumnWidth The maximum column width that the message lines will have |
*/ |
public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) |
{ |
parent::__construct($level, $bubble); |
$this->to = is_array($to) ? $to : array($to); |
$this->subject = $subject; |
$this->addHeader(sprintf('From: %s', $from)); |
$this->maxColumnWidth = $maxColumnWidth; |
} |
|
/** |
* Add headers to the message |
* |
* @param string|array $headers Custom added headers |
* @return self |
*/ |
public function addHeader($headers) |
{ |
foreach ((array) $headers as $header) { |
if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { |
throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); |
} |
$this->headers[] = $header; |
} |
|
return $this; |
} |
|
/** |
* Add parameters to the message |
* |
* @param string|array $parameters Custom added parameters |
* @return self |
*/ |
public function addParameter($parameters) |
{ |
$this->parameters = array_merge($this->parameters, (array) $parameters); |
|
return $this; |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function send($content, array $records) |
{ |
$content = wordwrap($content, $this->maxColumnWidth); |
$headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); |
$headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; |
if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { |
$headers .= 'MIME-Version: 1.0' . "\r\n"; |
} |
|
$subject = $this->subject; |
if ($records) { |
$subjectFormatter = new LineFormatter($this->subject); |
$subject = $subjectFormatter->format($this->getHighestRecord($records)); |
} |
|
$parameters = implode(' ', $this->parameters); |
foreach ($this->to as $to) { |
mail($to, $subject, $content, $headers, $parameters); |
} |
} |
|
/** |
* @return string $contentType |
*/ |
public function getContentType() |
{ |
return $this->contentType; |
} |
|
/** |
* @return string $encoding |
*/ |
public function getEncoding() |
{ |
return $this->encoding; |
} |
|
/** |
* @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML |
* messages. |
* @return self |
*/ |
public function setContentType($contentType) |
{ |
if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { |
throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); |
} |
|
$this->contentType = $contentType; |
|
return $this; |
} |
|
/** |
* @param string $encoding |
* @return self |
*/ |
public function setEncoding($encoding) |
{ |
if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { |
throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); |
} |
|
$this->encoding = $encoding; |
|
return $this; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php |
@@ -0,0 +1,202 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
use Monolog\Formatter\NormalizerFormatter; |
|
/** |
* Class to record a log on a NewRelic application. |
* Enabling New Relic High Security mode may prevent capture of useful information. |
* |
* @see https://docs.newrelic.com/docs/agents/php-agent |
* @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security |
*/ |
class NewRelicHandler extends AbstractProcessingHandler |
{ |
/** |
* Name of the New Relic application that will receive logs from this handler. |
* |
* @var string |
*/ |
protected $appName; |
|
/** |
* Name of the current transaction |
* |
* @var string |
*/ |
protected $transactionName; |
|
/** |
* Some context and extra data is passed into the handler as arrays of values. Do we send them as is |
* (useful if we are using the API), or explode them for display on the NewRelic RPM website? |
* |
* @var bool |
*/ |
protected $explodeArrays; |
|
/** |
* {@inheritDoc} |
* |
* @param string $appName |
* @param bool $explodeArrays |
* @param string $transactionName |
*/ |
public function __construct( |
$level = Logger::ERROR, |
$bubble = true, |
$appName = null, |
$explodeArrays = false, |
$transactionName = null |
) { |
parent::__construct($level, $bubble); |
|
$this->appName = $appName; |
$this->explodeArrays = $explodeArrays; |
$this->transactionName = $transactionName; |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function write(array $record) |
{ |
if (!$this->isNewRelicEnabled()) { |
throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); |
} |
|
if ($appName = $this->getAppName($record['context'])) { |
$this->setNewRelicAppName($appName); |
} |
|
if ($transactionName = $this->getTransactionName($record['context'])) { |
$this->setNewRelicTransactionName($transactionName); |
unset($record['formatted']['context']['transaction_name']); |
} |
|
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { |
newrelic_notice_error($record['message'], $record['context']['exception']); |
unset($record['formatted']['context']['exception']); |
} else { |
newrelic_notice_error($record['message']); |
} |
|
if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { |
foreach ($record['formatted']['context'] as $key => $parameter) { |
if (is_array($parameter) && $this->explodeArrays) { |
foreach ($parameter as $paramKey => $paramValue) { |
$this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); |
} |
} else { |
$this->setNewRelicParameter('context_' . $key, $parameter); |
} |
} |
} |
|
if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { |
foreach ($record['formatted']['extra'] as $key => $parameter) { |
if (is_array($parameter) && $this->explodeArrays) { |
foreach ($parameter as $paramKey => $paramValue) { |
$this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); |
} |
} else { |
$this->setNewRelicParameter('extra_' . $key, $parameter); |
} |
} |
} |
} |
|
/** |
* Checks whether the NewRelic extension is enabled in the system. |
* |
* @return bool |
*/ |
protected function isNewRelicEnabled() |
{ |
return extension_loaded('newrelic'); |
} |
|
/** |
* Returns the appname where this log should be sent. Each log can override the default appname, set in this |
* handler's constructor, by providing the appname in it's context. |
* |
* @param array $context |
* @return null|string |
*/ |
protected function getAppName(array $context) |
{ |
if (isset($context['appname'])) { |
return $context['appname']; |
} |
|
return $this->appName; |
} |
|
/** |
* Returns the name of the current transaction. Each log can override the default transaction name, set in this |
* handler's constructor, by providing the transaction_name in it's context |
* |
* @param array $context |
* |
* @return null|string |
*/ |
protected function getTransactionName(array $context) |
{ |
if (isset($context['transaction_name'])) { |
return $context['transaction_name']; |
} |
|
return $this->transactionName; |
} |
|
/** |
* Sets the NewRelic application that should receive this log. |
* |
* @param string $appName |
*/ |
protected function setNewRelicAppName($appName) |
{ |
newrelic_set_appname($appName); |
} |
|
/** |
* Overwrites the name of the current transaction |
* |
* @param string $transactionName |
*/ |
protected function setNewRelicTransactionName($transactionName) |
{ |
newrelic_name_transaction($transactionName); |
} |
|
/** |
* @param string $key |
* @param mixed $value |
*/ |
protected function setNewRelicParameter($key, $value) |
{ |
if (null === $value || is_scalar($value)) { |
newrelic_add_custom_parameter($key, $value); |
} else { |
newrelic_add_custom_parameter($key, @json_encode($value)); |
} |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function getDefaultFormatter() |
{ |
return new NormalizerFormatter(); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php |
@@ -0,0 +1,242 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Exception; |
use Monolog\Formatter\LineFormatter; |
use Monolog\Logger; |
use PhpConsole\Connector; |
use PhpConsole\Handler; |
use PhpConsole\Helper; |
|
/** |
* Monolog handler for Google Chrome extension "PHP Console" |
* |
* Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely |
* |
* Usage: |
* 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef |
* 2. See overview https://github.com/barbushin/php-console#overview |
* 3. Install PHP Console library https://github.com/barbushin/php-console#installation |
* 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) |
* |
* $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); |
* \Monolog\ErrorHandler::register($logger); |
* echo $undefinedVar; |
* $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); |
* PC::debug($_SERVER); // PHP Console debugger for any type of vars |
* |
* @author Sergey Barbushin https://www.linkedin.com/in/barbushin |
*/ |
class PHPConsoleHandler extends AbstractProcessingHandler |
{ |
private $options = array( |
'enabled' => true, // bool Is PHP Console server enabled |
'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... |
'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled |
'useOwnErrorsHandler' => false, // bool Enable errors handling |
'useOwnExceptionsHandler' => false, // bool Enable exceptions handling |
'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths |
'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') |
'serverEncoding' => null, // string|null Server internal encoding |
'headersLimit' => null, // int|null Set headers size limit for your web-server |
'password' => null, // string|null Protect PHP Console connection by password |
'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed |
'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') |
'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) |
'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings |
'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level |
'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number |
'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item |
'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON |
'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug |
'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) |
); |
|
/** @var Connector */ |
private $connector; |
|
/** |
* @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details |
* @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) |
* @param int $level |
* @param bool $bubble |
* @throws Exception |
*/ |
public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) |
{ |
if (!class_exists('PhpConsole\Connector')) { |
throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); |
} |
parent::__construct($level, $bubble); |
$this->options = $this->initOptions($options); |
$this->connector = $this->initConnector($connector); |
} |
|
private function initOptions(array $options) |
{ |
$wrongOptions = array_diff(array_keys($options), array_keys($this->options)); |
if ($wrongOptions) { |
throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); |
} |
|
return array_replace($this->options, $options); |
} |
|
private function initConnector(Connector $connector = null) |
{ |
if (!$connector) { |
if ($this->options['dataStorage']) { |
Connector::setPostponeStorage($this->options['dataStorage']); |
} |
$connector = Connector::getInstance(); |
} |
|
if ($this->options['registerHelper'] && !Helper::isRegistered()) { |
Helper::register(); |
} |
|
if ($this->options['enabled'] && $connector->isActiveClient()) { |
if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { |
$handler = Handler::getInstance(); |
$handler->setHandleErrors($this->options['useOwnErrorsHandler']); |
$handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); |
$handler->start(); |
} |
if ($this->options['sourcesBasePath']) { |
$connector->setSourcesBasePath($this->options['sourcesBasePath']); |
} |
if ($this->options['serverEncoding']) { |
$connector->setServerEncoding($this->options['serverEncoding']); |
} |
if ($this->options['password']) { |
$connector->setPassword($this->options['password']); |
} |
if ($this->options['enableSslOnlyMode']) { |
$connector->enableSslOnlyMode(); |
} |
if ($this->options['ipMasks']) { |
$connector->setAllowedIpMasks($this->options['ipMasks']); |
} |
if ($this->options['headersLimit']) { |
$connector->setHeadersLimit($this->options['headersLimit']); |
} |
if ($this->options['detectDumpTraceAndSource']) { |
$connector->getDebugDispatcher()->detectTraceAndSource = true; |
} |
$dumper = $connector->getDumper(); |
$dumper->levelLimit = $this->options['dumperLevelLimit']; |
$dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; |
$dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; |
$dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; |
$dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; |
if ($this->options['enableEvalListener']) { |
$connector->startEvalRequestsListener(); |
} |
} |
|
return $connector; |
} |
|
public function getConnector() |
{ |
return $this->connector; |
} |
|
public function getOptions() |
{ |
return $this->options; |
} |
|
public function handle(array $record) |
{ |
if ($this->options['enabled'] && $this->connector->isActiveClient()) { |
return parent::handle($record); |
} |
|
return !$this->bubble; |
} |
|
/** |
* Writes the record down to the log of the implementing handler |
* |
* @param array $record |
* @return void |
*/ |
protected function write(array $record) |
{ |
if ($record['level'] < Logger::NOTICE) { |
$this->handleDebugRecord($record); |
} elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { |
$this->handleExceptionRecord($record); |
} else { |
$this->handleErrorRecord($record); |
} |
} |
|
private function handleDebugRecord(array $record) |
{ |
$tags = $this->getRecordTags($record); |
$message = $record['message']; |
if ($record['context']) { |
$message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context']))); |
} |
$this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); |
} |
|
private function handleExceptionRecord(array $record) |
{ |
$this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); |
} |
|
private function handleErrorRecord(array $record) |
{ |
$context = $record['context']; |
|
$this->connector->getErrorsDispatcher()->dispatchError( |
isset($context['code']) ? $context['code'] : null, |
isset($context['message']) ? $context['message'] : $record['message'], |
isset($context['file']) ? $context['file'] : null, |
isset($context['line']) ? $context['line'] : null, |
$this->options['classesPartialsTraceIgnore'] |
); |
} |
|
private function getRecordTags(array &$record) |
{ |
$tags = null; |
if (!empty($record['context'])) { |
$context = & $record['context']; |
foreach ($this->options['debugTagsKeysInContext'] as $key) { |
if (!empty($context[$key])) { |
$tags = $context[$key]; |
if ($key === 0) { |
array_shift($context); |
} else { |
unset($context[$key]); |
} |
break; |
} |
} |
} |
|
return $tags ?: strtolower($record['level_name']); |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function getDefaultFormatter() |
{ |
return new LineFormatter('%message%'); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php |
@@ -0,0 +1,185 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* Sends notifications through the pushover api to mobile phones |
* |
* @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com> |
* @see https://www.pushover.net/api |
*/ |
class PushoverHandler extends SocketHandler |
{ |
private $token; |
private $users; |
private $title; |
private $user; |
private $retry; |
private $expire; |
|
private $highPriorityLevel; |
private $emergencyLevel; |
private $useFormattedMessage = false; |
|
/** |
* All parameters that can be sent to Pushover |
* @see https://pushover.net/api |
* @var array |
*/ |
private $parameterNames = array( |
'token' => true, |
'user' => true, |
'message' => true, |
'device' => true, |
'title' => true, |
'url' => true, |
'url_title' => true, |
'priority' => true, |
'timestamp' => true, |
'sound' => true, |
'retry' => true, |
'expire' => true, |
'callback' => true, |
); |
|
/** |
* Sounds the api supports by default |
* @see https://pushover.net/api#sounds |
* @var array |
*/ |
private $sounds = array( |
'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', |
'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', |
'persistent', 'echo', 'updown', 'none', |
); |
|
/** |
* @param string $token Pushover api token |
* @param string|array $users Pushover user id or array of ids the message will be sent to |
* @param string $title Title sent to the Pushover API |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
* @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not |
* the pushover.net app owner. OpenSSL is required for this option. |
* @param int $highPriorityLevel The minimum logging level at which this handler will start |
* sending "high priority" requests to the Pushover API |
* @param int $emergencyLevel The minimum logging level at which this handler will start |
* sending "emergency" requests to the Pushover API |
* @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. |
* @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). |
*/ |
public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) |
{ |
$connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; |
parent::__construct($connectionString, $level, $bubble); |
|
$this->token = $token; |
$this->users = (array) $users; |
$this->title = $title ?: gethostname(); |
$this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); |
$this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); |
$this->retry = $retry; |
$this->expire = $expire; |
} |
|
protected function generateDataStream($record) |
{ |
$content = $this->buildContent($record); |
|
return $this->buildHeader($content) . $content; |
} |
|
private function buildContent($record) |
{ |
// Pushover has a limit of 512 characters on title and message combined. |
$maxMessageLength = 512 - strlen($this->title); |
|
$message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; |
$message = substr($message, 0, $maxMessageLength); |
|
$timestamp = $record['datetime']->getTimestamp(); |
|
$dataArray = array( |
'token' => $this->token, |
'user' => $this->user, |
'message' => $message, |
'title' => $this->title, |
'timestamp' => $timestamp, |
); |
|
if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { |
$dataArray['priority'] = 2; |
$dataArray['retry'] = $this->retry; |
$dataArray['expire'] = $this->expire; |
} elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { |
$dataArray['priority'] = 1; |
} |
|
// First determine the available parameters |
$context = array_intersect_key($record['context'], $this->parameterNames); |
$extra = array_intersect_key($record['extra'], $this->parameterNames); |
|
// Least important info should be merged with subsequent info |
$dataArray = array_merge($extra, $context, $dataArray); |
|
// Only pass sounds that are supported by the API |
if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { |
unset($dataArray['sound']); |
} |
|
return http_build_query($dataArray); |
} |
|
private function buildHeader($content) |
{ |
$header = "POST /1/messages.json HTTP/1.1\r\n"; |
$header .= "Host: api.pushover.net\r\n"; |
$header .= "Content-Type: application/x-www-form-urlencoded\r\n"; |
$header .= "Content-Length: " . strlen($content) . "\r\n"; |
$header .= "\r\n"; |
|
return $header; |
} |
|
protected function write(array $record) |
{ |
foreach ($this->users as $user) { |
$this->user = $user; |
|
parent::write($record); |
$this->closeSocket(); |
} |
|
$this->user = null; |
} |
|
public function setHighPriorityLevel($value) |
{ |
$this->highPriorityLevel = $value; |
} |
|
public function setEmergencyLevel($value) |
{ |
$this->emergencyLevel = $value; |
} |
|
/** |
* Use the formatted message? |
* @param bool $value |
*/ |
public function useFormattedMessage($value) |
{ |
$this->useFormattedMessage = (boolean) $value; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php |
@@ -0,0 +1,232 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Formatter\LineFormatter; |
use Monolog\Formatter\FormatterInterface; |
use Monolog\Logger; |
use Raven_Client; |
|
/** |
* Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server |
* using raven-php (https://github.com/getsentry/raven-php) |
* |
* @author Marc Abramowitz <marc@marc-abramowitz.com> |
*/ |
class RavenHandler extends AbstractProcessingHandler |
{ |
/** |
* Translates Monolog log levels to Raven log levels. |
*/ |
private $logLevels = array( |
Logger::DEBUG => Raven_Client::DEBUG, |
Logger::INFO => Raven_Client::INFO, |
Logger::NOTICE => Raven_Client::INFO, |
Logger::WARNING => Raven_Client::WARNING, |
Logger::ERROR => Raven_Client::ERROR, |
Logger::CRITICAL => Raven_Client::FATAL, |
Logger::ALERT => Raven_Client::FATAL, |
Logger::EMERGENCY => Raven_Client::FATAL, |
); |
|
/** |
* @var string should represent the current version of the calling |
* software. Can be any string (git commit, version number) |
*/ |
private $release; |
|
/** |
* @var Raven_Client the client object that sends the message to the server |
*/ |
protected $ravenClient; |
|
/** |
* @var LineFormatter The formatter to use for the logs generated via handleBatch() |
*/ |
protected $batchFormatter; |
|
/** |
* @param Raven_Client $ravenClient |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
*/ |
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) |
{ |
parent::__construct($level, $bubble); |
|
$this->ravenClient = $ravenClient; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function handleBatch(array $records) |
{ |
$level = $this->level; |
|
// filter records based on their level |
$records = array_filter($records, function ($record) use ($level) { |
return $record['level'] >= $level; |
}); |
|
if (!$records) { |
return; |
} |
|
// the record with the highest severity is the "main" one |
$record = array_reduce($records, function ($highest, $record) { |
if ($record['level'] > $highest['level']) { |
return $record; |
} |
|
return $highest; |
}); |
|
// the other ones are added as a context item |
$logs = array(); |
foreach ($records as $r) { |
$logs[] = $this->processRecord($r); |
} |
|
if ($logs) { |
$record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); |
} |
|
$this->handle($record); |
} |
|
/** |
* Sets the formatter for the logs generated by handleBatch(). |
* |
* @param FormatterInterface $formatter |
*/ |
public function setBatchFormatter(FormatterInterface $formatter) |
{ |
$this->batchFormatter = $formatter; |
} |
|
/** |
* Gets the formatter for the logs generated by handleBatch(). |
* |
* @return FormatterInterface |
*/ |
public function getBatchFormatter() |
{ |
if (!$this->batchFormatter) { |
$this->batchFormatter = $this->getDefaultBatchFormatter(); |
} |
|
return $this->batchFormatter; |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function write(array $record) |
{ |
$previousUserContext = false; |
$options = array(); |
$options['level'] = $this->logLevels[$record['level']]; |
$options['tags'] = array(); |
if (!empty($record['extra']['tags'])) { |
$options['tags'] = array_merge($options['tags'], $record['extra']['tags']); |
unset($record['extra']['tags']); |
} |
if (!empty($record['context']['tags'])) { |
$options['tags'] = array_merge($options['tags'], $record['context']['tags']); |
unset($record['context']['tags']); |
} |
if (!empty($record['context']['fingerprint'])) { |
$options['fingerprint'] = $record['context']['fingerprint']; |
unset($record['context']['fingerprint']); |
} |
if (!empty($record['context']['logger'])) { |
$options['logger'] = $record['context']['logger']; |
unset($record['context']['logger']); |
} else { |
$options['logger'] = $record['channel']; |
} |
foreach ($this->getExtraParameters() as $key) { |
foreach (array('extra', 'context') as $source) { |
if (!empty($record[$source][$key])) { |
$options[$key] = $record[$source][$key]; |
unset($record[$source][$key]); |
} |
} |
} |
if (!empty($record['context'])) { |
$options['extra']['context'] = $record['context']; |
if (!empty($record['context']['user'])) { |
$previousUserContext = $this->ravenClient->context->user; |
$this->ravenClient->user_context($record['context']['user']); |
unset($options['extra']['context']['user']); |
} |
} |
if (!empty($record['extra'])) { |
$options['extra']['extra'] = $record['extra']; |
} |
|
if (!empty($this->release) && !isset($options['release'])) { |
$options['release'] = $this->release; |
} |
|
if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { |
$options['extra']['message'] = $record['formatted']; |
$this->ravenClient->captureException($record['context']['exception'], $options); |
} else { |
$this->ravenClient->captureMessage($record['formatted'], array(), $options); |
} |
|
if ($previousUserContext !== false) { |
$this->ravenClient->user_context($previousUserContext); |
} |
} |
|
/** |
* {@inheritDoc} |
*/ |
protected function getDefaultFormatter() |
{ |
return new LineFormatter('[%channel%] %message%'); |
} |
|
/** |
* Gets the default formatter for the logs generated by handleBatch(). |
* |
* @return FormatterInterface |
*/ |
protected function getDefaultBatchFormatter() |
{ |
return new LineFormatter(); |
} |
|
/** |
* Gets extra parameters supported by Raven that can be found in "extra" and "context" |
* |
* @return array |
*/ |
protected function getExtraParameters() |
{ |
return array('checksum', 'release', 'event_id'); |
} |
|
/** |
* @param string $value |
* @return self |
*/ |
public function setRelease($value) |
{ |
$this->release = $value; |
|
return $this; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php |
@@ -0,0 +1,132 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use RollbarNotifier; |
use Exception; |
use Monolog\Logger; |
|
/** |
* Sends errors to Rollbar |
* |
* If the context data contains a `payload` key, that is used as an array |
* of payload options to RollbarNotifier's report_message/report_exception methods. |
* |
* Rollbar's context info will contain the context + extra keys from the log record |
* merged, and then on top of that a few keys: |
* |
* - level (rollbar level name) |
* - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) |
* - channel |
* - datetime (unix timestamp) |
* |
* @author Paul Statezny <paulstatezny@gmail.com> |
*/ |
class RollbarHandler extends AbstractProcessingHandler |
{ |
/** |
* Rollbar notifier |
* |
* @var RollbarNotifier |
*/ |
protected $rollbarNotifier; |
|
protected $levelMap = array( |
Logger::DEBUG => 'debug', |
Logger::INFO => 'info', |
Logger::NOTICE => 'info', |
Logger::WARNING => 'warning', |
Logger::ERROR => 'error', |
Logger::CRITICAL => 'critical', |
Logger::ALERT => 'critical', |
Logger::EMERGENCY => 'critical', |
); |
|
/** |
* Records whether any log records have been added since the last flush of the rollbar notifier |
* |
* @var bool |
*/ |
private $hasRecords = false; |
|
protected $initialized = false; |
|
/** |
* @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not |
*/ |
public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) |
{ |
$this->rollbarNotifier = $rollbarNotifier; |
|
parent::__construct($level, $bubble); |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function write(array $record) |
{ |
if (!$this->initialized) { |
// __destructor() doesn't get called on Fatal errors |
register_shutdown_function(array($this, 'close')); |
$this->initialized = true; |
} |
|
$context = $record['context']; |
$payload = array(); |
if (isset($context['payload'])) { |
$payload = $context['payload']; |
unset($context['payload']); |
} |
$context = array_merge($context, $record['extra'], array( |
'level' => $this->levelMap[$record['level']], |
'monolog_level' => $record['level_name'], |
'channel' => $record['channel'], |
'datetime' => $record['datetime']->format('U'), |
)); |
|
if (isset($context['exception']) && $context['exception'] instanceof Exception) { |
$payload['level'] = $context['level']; |
$exception = $context['exception']; |
unset($context['exception']); |
|
$this->rollbarNotifier->report_exception($exception, $context, $payload); |
} else { |
$this->rollbarNotifier->report_message( |
$record['message'], |
$context['level'], |
$context, |
$payload |
); |
} |
|
$this->hasRecords = true; |
} |
|
public function flush() |
{ |
if ($this->hasRecords) { |
$this->rollbarNotifier->flush(); |
$this->hasRecords = false; |
} |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function close() |
{ |
$this->flush(); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php |
@@ -0,0 +1,178 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* Stores logs to files that are rotated every day and a limited number of files are kept. |
* |
* This rotation is only intended to be used as a workaround. Using logrotate to |
* handle the rotation is strongly encouraged when you can use it. |
* |
* @author Christophe Coevoet <stof@notk.org> |
* @author Jordi Boggiano <j.boggiano@seld.be> |
*/ |
class RotatingFileHandler extends StreamHandler |
{ |
const FILE_PER_DAY = 'Y-m-d'; |
const FILE_PER_MONTH = 'Y-m'; |
const FILE_PER_YEAR = 'Y'; |
|
protected $filename; |
protected $maxFiles; |
protected $mustRotate; |
protected $nextRotation; |
protected $filenameFormat; |
protected $dateFormat; |
|
/** |
* @param string $filename |
* @param int $maxFiles The maximal amount of files to keep (0 means unlimited) |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) |
* @param Boolean $useLocking Try to lock log file before doing any writes |
*/ |
public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) |
{ |
$this->filename = $filename; |
$this->maxFiles = (int) $maxFiles; |
$this->nextRotation = new \DateTime('tomorrow'); |
$this->filenameFormat = '{filename}-{date}'; |
$this->dateFormat = 'Y-m-d'; |
|
parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function close() |
{ |
parent::close(); |
|
if (true === $this->mustRotate) { |
$this->rotate(); |
} |
} |
|
public function setFilenameFormat($filenameFormat, $dateFormat) |
{ |
if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { |
trigger_error( |
'Invalid date format - format must be one of '. |
'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. |
'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. |
'date formats using slashes, underscores and/or dots instead of dashes.', |
E_USER_DEPRECATED |
); |
} |
if (substr_count($filenameFormat, '{date}') === 0) { |
trigger_error( |
'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', |
E_USER_DEPRECATED |
); |
} |
$this->filenameFormat = $filenameFormat; |
$this->dateFormat = $dateFormat; |
$this->url = $this->getTimedFilename(); |
$this->close(); |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function write(array $record) |
{ |
// on the first record written, if the log is new, we should rotate (once per day) |
if (null === $this->mustRotate) { |
$this->mustRotate = !file_exists($this->url); |
} |
|
if ($this->nextRotation < $record['datetime']) { |
$this->mustRotate = true; |
$this->close(); |
} |
|
parent::write($record); |
} |
|
/** |
* Rotates the files. |
*/ |
protected function rotate() |
{ |
// update filename |
$this->url = $this->getTimedFilename(); |
$this->nextRotation = new \DateTime('tomorrow'); |
|
// skip GC of old logs if files are unlimited |
if (0 === $this->maxFiles) { |
return; |
} |
|
$logFiles = glob($this->getGlobPattern()); |
if ($this->maxFiles >= count($logFiles)) { |
// no files to remove |
return; |
} |
|
// Sorting the files by name to remove the older ones |
usort($logFiles, function ($a, $b) { |
return strcmp($b, $a); |
}); |
|
foreach (array_slice($logFiles, $this->maxFiles) as $file) { |
if (is_writable($file)) { |
// suppress errors here as unlink() might fail if two processes |
// are cleaning up/rotating at the same time |
set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); |
unlink($file); |
restore_error_handler(); |
} |
} |
|
$this->mustRotate = false; |
} |
|
protected function getTimedFilename() |
{ |
$fileInfo = pathinfo($this->filename); |
$timedFilename = str_replace( |
array('{filename}', '{date}'), |
array($fileInfo['filename'], date($this->dateFormat)), |
$fileInfo['dirname'] . '/' . $this->filenameFormat |
); |
|
if (!empty($fileInfo['extension'])) { |
$timedFilename .= '.'.$fileInfo['extension']; |
} |
|
return $timedFilename; |
} |
|
protected function getGlobPattern() |
{ |
$fileInfo = pathinfo($this->filename); |
$glob = str_replace( |
array('{filename}', '{date}'), |
array($fileInfo['filename'], '*'), |
$fileInfo['dirname'] . '/' . $this->filenameFormat |
); |
if (!empty($fileInfo['extension'])) { |
$glob .= '.'.$fileInfo['extension']; |
} |
|
return $glob; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php |
@@ -0,0 +1,294 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler\Slack; |
|
use Monolog\Logger; |
use Monolog\Formatter\NormalizerFormatter; |
use Monolog\Formatter\FormatterInterface; |
|
/** |
* Slack record utility helping to log to Slack webhooks or API. |
* |
* @author Greg Kedzierski <greg@gregkedzierski.com> |
* @author Haralan Dobrev <hkdobrev@gmail.com> |
* @see https://api.slack.com/incoming-webhooks |
* @see https://api.slack.com/docs/message-attachments |
*/ |
class SlackRecord |
{ |
const COLOR_DANGER = 'danger'; |
|
const COLOR_WARNING = 'warning'; |
|
const COLOR_GOOD = 'good'; |
|
const COLOR_DEFAULT = '#e3e4e6'; |
|
/** |
* Slack channel (encoded ID or name) |
* @var string|null |
*/ |
private $channel; |
|
/** |
* Name of a bot |
* @var string|null |
*/ |
private $username; |
|
/** |
* User icon e.g. 'ghost', 'http://example.com/user.png' |
* @var string |
*/ |
private $userIcon; |
|
/** |
* Whether the message should be added to Slack as attachment (plain text otherwise) |
* @var bool |
*/ |
private $useAttachment; |
|
/** |
* Whether the the context/extra messages added to Slack as attachments are in a short style |
* @var bool |
*/ |
private $useShortAttachment; |
|
/** |
* Whether the attachment should include context and extra data |
* @var bool |
*/ |
private $includeContextAndExtra; |
|
/** |
* Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] |
* @var array |
*/ |
private $excludeFields; |
|
/** |
* @var FormatterInterface |
*/ |
private $formatter; |
|
/** |
* @var NormalizerFormatter |
*/ |
private $normalizerFormatter; |
|
public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) |
{ |
$this->channel = $channel; |
$this->username = $username; |
$this->userIcon = trim($userIcon, ':'); |
$this->useAttachment = $useAttachment; |
$this->useShortAttachment = $useShortAttachment; |
$this->includeContextAndExtra = $includeContextAndExtra; |
$this->excludeFields = $excludeFields; |
$this->formatter = $formatter; |
|
if ($this->includeContextAndExtra) { |
$this->normalizerFormatter = new NormalizerFormatter(); |
} |
} |
|
public function getSlackData(array $record) |
{ |
$dataArray = array(); |
$record = $this->excludeFields($record); |
|
if ($this->username) { |
$dataArray['username'] = $this->username; |
} |
|
if ($this->channel) { |
$dataArray['channel'] = $this->channel; |
} |
|
if ($this->formatter && !$this->useAttachment) { |
$message = $this->formatter->format($record); |
} else { |
$message = $record['message']; |
} |
|
if ($this->useAttachment) { |
$attachment = array( |
'fallback' => $message, |
'text' => $message, |
'color' => $this->getAttachmentColor($record['level']), |
'fields' => array(), |
'mrkdwn_in' => array('fields'), |
'ts' => $record['datetime']->getTimestamp() |
); |
|
if ($this->useShortAttachment) { |
$attachment['title'] = $record['level_name']; |
} else { |
$attachment['title'] = 'Message'; |
$attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); |
} |
|
|
if ($this->includeContextAndExtra) { |
foreach (array('extra', 'context') as $key) { |
if (empty($record[$key])) { |
continue; |
} |
|
if ($this->useShortAttachment) { |
$attachment['fields'][] = $this->generateAttachmentField( |
ucfirst($key), |
$record[$key] |
); |
} else { |
// Add all extra fields as individual fields in attachment |
$attachment['fields'] = array_merge( |
$attachment['fields'], |
$this->generateAttachmentFields($record[$key]) |
); |
} |
} |
} |
|
$dataArray['attachments'] = array($attachment); |
} else { |
$dataArray['text'] = $message; |
} |
|
if ($this->userIcon) { |
if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { |
$dataArray['icon_url'] = $this->userIcon; |
} else { |
$dataArray['icon_emoji'] = ":{$this->userIcon}:"; |
} |
} |
|
return $dataArray; |
} |
|
/** |
* Returned a Slack message attachment color associated with |
* provided level. |
* |
* @param int $level |
* @return string |
*/ |
public function getAttachmentColor($level) |
{ |
switch (true) { |
case $level >= Logger::ERROR: |
return self::COLOR_DANGER; |
case $level >= Logger::WARNING: |
return self::COLOR_WARNING; |
case $level >= Logger::INFO: |
return self::COLOR_GOOD; |
default: |
return self::COLOR_DEFAULT; |
} |
} |
|
/** |
* Stringifies an array of key/value pairs to be used in attachment fields |
* |
* @param array $fields |
* |
* @return string |
*/ |
public function stringify($fields) |
{ |
$normalized = $this->normalizerFormatter->format($fields); |
$prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; |
|
$hasSecondDimension = count(array_filter($normalized, 'is_array')); |
$hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); |
|
return $hasSecondDimension || $hasNonNumericKeys |
? json_encode($normalized, $prettyPrintFlag) |
: json_encode($normalized); |
} |
|
/** |
* Sets the formatter |
* |
* @param FormatterInterface $formatter |
*/ |
public function setFormatter(FormatterInterface $formatter) |
{ |
$this->formatter = $formatter; |
} |
|
/** |
* Generates attachment field |
* |
* @param string $title |
* @param string|array $value\ |
* |
* @return array |
*/ |
private function generateAttachmentField($title, $value) |
{ |
$value = is_array($value) |
? sprintf('```%s```', $this->stringify($value)) |
: $value; |
|
return array( |
'title' => $title, |
'value' => $value, |
'short' => false |
); |
} |
|
/** |
* Generates a collection of attachment fields from array |
* |
* @param array $data |
* |
* @return array |
*/ |
private function generateAttachmentFields(array $data) |
{ |
$fields = array(); |
foreach ($data as $key => $value) { |
$fields[] = $this->generateAttachmentField($key, $value); |
} |
|
return $fields; |
} |
|
/** |
* Get a copy of record with fields excluded according to $this->excludeFields |
* |
* @param array $record |
* |
* @return array |
*/ |
private function excludeFields(array $record) |
{ |
foreach ($this->excludeFields as $field) { |
$keys = explode('.', $field); |
$node = &$record; |
$lastKey = end($keys); |
foreach ($keys as $key) { |
if (!isset($node[$key])) { |
break; |
} |
if ($lastKey === $key) { |
unset($node[$key]); |
break; |
} |
$node = &$node[$key]; |
} |
} |
|
return $record; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php |
@@ -0,0 +1,215 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Formatter\FormatterInterface; |
use Monolog\Logger; |
use Monolog\Handler\Slack\SlackRecord; |
|
/** |
* Sends notifications through Slack API |
* |
* @author Greg Kedzierski <greg@gregkedzierski.com> |
* @see https://api.slack.com/ |
*/ |
class SlackHandler extends SocketHandler |
{ |
/** |
* Slack API token |
* @var string |
*/ |
private $token; |
|
/** |
* Instance of the SlackRecord util class preparing data for Slack API. |
* @var SlackRecord |
*/ |
private $slackRecord; |
|
/** |
* @param string $token Slack API token |
* @param string $channel Slack channel (encoded ID or name) |
* @param string|null $username Name of a bot |
* @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) |
* @param string|null $iconEmoji The emoji name to use (or null) |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not |
* @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style |
* @param bool $includeContextAndExtra Whether the attachment should include context and extra data |
* @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] |
* @throws MissingExtensionException If no OpenSSL PHP extension configured |
*/ |
public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) |
{ |
if (!extension_loaded('openssl')) { |
throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); |
} |
|
parent::__construct('ssl://slack.com:443', $level, $bubble); |
|
$this->slackRecord = new SlackRecord( |
$channel, |
$username, |
$useAttachment, |
$iconEmoji, |
$useShortAttachment, |
$includeContextAndExtra, |
$excludeFields, |
$this->formatter |
); |
|
$this->token = $token; |
} |
|
public function getSlackRecord() |
{ |
return $this->slackRecord; |
} |
|
/** |
* {@inheritdoc} |
* |
* @param array $record |
* @return string |
*/ |
protected function generateDataStream($record) |
{ |
$content = $this->buildContent($record); |
|
return $this->buildHeader($content) . $content; |
} |
|
/** |
* Builds the body of API call |
* |
* @param array $record |
* @return string |
*/ |
private function buildContent($record) |
{ |
$dataArray = $this->prepareContentData($record); |
|
return http_build_query($dataArray); |
} |
|
/** |
* Prepares content data |
* |
* @param array $record |
* @return array |
*/ |
protected function prepareContentData($record) |
{ |
$dataArray = $this->slackRecord->getSlackData($record); |
$dataArray['token'] = $this->token; |
|
if (!empty($dataArray['attachments'])) { |
$dataArray['attachments'] = json_encode($dataArray['attachments']); |
} |
|
return $dataArray; |
} |
|
/** |
* Builds the header of the API Call |
* |
* @param string $content |
* @return string |
*/ |
private function buildHeader($content) |
{ |
$header = "POST /api/chat.postMessage HTTP/1.1\r\n"; |
$header .= "Host: slack.com\r\n"; |
$header .= "Content-Type: application/x-www-form-urlencoded\r\n"; |
$header .= "Content-Length: " . strlen($content) . "\r\n"; |
$header .= "\r\n"; |
|
return $header; |
} |
|
/** |
* {@inheritdoc} |
* |
* @param array $record |
*/ |
protected function write(array $record) |
{ |
parent::write($record); |
$this->finalizeWrite(); |
} |
|
/** |
* Finalizes the request by reading some bytes and then closing the socket |
* |
* If we do not read some but close the socket too early, slack sometimes |
* drops the request entirely. |
*/ |
protected function finalizeWrite() |
{ |
$res = $this->getResource(); |
if (is_resource($res)) { |
@fread($res, 2048); |
} |
$this->closeSocket(); |
} |
|
/** |
* Returned a Slack message attachment color associated with |
* provided level. |
* |
* @param int $level |
* @return string |
* @deprecated Use underlying SlackRecord instead |
*/ |
protected function getAttachmentColor($level) |
{ |
trigger_error( |
'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', |
E_USER_DEPRECATED |
); |
|
return $this->slackRecord->getAttachmentColor($level); |
} |
|
/** |
* Stringifies an array of key/value pairs to be used in attachment fields |
* |
* @param array $fields |
* @return string |
* @deprecated Use underlying SlackRecord instead |
*/ |
protected function stringify($fields) |
{ |
trigger_error( |
'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', |
E_USER_DEPRECATED |
); |
|
return $this->slackRecord->stringify($fields); |
} |
|
public function setFormatter(FormatterInterface $formatter) |
{ |
parent::setFormatter($formatter); |
$this->slackRecord->setFormatter($formatter); |
|
return $this; |
} |
|
public function getFormatter() |
{ |
$formatter = parent::getFormatter(); |
$this->slackRecord->setFormatter($formatter); |
|
return $formatter; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php |
@@ -0,0 +1,346 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* Stores to any socket - uses fsockopen() or pfsockopen(). |
* |
* @author Pablo de Leon Belloc <pablolb@gmail.com> |
* @see http://php.net/manual/en/function.fsockopen.php |
*/ |
class SocketHandler extends AbstractProcessingHandler |
{ |
private $connectionString; |
private $connectionTimeout; |
private $resource; |
private $timeout = 0; |
private $writingTimeout = 10; |
private $lastSentBytes = null; |
private $persistent = false; |
private $errno; |
private $errstr; |
private $lastWritingAt; |
|
/** |
* @param string $connectionString Socket connection string |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
*/ |
public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) |
{ |
parent::__construct($level, $bubble); |
$this->connectionString = $connectionString; |
$this->connectionTimeout = (float) ini_get('default_socket_timeout'); |
} |
|
/** |
* Connect (if necessary) and write to the socket |
* |
* @param array $record |
* |
* @throws \UnexpectedValueException |
* @throws \RuntimeException |
*/ |
protected function write(array $record) |
{ |
$this->connectIfNotConnected(); |
$data = $this->generateDataStream($record); |
$this->writeToSocket($data); |
} |
|
/** |
* We will not close a PersistentSocket instance so it can be reused in other requests. |
*/ |
public function close() |
{ |
if (!$this->isPersistent()) { |
$this->closeSocket(); |
} |
} |
|
/** |
* Close socket, if open |
*/ |
public function closeSocket() |
{ |
if (is_resource($this->resource)) { |
fclose($this->resource); |
$this->resource = null; |
} |
} |
|
/** |
* Set socket connection to nbe persistent. It only has effect before the connection is initiated. |
* |
* @param bool $persistent |
*/ |
public function setPersistent($persistent) |
{ |
$this->persistent = (boolean) $persistent; |
} |
|
/** |
* Set connection timeout. Only has effect before we connect. |
* |
* @param float $seconds |
* |
* @see http://php.net/manual/en/function.fsockopen.php |
*/ |
public function setConnectionTimeout($seconds) |
{ |
$this->validateTimeout($seconds); |
$this->connectionTimeout = (float) $seconds; |
} |
|
/** |
* Set write timeout. Only has effect before we connect. |
* |
* @param float $seconds |
* |
* @see http://php.net/manual/en/function.stream-set-timeout.php |
*/ |
public function setTimeout($seconds) |
{ |
$this->validateTimeout($seconds); |
$this->timeout = (float) $seconds; |
} |
|
/** |
* Set writing timeout. Only has effect during connection in the writing cycle. |
* |
* @param float $seconds 0 for no timeout |
*/ |
public function setWritingTimeout($seconds) |
{ |
$this->validateTimeout($seconds); |
$this->writingTimeout = (float) $seconds; |
} |
|
/** |
* Get current connection string |
* |
* @return string |
*/ |
public function getConnectionString() |
{ |
return $this->connectionString; |
} |
|
/** |
* Get persistent setting |
* |
* @return bool |
*/ |
public function isPersistent() |
{ |
return $this->persistent; |
} |
|
/** |
* Get current connection timeout setting |
* |
* @return float |
*/ |
public function getConnectionTimeout() |
{ |
return $this->connectionTimeout; |
} |
|
/** |
* Get current in-transfer timeout |
* |
* @return float |
*/ |
public function getTimeout() |
{ |
return $this->timeout; |
} |
|
/** |
* Get current local writing timeout |
* |
* @return float |
*/ |
public function getWritingTimeout() |
{ |
return $this->writingTimeout; |
} |
|
/** |
* Check to see if the socket is currently available. |
* |
* UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. |
* |
* @return bool |
*/ |
public function isConnected() |
{ |
return is_resource($this->resource) |
&& !feof($this->resource); // on TCP - other party can close connection. |
} |
|
/** |
* Wrapper to allow mocking |
*/ |
protected function pfsockopen() |
{ |
return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); |
} |
|
/** |
* Wrapper to allow mocking |
*/ |
protected function fsockopen() |
{ |
return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); |
} |
|
/** |
* Wrapper to allow mocking |
* |
* @see http://php.net/manual/en/function.stream-set-timeout.php |
*/ |
protected function streamSetTimeout() |
{ |
$seconds = floor($this->timeout); |
$microseconds = round(($this->timeout - $seconds) * 1e6); |
|
return stream_set_timeout($this->resource, $seconds, $microseconds); |
} |
|
/** |
* Wrapper to allow mocking |
*/ |
protected function fwrite($data) |
{ |
return @fwrite($this->resource, $data); |
} |
|
/** |
* Wrapper to allow mocking |
*/ |
protected function streamGetMetadata() |
{ |
return stream_get_meta_data($this->resource); |
} |
|
private function validateTimeout($value) |
{ |
$ok = filter_var($value, FILTER_VALIDATE_FLOAT); |
if ($ok === false || $value < 0) { |
throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); |
} |
} |
|
private function connectIfNotConnected() |
{ |
if ($this->isConnected()) { |
return; |
} |
$this->connect(); |
} |
|
protected function generateDataStream($record) |
{ |
return (string) $record['formatted']; |
} |
|
/** |
* @return resource|null |
*/ |
protected function getResource() |
{ |
return $this->resource; |
} |
|
private function connect() |
{ |
$this->createSocketResource(); |
$this->setSocketTimeout(); |
} |
|
private function createSocketResource() |
{ |
if ($this->isPersistent()) { |
$resource = $this->pfsockopen(); |
} else { |
$resource = $this->fsockopen(); |
} |
if (!$resource) { |
throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); |
} |
$this->resource = $resource; |
} |
|
private function setSocketTimeout() |
{ |
if (!$this->streamSetTimeout()) { |
throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); |
} |
} |
|
private function writeToSocket($data) |
{ |
$length = strlen($data); |
$sent = 0; |
$this->lastSentBytes = $sent; |
while ($this->isConnected() && $sent < $length) { |
if (0 == $sent) { |
$chunk = $this->fwrite($data); |
} else { |
$chunk = $this->fwrite(substr($data, $sent)); |
} |
if ($chunk === false) { |
throw new \RuntimeException("Could not write to socket"); |
} |
$sent += $chunk; |
$socketInfo = $this->streamGetMetadata(); |
if ($socketInfo['timed_out']) { |
throw new \RuntimeException("Write timed-out"); |
} |
|
if ($this->writingIsTimedOut($sent)) { |
throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); |
} |
} |
if (!$this->isConnected() && $sent < $length) { |
throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); |
} |
} |
|
private function writingIsTimedOut($sent) |
{ |
$writingTimeout = (int) floor($this->writingTimeout); |
if (0 === $writingTimeout) { |
return false; |
} |
|
if ($sent !== $this->lastSentBytes) { |
$this->lastWritingAt = time(); |
$this->lastSentBytes = $sent; |
|
return false; |
} else { |
usleep(100); |
} |
|
if ((time() - $this->lastWritingAt) >= $writingTimeout) { |
$this->closeSocket(); |
|
return true; |
} |
|
return false; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php |
@@ -0,0 +1,176 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
use Monolog\Logger; |
|
/** |
* Stores to any stream resource |
* |
* Can be used to store into php://stderr, remote and local files, etc. |
* |
* @author Jordi Boggiano <j.boggiano@seld.be> |
*/ |
class StreamHandler extends AbstractProcessingHandler |
{ |
protected $stream; |
protected $url; |
private $errorMessage; |
protected $filePermission; |
protected $useLocking; |
private $dirCreated; |
|
/** |
* @param resource|string $stream |
* @param int $level The minimum logging level at which this handler will be triggered |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) |
* @param Boolean $useLocking Try to lock log file before doing any writes |
* |
* @throws \Exception If a missing directory is not buildable |
* @throws \InvalidArgumentException If stream is not a resource or string |
*/ |
public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) |
{ |
parent::__construct($level, $bubble); |
if (is_resource($stream)) { |
$this->stream = $stream; |
} elseif (is_string($stream)) { |
$this->url = $stream; |
} else { |
throw new \InvalidArgumentException('A stream must either be a resource or a string.'); |
} |
|
$this->filePermission = $filePermission; |
$this->useLocking = $useLocking; |
} |
|
/** |
* {@inheritdoc} |
*/ |
public function close() |
{ |
if ($this->url && is_resource($this->stream)) { |
fclose($this->stream); |
} |
$this->stream = null; |
} |
|
/** |
* Return the currently active stream if it is open |
* |
* @return resource|null |
*/ |
public function getStream() |
{ |
return $this->stream; |
} |
|
/** |
* Return the stream URL if it was configured with a URL and not an active resource |
* |
* @return string|null |
*/ |
public function getUrl() |
{ |
return $this->url; |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function write(array $record) |
{ |
if (!is_resource($this->stream)) { |
if (null === $this->url || '' === $this->url) { |
throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); |
} |
$this->createDir(); |
$this->errorMessage = null; |
set_error_handler(array($this, 'customErrorHandler')); |
$this->stream = fopen($this->url, 'a'); |
if ($this->filePermission !== null) { |
@chmod($this->url, $this->filePermission); |
} |
restore_error_handler(); |
if (!is_resource($this->stream)) { |
$this->stream = null; |
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url)); |
} |
} |
|
if ($this->useLocking) { |
// ignoring errors here, there's not much we can do about them |
flock($this->stream, LOCK_EX); |
} |
|
$this->streamWrite($this->stream, $record); |
|
if ($this->useLocking) { |
flock($this->stream, LOCK_UN); |
} |
} |
|
/** |
* Write to stream |
* @param resource $stream |
* @param array $record |
*/ |
protected function streamWrite($stream, array $record) |
{ |
fwrite($stream, (string) $record['formatted']); |
} |
|
private function customErrorHandler($code, $msg) |
{ |
$this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); |
} |
|
/** |
* @param string $stream |
* |
* @return null|string |
*/ |
private function getDirFromStream($stream) |
{ |
$pos = strpos($stream, '://'); |
if ($pos === false) { |
return dirname($stream); |
} |
|
if ('file://' === substr($stream, 0, 7)) { |
return dirname(substr($stream, 7)); |
} |
|
return; |
} |
|
private function createDir() |
{ |
// Do not try to create dir if it has already been tried. |
if ($this->dirCreated) { |
return; |
} |
|
$dir = $this->getDirFromStream($this->url); |
if (null !== $dir && !is_dir($dir)) { |
$this->errorMessage = null; |
set_error_handler(array($this, 'customErrorHandler')); |
$status = mkdir($dir, 0777, true); |
restore_error_handler(); |
if (false === $status) { |
throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); |
} |
} |
$this->dirCreated = true; |
} |
} |
/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php |
@@ -0,0 +1,154 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog\Handler; |
|
/** |
* Used for testing purposes. |
* |
* It records all records and gives you access to them for verification. |
* |
* @author Jordi Boggiano <j.boggiano@seld.be> |
* |
* @method bool hasEmergency($record) |
* @method bool hasAlert($record) |
* @method bool hasCritical($record) |
* @method bool hasError($record) |
* @method bool hasWarning($record) |
* @method bool hasNotice($record) |
* @method bool hasInfo($record) |
* @method bool hasDebug($record) |
* |
* @method bool hasEmergencyRecords() |
* @method bool hasAlertRecords() |
* @method bool hasCriticalRecords() |
* @method bool hasErrorRecords() |
* @method bool hasWarningRecords() |
* @method bool hasNoticeRecords() |
* @method bool hasInfoRecords() |
* @method bool hasDebugRecords() |
* |
* @method bool hasEmergencyThatContains($message) |
* @method bool hasAlertThatContains($message) |
* @method bool hasCriticalThatContains($message) |
* @method bool hasErrorThatContains($message) |
* @method bool hasWarningThatContains($message) |
* @method bool hasNoticeThatContains($message) |
* @method bool hasInfoThatContains($message) |
* @method bool hasDebugThatContains($message) |
* |
* @method bool hasEmergencyThatMatches($message) |
* @method bool hasAlertThatMatches($message) |
* @method bool hasCriticalThatMatches($message) |
* @method bool hasErrorThatMatches($message) |
* @method bool hasWarningThatMatches($message) |
* @method bool hasNoticeThatMatches($message) |
* @method bool hasInfoThatMatches($message) |
* @method bool hasDebugThatMatches($message) |
* |
* @method bool hasEmergencyThatPasses($message) |
* @method bool hasAlertThatPasses($message) |
* @method bool hasCriticalThatPasses($message) |
* @method bool hasErrorThatPasses($message) |
* @method bool hasWarningThatPasses($message) |
* @method bool hasNoticeThatPasses($message) |
* @method bool hasInfoThatPasses($message) |
* @method bool hasDebugThatPasses($message) |
*/ |
class TestHandler extends AbstractProcessingHandler |
{ |
protected $records = array(); |
protected $recordsByLevel = array(); |
|
public function getRecords() |
{ |
return $this->records; |
} |
|
public function clear() |
{ |
$this->records = array(); |
$this->recordsByLevel = array(); |
} |
|
public function hasRecords($level) |
{ |
return isset($this->recordsByLevel[$level]); |
} |
|
public function hasRecord($record, $level) |
{ |
if (is_array($record)) { |
$record = $record['message']; |
} |
|
return $this->hasRecordThatPasses(function ($rec) use ($record) { |
return $rec['message'] === $record; |
}, $level); |
} |
|
public function hasRecordThatContains($message, $level) |
{ |
return $this->hasRecordThatPasses(function ($rec) use ($message) { |
return strpos($rec['message'], $message) !== false; |
}, $level); |
} |
|
public function hasRecordThatMatches($regex, $level) |
{ |
return $this->hasRecordThatPasses(function ($rec) use ($regex) { |
return preg_match($regex, $rec['message']) > 0; |
}, $level); |
} |
|
public function hasRecordThatPasses($predicate, $level) |
{ |
if (!is_callable($predicate)) { |
throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); |
} |
|
if (!isset($this->recordsByLevel[$level])) { |
return false; |
} |
|
foreach ($this->recordsByLevel[$level] as $i => $rec) { |
if (call_user_func($predicate, $rec, $i)) { |
return true; |
} |
} |
|
return false; |
} |
|
/** |
* {@inheritdoc} |
*/ |
protected function write(array $record) |
{ |
$this->recordsByLevel[$record['level']][] = $record; |
$this->records[] = $record; |
} |
|
public function __call($method, $args) |
{ |
if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { |
$genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; |
$level = constant('Monolog\Logger::' . strtoupper($matches[2])); |
if (method_exists($this, $genericMethod)) { |
$args[] = $level; |
|
return call_user_func_array(array($this, $genericMethod), $args); |
} |
} |
|
throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); |
} |
} |
/vendor/monolog/monolog/src/Monolog/Logger.php |
@@ -0,0 +1,700 @@ |
<?php |
|
/* |
* This file is part of the Monolog package. |
* |
* (c) Jordi Boggiano <j.boggiano@seld.be> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Monolog; |
|
use Monolog\Handler\HandlerInterface; |
use Monolog\Handler\StreamHandler; |
use Psr\Log\LoggerInterface; |
use Psr\Log\InvalidArgumentException; |
|
/** |
* Monolog log channel |
* |
* It contains a stack of Handlers and a stack of Processors, |
* and uses them to store records that are added to it. |
* |
* @author Jordi Boggiano <j.boggiano@seld.be> |
*/ |
class Logger implements LoggerInterface |
{ |
/** |
* Detailed debug information |
*/ |
const DEBUG = 100; |
|
/** |
* Interesting events |
* |
* Examples: User logs in, SQL logs. |
*/ |
const INFO = 200; |
|
/** |
* Uncommon events |
*/ |
const NOTICE = 250; |
|
/** |
* Exceptional occurrences that are not errors |
* |
* Examples: Use of deprecated APIs, poor use of an API, |
* undesirable things that are not necessarily wrong. |
*/ |
const WARNING = 300; |
|
/** |
* Runtime errors |
*/ |
const ERROR = 400; |
|
/** |
* Critical conditions |
* |
* Example: Application component unavailable, unexpected exception. |
*/ |
const CRITICAL = 500; |
|
/** |
* Action must be taken immediately |
* |
* Example: Entire website down, database unavailable, etc. |
* This should trigger the SMS alerts and wake you up. |
*/ |
const ALERT = 550; |
|
/** |
* Urgent alert. |
*/ |
const EMERGENCY = 600; |
|
/** |
* Monolog API version |
* |
* This is only bumped when API breaks are done and should |
* follow the major version of the library |
* |
* @var int |
*/ |
const API = 1; |
|
/** |
* Logging levels from syslog protocol defined in RFC 5424 |
* |
* @var array $levels Logging levels |
*/ |
protected static $levels = array( |
self::DEBUG => 'DEBUG', |
self::INFO => 'INFO', |
self::NOTICE => 'NOTICE', |
self::WARNING => 'WARNING', |
self::ERROR => 'ERROR', |
self::CRITICAL => 'CRITICAL', |
self::ALERT => 'ALERT', |
self::EMERGENCY => 'EMERGENCY', |
); |
|
/** |
* @var \DateTimeZone |
*/ |
protected static $timezone; |
|
/** |
* @var string |
*/ |
protected $name; |
|
/** |
* The handler stack |
* |
* @var HandlerInterface[] |
*/ |
protected $handlers; |
|
/** |
* Processors that will process all log records |
* |
* To process records of a single handler instead, add the processor on that specific handler |
* |
* @var callable[] |
*/ |
protected $processors; |
|
/** |
* @var bool |
*/ |
protected $microsecondTimestamps = true; |
|
/** |
* @param string $name The logging channel |
* @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. |
* @param callable[] $processors Optional array of processors |
*/ |
public function __construct($name, array $handlers = array(), array $processors = array()) |
{ |
$this->name = $name; |
$this->handlers = $handlers; |
$this->processors = $processors; |
} |
|
/** |
* @return string |
*/ |
public function getName() |
{ |
return $this->name; |
} |
|
/** |
* Return a new cloned instance with the name changed |
* |
* @return static |
*/ |
public function withName($name) |
{ |
$new = clone $this; |
$new->name = $name; |
|
return $new; |
} |
|
/** |
* Pushes a handler on to the stack. |
* |
* @param HandlerInterface $handler |
* @return $this |
*/ |
public function pushHandler(HandlerInterface $handler) |
{ |
array_unshift($this->handlers, $handler); |
|
return $this; |
} |
|
/** |
* Pops a handler from the stack |
* |
* @return HandlerInterface |
*/ |
public function popHandler() |
{ |
if (!$this->handlers) { |
throw new \LogicException('You tried to pop from an empty handler stack.'); |
} |
|
return array_shift($this->handlers); |
} |
|
/** |
* Set handlers, replacing all existing ones. |
* |
* If a map is passed, keys will be ignored. |
* |
* @param HandlerInterface[] $handlers |
* @return $this |
*/ |
public function setHandlers(array $handlers) |
{ |
$this->handlers = array(); |
foreach (array_reverse($handlers) as $handler) { |
$this->pushHandler($handler); |
} |
|
return $this; |
} |
|
/** |
* @return HandlerInterface[] |
*/ |
public function getHandlers() |
{ |
return $this->handlers; |
} |
|
/** |
* Adds a processor on to the stack. |
* |
* @param callable $callback |
* @return $this |
*/ |
public function pushProcessor($callback) |
{ |
if (!is_callable($callback)) { |
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); |
} |
array_unshift($this->processors, $callback); |
|
return $this; |
} |
|
/** |
* Removes the processor on top of the stack and returns it. |
* |
* @return callable |
*/ |
public function popProcessor() |
{ |
if (!$this->processors) { |
throw new \LogicException('You tried to pop from an empty processor stack.'); |
} |
|
return array_shift($this->processors); |
} |
|
/** |
* @return callable[] |
*/ |
public function getProcessors() |
{ |
return $this->processors; |
} |
|
/** |
* Control the use of microsecond resolution timestamps in the 'datetime' |
* member of new records. |
* |
* Generating microsecond resolution timestamps by calling |
* microtime(true), formatting the result via sprintf() and then parsing |
* the resulting string via \DateTime::createFromFormat() can incur |
* a measurable runtime overhead vs simple usage of DateTime to capture |
* a second resolution timestamp in systems which generate a large number |
* of log events. |
* |
* @param bool $micro True to use microtime() to create timestamps |
*/ |
public function useMicrosecondTimestamps($micro) |
{ |
$this->microsecondTimestamps = (bool) $micro; |
} |
|
/** |
* Adds a log record. |
* |
* @param int $level The logging level |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addRecord($level, $message, array $context = array()) |
{ |
if (!$this->handlers) { |
$this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); |
} |
|
$levelName = static::getLevelName($level); |
|
// check if any handler will handle this message so we can return early and save cycles |
$handlerKey = null; |
reset($this->handlers); |
while ($handler = current($this->handlers)) { |
if ($handler->isHandling(array('level' => $level))) { |
$handlerKey = key($this->handlers); |
break; |
} |
|
next($this->handlers); |
} |
|
if (null === $handlerKey) { |
return false; |
} |
|
if (!static::$timezone) { |
static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); |
} |
|
// php7.1+ always has microseconds enabled, so we do not need this hack |
if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { |
$ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); |
} else { |
$ts = new \DateTime(null, static::$timezone); |
} |
$ts->setTimezone(static::$timezone); |
|
$record = array( |
'message' => (string) $message, |
'context' => $context, |
'level' => $level, |
'level_name' => $levelName, |
'channel' => $this->name, |
'datetime' => $ts, |
'extra' => array(), |
); |
|
foreach ($this->processors as $processor) { |
$record = call_user_func($processor, $record); |
} |
|
while ($handler = current($this->handlers)) { |
if (true === $handler->handle($record)) { |
break; |
} |
|
next($this->handlers); |
} |
|
return true; |
} |
|
/** |
* Adds a log record at the DEBUG level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addDebug($message, array $context = array()) |
{ |
return $this->addRecord(static::DEBUG, $message, $context); |
} |
|
/** |
* Adds a log record at the INFO level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addInfo($message, array $context = array()) |
{ |
return $this->addRecord(static::INFO, $message, $context); |
} |
|
/** |
* Adds a log record at the NOTICE level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addNotice($message, array $context = array()) |
{ |
return $this->addRecord(static::NOTICE, $message, $context); |
} |
|
/** |
* Adds a log record at the WARNING level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addWarning($message, array $context = array()) |
{ |
return $this->addRecord(static::WARNING, $message, $context); |
} |
|
/** |
* Adds a log record at the ERROR level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addError($message, array $context = array()) |
{ |
return $this->addRecord(static::ERROR, $message, $context); |
} |
|
/** |
* Adds a log record at the CRITICAL level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addCritical($message, array $context = array()) |
{ |
return $this->addRecord(static::CRITICAL, $message, $context); |
} |
|
/** |
* Adds a log record at the ALERT level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addAlert($message, array $context = array()) |
{ |
return $this->addRecord(static::ALERT, $message, $context); |
} |
|
/** |
* Adds a log record at the EMERGENCY level. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function addEmergency($message, array $context = array()) |
{ |
return $this->addRecord(static::EMERGENCY, $message, $context); |
} |
|
/** |
* Gets all supported logging levels. |
* |
* @return array Assoc array with human-readable level names => level codes. |
*/ |
public static function getLevels() |
{ |
return array_flip(static::$levels); |
} |
|
/** |
* Gets the name of the logging level. |
* |
* @param int $level |
* @return string |
*/ |
public static function getLevelName($level) |
{ |
if (!isset(static::$levels[$level])) { |
throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); |
} |
|
return static::$levels[$level]; |
} |
|
/** |
* Converts PSR-3 levels to Monolog ones if necessary |
* |
* @param string|int Level number (monolog) or name (PSR-3) |
* @return int |
*/ |
public static function toMonologLevel($level) |
{ |
if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { |
return constant(__CLASS__.'::'.strtoupper($level)); |
} |
|
return $level; |
} |
|
/** |
* Checks whether the Logger has a handler that listens on the given level |
* |
* @param int $level |
* @return Boolean |
*/ |
public function isHandling($level) |
{ |
$record = array( |
'level' => $level, |
); |
|
foreach ($this->handlers as $handler) { |
if ($handler->isHandling($record)) { |
return true; |
} |
} |
|
return false; |
} |
|
/** |
* Adds a log record at an arbitrary level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param mixed $level The log level |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function log($level, $message, array $context = array()) |
{ |
$level = static::toMonologLevel($level); |
|
return $this->addRecord($level, $message, $context); |
} |
|
/** |
* Adds a log record at the DEBUG level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function debug($message, array $context = array()) |
{ |
return $this->addRecord(static::DEBUG, $message, $context); |
} |
|
/** |
* Adds a log record at the INFO level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function info($message, array $context = array()) |
{ |
return $this->addRecord(static::INFO, $message, $context); |
} |
|
/** |
* Adds a log record at the NOTICE level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function notice($message, array $context = array()) |
{ |
return $this->addRecord(static::NOTICE, $message, $context); |
} |
|
/** |
* Adds a log record at the WARNING level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function warn($message, array $context = array()) |
{ |
return $this->addRecord(static::WARNING, $message, $context); |
} |
|
/** |
* Adds a log record at the WARNING level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function warning($message, array $context = array()) |
{ |
return $this->addRecord(static::WARNING, $message, $context); |
} |
|
/** |
* Adds a log record at the ERROR level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function err($message, array $context = array()) |
{ |
return $this->addRecord(static::ERROR, $message, $context); |
} |
|
/** |
* Adds a log record at the ERROR level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function error($message, array $context = array()) |
{ |
return $this->addRecord(static::ERROR, $message, $context); |
} |
|
/** |
* Adds a log record at the CRITICAL level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function crit($message, array $context = array()) |
{ |
return $this->addRecord(static::CRITICAL, $message, $context); |
} |
|
/** |
* Adds a log record at the CRITICAL level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function critical($message, array $context = array()) |
{ |
return $this->addRecord(static::CRITICAL, $message, $context); |
} |
|
/** |
* Adds a log record at the ALERT level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function alert($message, array $context = array()) |
{ |
return $this->addRecord(static::ALERT, $message, $context); |
} |
|
/** |
* Adds a log record at the EMERGENCY level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function emerg($message, array $context = array()) |
{ |
return $this->addRecord(static::EMERGENCY, $message, $context); |
} |
|
/** |
* Adds a log record at the EMERGENCY level. |
* |
* This method allows for compatibility with common interfaces. |
* |
* @param string $message The log message |
* @param array $context The log context |
* @return Boolean Whether the record has been processed |
*/ |
public function emergency($message, array $context = array()) |
{ |
return $this->addRecord(static::EMERGENCY, $message, $context); |
} |
|
/** |
* Set the timezone to be used for the timestamp of log records. |
* |
* This is stored globally for all Logger instances |
* |
* @param \DateTimeZone $tz Timezone object |
*/ |
public static function setTimezone(\DateTimeZone $tz) |
{ |
self::$timezone = $tz; |
} |
} |