Commit 28f8c396 by 朱继来

监控告警

parent e6f0b724
Showing with 1289 additions and 206 deletions
......@@ -10,7 +10,8 @@
"justinrainbow/json-schema": "~1.3",
"maatwebsite/excel": "~2.0.0",
"guzzlehttp/guzzle": "^6.3",
"predis/predis": "^1.1"
"predis/predis": "^1.1",
"redgo/monitor-ding": "0.2"
},
"require-dev": {
"fzaninotto/faker": "~1.4",
......
......@@ -155,6 +155,7 @@ return [
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Redgo\MonitorDing\MonitorDingServiceProvider::class,
],
/*
......@@ -201,6 +202,7 @@ return [
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Excel' => Maatwebsite\Excel\Facades\Excel::class,
'MonitorDing' => Redgo\MonitorDing\Facades\MonitorDing::class,
],
......
<?php
return [
// 是否开启报错写入
'enabled' => true,
// curl证书验证, 线下环境不用开启
'curl_verify' => true,
// webhook的值
'webhook' => 'https://oapi.dingtalk.com/robot/send?access_token=a1ecbb3b7dc17660f0d8b833d79a16578b682e28b673dd024c4125ca974abadd',
];
\ No newline at end of file
......@@ -377,11 +377,11 @@ class ClassLoader
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
$search = $subPath.'\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
$length = $this->prefixLengthsPsr4[$first][$search];
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
......
This diff could not be displayed because it is too large.
......@@ -11,11 +11,11 @@ return array(
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
'e7223560d890eab89cda23685e711e2c' => $vendorDir . '/psy/psysh/src/Psy/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
);
......@@ -27,6 +27,7 @@ return array(
'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'SuperClosure\\' => array($vendorDir . '/jeremeamia/SuperClosure/src'),
'Redgo\\MonitorDing\\' => array($vendorDir . '/redgo/monitor-ding/src'),
'Psy\\' => array($vendorDir . '/psy/psysh/src/Psy'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
......
This diff could not be displayed because it is too large.
......@@ -14,10 +14,10 @@
}
],
"require": {
"php": ">=5.5.9",
"ext-fileinfo": "*"
"php": ">=5.5.9"
},
"require-dev": {
"ext-fileinfo": "*",
"phpspec/phpspec": "^3.4",
"phpunit/phpunit": "^5.7.10"
},
......
......@@ -8,12 +8,12 @@ The `Handler` type and associated calls will be removed in version 2.0.
### Upgrade path
You should create your own implementation for handling OOP usage,
You should either create your own implementation for handling OOP usage,
but it's recommended to move away from using an OOP-style wrapper entirely.
The reason for this is that it's too easy for implementation details (for
your application this is Flysystem) to leak into the application. The most
important part for Flysystem is that it improves portability and creates a
important part for Flysystem is that it improved portability and creates a
solid boundary between your application core and the infrastructure you use.
The OOP-style handling breaks this principle, therefore I want to stop
The OOP-style handling breaks this principle, therefor I want to stop
promoting it.
\ No newline at end of file
### 1.24.0 (2018-11-05)
* Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors
* Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers)
* Added a way to log signals being received using Monolog\SignalHandler
* Added ability to customize error handling at the Logger level using Logger::setExceptionHandler
* Added InsightOpsHandler to migrate users of the LogEntriesHandler
* Added protection to NormalizerHandler against circular and very deep structures, it now stops normalizing at a depth of 9
* Added capture of stack traces to ErrorHandler when logging PHP errors
* Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts
* Added forwarding of context info to FluentdFormatter
* Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example
* Added ability to extend/override BrowserConsoleHandler
* Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility
* Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility
* Dropped official support for HHVM in test builds
* Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain
* Fixed naming of fields in Slack handler, all field names are now capitalized in all cases
* Fixed HipChatHandler bug where slack dropped messages randomly
* Fixed normalization of objects in Slack handlers
* Fixed support for PHP7's Throwable in NewRelicHandler
* Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory
* Fixed table row styling issues in HtmlFormatter
* Fixed RavenHandler dropping the message when logging exception
* Fixed WhatFailureGroupHandler skipping processors when using handleBatch
and implement it where possible
* Fixed display of anonymous class names
### 1.23.0 (2017-06-19)
* Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
......
......@@ -2,7 +2,6 @@
[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references)
Monolog sends your logs to files, sockets, inboxes, databases and various
......
......@@ -55,6 +55,7 @@
- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account.
- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server.
- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account.
- _InsightOpsHandler_: Logs records to a [InsightOps](https://www.rapid7.com/products/insightops/) account.
### Logging in development
......
......@@ -5,6 +5,8 @@
help in some older codebases or for ease of use.
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
a Logger instance as an exception handler, error handler or fatal error handler.
- _SignalHandler_: The `Monolog\SignalHandler` class allows you to easily register
a Logger instance as a POSIX signal handler.
- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log
level is reached.
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain
......
......@@ -14,6 +14,7 @@ namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Monolog\Handler\AbstractHandler;
use Monolog\Registry;
/**
* Monolog error handler
......@@ -38,6 +39,7 @@ class ErrorHandler
private $hasFatalErrorHandler;
private $fatalLevel;
private $reservedMemory;
private $lastFatalTrace;
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
public function __construct(LoggerInterface $logger)
......@@ -132,7 +134,7 @@ class ErrorHandler
{
$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()),
sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),
array('exception' => $e)
);
......@@ -156,6 +158,13 @@ class ErrorHandler
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));
} else {
// http://php.net/manual/en/function.debug-backtrace.php
// As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added.
// Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'.
$trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS);
array_shift($trace); // Exclude handleError from trace
$this->lastFatalTrace = $trace;
}
if ($this->previousErrorHandler === true) {
......@@ -177,7 +186,7 @@ class ErrorHandler
$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'])
array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace)
);
if ($this->logger instanceof Logger) {
......
......@@ -62,6 +62,7 @@ class FluentdFormatter implements FormatterInterface
$message = array(
'message' => $record['message'],
'context' => $record['context'],
'extra' => $record['extra'],
);
......
......@@ -58,7 +58,7 @@ class HtmlFormatter extends NormalizerFormatter
$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>";
return "<tr style=\"padding: 4px;text-align: left;\">\n<th style=\"vertical-align: top;background: #ccc;color: #000\" width=\"100\">$th:</th>\n<td style=\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\">".$td."</td>\n</tr>";
}
/**
......
......@@ -12,6 +12,7 @@
namespace Monolog\Formatter;
use Exception;
use Monolog\Utils;
use Throwable;
/**
......@@ -138,18 +139,23 @@ class JsonFormatter extends NormalizerFormatter
*
* @return mixed
*/
protected function normalize($data)
protected function normalize($data, $depth = 0)
{
if ($depth > 9) {
return 'Over 9 levels deep, aborting normalization';
}
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';
if ($count++ > 1000) {
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
$normalized[$key] = $this->normalize($value, $depth+1);
}
return $normalized;
......@@ -174,11 +180,11 @@ class JsonFormatter extends NormalizerFormatter
{
// 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));
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
}
$data = array(
'class' => get_class($e),
'class' => Utils::getClass($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
......
......@@ -11,6 +11,8 @@
namespace Monolog\Formatter;
use Monolog\Utils;
/**
* Formats incoming records into a one-line string
*
......@@ -129,17 +131,17 @@ class LineFormatter extends NormalizerFormatter
{
// 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));
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
}
$previousText = '';
if ($previous = $e->getPrevious()) {
do {
$previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
$previousText .= ', '.Utils::getClass($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.')';
$str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
}
......
......@@ -11,6 +11,8 @@
namespace Monolog\Formatter;
use Monolog\Utils;
/**
* Formats a record for use with the MongoDBHandler.
*
......@@ -75,7 +77,7 @@ class MongoDBFormatter implements FormatterInterface
protected function formatObject($value, $nestingLevel)
{
$objectVars = get_object_vars($value);
$objectVars['class'] = get_class($value);
$objectVars['class'] = Utils::getClass($value);
return $this->formatArray($objectVars, $nestingLevel);
}
......@@ -83,7 +85,7 @@ class MongoDBFormatter implements FormatterInterface
protected function formatException(\Exception $exception, $nestingLevel)
{
$formattedException = array(
'class' => get_class($exception),
'class' => Utils::getClass($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
......
......@@ -12,6 +12,7 @@
namespace Monolog\Formatter;
use Exception;
use Monolog\Utils;
/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
......@@ -55,8 +56,12 @@ class NormalizerFormatter implements FormatterInterface
return $records;
}
protected function normalize($data)
protected function normalize($data, $depth = 0)
{
if ($depth > 9) {
return 'Over 9 levels deep, aborting normalization';
}
if (null === $data || is_scalar($data)) {
if (is_float($data)) {
if (is_infinite($data)) {
......@@ -75,11 +80,12 @@ class NormalizerFormatter implements FormatterInterface
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
if ($count++ > 1000) {
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
$normalized[$key] = $this->normalize($value, $depth+1);
}
return $normalized;
......@@ -103,7 +109,7 @@ class NormalizerFormatter implements FormatterInterface
$value = $this->toJson($data, true);
}
return sprintf("[object] (%s: %s)", get_class($data), $value);
return sprintf("[object] (%s: %s)", Utils::getClass($data), $value);
}
if (is_resource($data)) {
......@@ -117,11 +123,11 @@ class NormalizerFormatter implements FormatterInterface
{
// 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));
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
}
$data = array(
'class' => get_class($e),
'class' => Utils::getClass($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
......@@ -146,9 +152,20 @@ class NormalizerFormatter implements FormatterInterface
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
// Simplify closures handling
$data['trace'][] = $frame['function'];
} else {
if (isset($frame['args'])) {
// Make sure that objects present as arguments are not serialized nicely but rather only
// as a class name to avoid any unexpected leak of sensitive information
$frame['args'] = array_map(function ($arg) {
if (is_object($arg) && !($arg instanceof \DateTime || $arg instanceof \DateTimeInterface)) {
return sprintf("[object] (%s)", Utils::getClass($arg));
}
return $arg;
}, $frame['args']);
}
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $this->toJson($this->normalize($frame), true);
}
......
......@@ -102,12 +102,12 @@ class WildfireFormatter extends NormalizerFormatter
throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
}
protected function normalize($data)
protected function normalize($data, $depth = 0)
{
if (is_object($data) && !$data instanceof \DateTime) {
return $data;
}
return parent::normalize($data);
return parent::normalize($data, $depth);
}
}
......@@ -11,16 +11,17 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use Monolog\ResettableInterface;
/**
* Base Handler class providing the Handler structure
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
abstract class AbstractHandler implements HandlerInterface
abstract class AbstractHandler implements HandlerInterface, ResettableInterface
{
protected $level = Logger::DEBUG;
protected $bubble = true;
......@@ -32,8 +33,8 @@ abstract class AbstractHandler implements HandlerInterface
protected $processors = array();
/**
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @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($level = Logger::DEBUG, $bubble = true)
{
......@@ -141,8 +142,8 @@ abstract class AbstractHandler implements HandlerInterface
/**
* Sets the bubbling behavior.
*
* @param Boolean $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @param bool $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
*/
public function setBubble($bubble)
......@@ -155,8 +156,8 @@ abstract class AbstractHandler implements HandlerInterface
/**
* Gets the bubbling behavior.
*
* @return Boolean true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return bool true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function getBubble()
{
......@@ -174,6 +175,15 @@ abstract class AbstractHandler implements HandlerInterface
}
}
public function reset()
{
foreach ($this->processors as $processor) {
if ($processor instanceof ResettableInterface) {
$processor->reset();
}
}
}
/**
* Gets the default formatter.
*
......
......@@ -11,6 +11,8 @@
namespace Monolog\Handler;
use Monolog\ResettableInterface;
/**
* Base Handler class providing the Handler structure
*
......
......@@ -53,9 +53,9 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler
);
/**
* @param mixed $facility
* @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 mixed $facility
* @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($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
{
......
......@@ -43,11 +43,11 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
protected function write(array $record)
{
// Accumulate records
self::$records[] = $record;
static::$records[] = $record;
// Register shutdown handler if not already done
if (!self::$initialized) {
self::$initialized = true;
if (!static::$initialized) {
static::$initialized = true;
$this->registerShutdownFunction();
}
}
......@@ -58,27 +58,37 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
*/
public static function send()
{
$format = self::getResponseFormat();
$format = static::getResponseFormat();
if ($format === 'unknown') {
return;
}
if (count(self::$records)) {
if (count(static::$records)) {
if ($format === 'html') {
self::writeOutput('<script>' . self::generateScript() . '</script>');
static::writeOutput('<script>' . static::generateScript() . '</script>');
} elseif ($format === 'js') {
self::writeOutput(self::generateScript());
static::writeOutput(static::generateScript());
}
self::reset();
static::resetStatic();
}
}
public function close()
{
self::resetStatic();
}
public function reset()
{
self::resetStatic();
}
/**
* Forget all logged records
*/
public static function reset()
public static function resetStatic()
{
self::$records = array();
static::$records = array();
}
/**
......@@ -133,18 +143,18 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
private static function generateScript()
{
$script = array();
foreach (self::$records as $record) {
$context = self::dump('Context', $record['context']);
$extra = self::dump('Extra', $record['extra']);
foreach (static::$records as $record) {
$context = static::dump('Context', $record['context']);
$extra = static::dump('Extra', $record['extra']);
if (empty($context) && empty($extra)) {
$script[] = self::call_array('log', self::handleStyles($record['formatted']));
$script[] = static::call_array('log', static::handleStyles($record['formatted']));
} else {
$script = array_merge($script,
array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))),
array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))),
$context,
$extra,
array(self::call('groupEnd'))
array(static::call('groupEnd'))
);
}
}
......@@ -154,19 +164,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
private static function handleStyles($formatted)
{
$args = array(self::quote('font-weight: normal'));
$args = array(static::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[] = static::quote(static::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));
array_unshift($args, static::quote($format));
return $args;
}
......@@ -198,13 +208,13 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
if (empty($dict)) {
return $script;
}
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
$script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
foreach ($dict as $key => $value) {
$value = json_encode($value);
if (empty($value)) {
$value = self::quote('');
$value = static::quote('');
}
$script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value);
$script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value);
}
return $script;
......@@ -220,7 +230,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
$args = func_get_args();
$method = array_shift($args);
return self::call_array($method, $args);
return static::call_array($method, $args);
}
private static function call_array($method, array $args)
......
......@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\ResettableInterface;
/**
* Buffers all records until closing the handler and then pass them as batch.
......@@ -34,8 +35,8 @@ class BufferHandler extends AbstractHandler
* @param HandlerInterface $handler Handler.
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param 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 $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
*/
public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
{
......@@ -114,4 +115,15 @@ class BufferHandler extends AbstractHandler
$this->bufferSize = 0;
$this->buffer = array();
}
public function reset()
{
$this->flush();
parent::reset();
if ($this->handler instanceof ResettableInterface) {
$this->handler->reset();
}
}
}
......@@ -32,7 +32,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
* Header name
*/
const HEADER_NAME = 'X-ChromeLogger-Data';
/**
* Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
*/
......@@ -45,7 +45,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
*
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
*
* @var Boolean
* @var bool
*/
protected static $overflowed = false;
......@@ -58,8 +58,8 @@ class ChromePHPHandler extends AbstractProcessingHandler
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
* @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($level = Logger::DEBUG, $bubble = true)
{
......@@ -174,7 +174,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
* @return bool
*/
protected function headersAccepted()
{
......
......@@ -60,7 +60,7 @@ class DeduplicationHandler extends BufferHandler
* @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
* @param bool $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)
{
......
......@@ -46,10 +46,10 @@ class ElasticSearchHandler extends AbstractProcessingHandler
protected $options = array();
/**
* @param Client $client Elastica Client object
* @param array $options Handler configuration
* @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 Client $client Elastica Client object
* @param array $options Handler configuration
* @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(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true)
{
......
......@@ -28,10 +28,10 @@ class ErrorLogHandler extends AbstractProcessingHandler
protected $expandNewlines;
/**
* @param int $messageType Says where the error should go.
* @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 $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
* @param int $messageType Says where the error should go.
* @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 $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
*/
public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
{
......
......@@ -40,7 +40,7 @@ class FilterHandler extends AbstractHandler
/**
* Whether the messages that are handled can bubble up the stack or not
*
* @var Boolean
* @var bool
*/
protected $bubble;
......@@ -48,7 +48,7 @@ class FilterHandler extends AbstractHandler
* @param callable|HandlerInterface $handler Handler or factory callable($record, $this).
* @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
* @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
{
......
......@@ -22,7 +22,7 @@ interface ActivationStrategyInterface
* Returns whether the given record activates the handler.
*
* @param array $record
* @return Boolean
* @return bool
*/
public function isHandlerActivated(array $record);
}
......@@ -14,6 +14,7 @@ namespace Monolog\Handler;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
use Monolog\ResettableInterface;
/**
* Buffers all records until a certain level is reached
......@@ -41,8 +42,8 @@ class FingersCrossedHandler extends AbstractHandler
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $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)
......@@ -130,24 +131,18 @@ class FingersCrossedHandler extends AbstractHandler
*/
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();
}
}
$this->flushBuffer();
}
/**
* Resets the state of the handler. Stops forwarding records to the wrapped handler.
*/
public function reset()
{
$this->buffering = true;
$this->flushBuffer();
parent::reset();
if ($this->handler instanceof ResettableInterface) {
$this->handler->reset();
}
}
/**
......@@ -160,4 +155,23 @@ class FingersCrossedHandler extends AbstractHandler
$this->buffer = array();
$this->reset();
}
/**
* Resets the state of the handler. Stops forwarding records to the wrapped handler.
*/
private function flushBuffer()
{
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();
$this->buffering = true;
}
}
......@@ -158,7 +158,7 @@ class FirePHPHandler extends AbstractProcessingHandler
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
* @return bool
*/
protected function headersAccepted()
{
......
......@@ -50,14 +50,6 @@ class GelfHandler extends AbstractProcessingHandler
/**
* {@inheritdoc}
*/
public function close()
{
$this->publisher = null;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->publisher->publish($record['formatted']);
......
......@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\ResettableInterface;
/**
* Forwards records to multiple handlers
......@@ -23,8 +24,8 @@ class GroupHandler extends AbstractHandler
protected $handlers;
/**
* @param array $handlers Array of Handlers.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param array $handlers Array of Handlers.
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(array $handlers, $bubble = true)
{
......@@ -90,6 +91,17 @@ class GroupHandler extends AbstractHandler
}
}
public function reset()
{
parent::reset();
foreach ($this->handlers as $handler) {
if ($handler instanceof ResettableInterface) {
$handler->reset();
}
}
}
/**
* {@inheritdoc}
*/
......
......@@ -31,7 +31,7 @@ interface HandlerInterface
*
* @param array $record Partial log record containing only a level key
*
* @return Boolean
* @return bool
*/
public function isHandling(array $record);
......@@ -46,7 +46,7 @@ interface HandlerInterface
* calling further handlers in the stack with a given log record.
*
* @param array $record The record to handle
* @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
* @return bool true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
*/
public function handle(array $record);
......
......@@ -11,6 +11,7 @@
namespace Monolog\Handler;
use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;
/**
......@@ -30,7 +31,7 @@ use Monolog\Formatter\FormatterInterface;
*
* @author Alexey Karapetov <alexey@karapetov.com>
*/
class HandlerWrapper implements HandlerInterface
class HandlerWrapper implements HandlerInterface, ResettableInterface
{
/**
* @var HandlerInterface
......@@ -105,4 +106,11 @@ class HandlerWrapper implements HandlerInterface
{
return $this->handler->getFormatter();
}
public function reset()
{
if ($this->handler instanceof ResettableInterface) {
return $this->handler->reset();
}
}
}
......@@ -219,6 +219,21 @@ class HipChatHandler extends SocketHandler
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, hipchat sometimes
* drops the request entirely.
*/
protected function finalizeWrite()
{
$res = $this->getResource();
if (is_resource($res)) {
@fread($res, 2048);
}
$this->closeSocket();
}
......
......@@ -30,10 +30,10 @@ class IFTTTHandler extends AbstractProcessingHandler
private $secretKey;
/**
* @param string $eventName The name of the IFTTT Maker event that should be triggered
* @param string $secretKey A valid IFTTT secret key
* @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 string $eventName The name of the IFTTT Maker event that should be triggered
* @param string $secretKey A valid IFTTT secret key
* @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($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
{
......
<?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;
/**
* Inspired on LogEntriesHandler.
*
* @author Robert Kaufmann III <rok3@rok3.me>
* @author Gabriel Machado <gabriel.ms1@hotmail.com>
*/
class InsightOpsHandler extends SocketHandler
{
/**
* @var string
*/
protected $logToken;
/**
* @param string $token Log token supplied by InsightOps
* @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'.
* @param bool $useSSL Whether or not SSL encryption should be used
* @param int $level The minimum logging level to trigger this handler
* @param bool $bubble Whether or not messages that are handled should bubble up the stack.
*
* @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
*/
public function __construct($token, $region = 'us', $useSSL = true, $level = Logger::DEBUG, $bubble = true)
{
if ($useSSL && !extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
}
$endpoint = $useSSL
? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443'
: $region . '.data.logs.insight.rapid7.com:80';
parent::__construct($endpoint, $level, $bubble);
$this->logToken = $token;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
return $this->logToken . ' ' . $record['formatted'];
}
}
......@@ -31,13 +31,13 @@ class LogEntriesHandler extends SocketHandler
*
* @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
*/
public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true)
public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true, $host = 'data.logentries.com')
{
if ($useSSL && !extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
}
$endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80';
$endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80';
parent::__construct($endpoint, $level, $bubble);
$this->logToken = $token;
}
......
......@@ -27,7 +27,7 @@ class MandrillHandler extends MailHandler
* @param string $apiKey A valid Mandrill API key
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
{
......
......@@ -18,6 +18,8 @@ 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.
*
* This handler requires a NormalizerFormatter to function and expects an array in $record['formatted']
*
* @see https://docs.newrelic.com/docs/agents/php-agent
* @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
*/
......@@ -84,7 +86,7 @@ class NewRelicHandler extends AbstractProcessingHandler
unset($record['formatted']['context']['transaction_name']);
}
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
newrelic_notice_error($record['message'], $record['context']['exception']);
unset($record['formatted']['context']['exception']);
} else {
......
......@@ -31,7 +31,7 @@ class PsrHandler extends AbstractHandler
/**
* @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true)
{
......
......@@ -69,8 +69,8 @@ class PushoverHandler extends SocketHandler
* @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
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $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
......@@ -180,6 +180,6 @@ class PushoverHandler extends SocketHandler
*/
public function useFormattedMessage($value)
{
$this->useFormattedMessage = (boolean) $value;
$this->useFormattedMessage = (bool) $value;
}
}
......@@ -18,7 +18,7 @@ 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)
* using sentry-php (https://github.com/getsentry/sentry-php)
*
* @author Marc Abramowitz <marc@marc-abramowitz.com>
*/
......@@ -27,7 +27,7 @@ class RavenHandler extends AbstractProcessingHandler
/**
* Translates Monolog log levels to Raven log levels.
*/
private $logLevels = array(
protected $logLevels = array(
Logger::DEBUG => Raven_Client::DEBUG,
Logger::INFO => Raven_Client::INFO,
Logger::NOTICE => Raven_Client::INFO,
......@@ -42,7 +42,7 @@ class RavenHandler extends AbstractProcessingHandler
* @var string should represent the current version of the calling
* software. Can be any string (git commit, version number)
*/
private $release;
protected $release;
/**
* @var Raven_Client the client object that sends the message to the server
......@@ -57,7 +57,7 @@ class RavenHandler extends AbstractProcessingHandler
/**
* @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
* @param bool $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)
{
......@@ -180,7 +180,7 @@ class RavenHandler extends AbstractProcessingHandler
}
if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
$options['extra']['message'] = $record['formatted'];
$options['message'] = $record['formatted'];
$this->ravenClient->captureException($record['context']['exception'], $options);
} else {
$this->ravenClient->captureMessage($record['formatted'], array(), $options);
......@@ -216,7 +216,7 @@ class RavenHandler extends AbstractProcessingHandler
*/
protected function getExtraParameters()
{
return array('checksum', 'release', 'event_id');
return array('contexts', 'checksum', 'release', 'event_id');
}
/**
......
......@@ -129,4 +129,16 @@ class RollbarHandler extends AbstractProcessingHandler
{
$this->flush();
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->flush();
parent::reset();
}
}
......@@ -39,9 +39,9 @@ class RotatingFileHandler extends StreamHandler
* @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 bool $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
* @param bool $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)
{
......@@ -66,6 +66,18 @@ class RotatingFileHandler extends StreamHandler
}
}
/**
* {@inheritdoc}
*/
public function reset()
{
parent::reset();
if (true === $this->mustRotate) {
$this->rotate();
}
}
public function setFilenameFormat($filenameFormat, $dateFormat)
{
if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) {
......@@ -166,7 +178,7 @@ class RotatingFileHandler extends StreamHandler
$fileInfo = pathinfo($this->filename);
$glob = str_replace(
array('{filename}', '{date}'),
array($fileInfo['filename'], '*'),
array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'),
$fileInfo['dirname'] . '/' . $this->filenameFormat
);
if (!empty($fileInfo['extension'])) {
......
......@@ -146,7 +146,7 @@ class SlackRecord
if ($this->useShortAttachment) {
$attachment['fields'][] = $this->generateAttachmentField(
ucfirst($key),
$key,
$record[$key]
);
} else {
......@@ -229,8 +229,8 @@ class SlackRecord
/**
* Generates attachment field
*
* @param string $title
* @param string|array $value\
* @param string $title
* @param string|array $value
*
* @return array
*/
......@@ -241,7 +241,7 @@ class SlackRecord
: $value;
return array(
'title' => $title,
'title' => ucfirst($title),
'value' => $value,
'short' => false
);
......@@ -257,7 +257,7 @@ class SlackRecord
private function generateAttachmentFields(array $data)
{
$fields = array();
foreach ($data as $key => $value) {
foreach ($this->normalizerFormatter->format($data) as $key => $value) {
$fields[] = $this->generateAttachmentField($key, $value);
}
......
......@@ -75,6 +75,11 @@ class SlackHandler extends SocketHandler
return $this->slackRecord;
}
public function getToken()
{
return $this->token;
}
/**
* {@inheritdoc}
*
......
......@@ -70,6 +70,11 @@ class SlackWebhookHandler extends AbstractProcessingHandler
return $this->slackRecord;
}
public function getWebhookUrl()
{
return $this->webhookUrl;
}
/**
* {@inheritdoc}
*
......
......@@ -27,15 +27,16 @@ class SocketHandler extends AbstractProcessingHandler
private $timeout = 0;
private $writingTimeout = 10;
private $lastSentBytes = null;
private $chunkSize = 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
* @param string $connectionString Socket connection string
* @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($connectionString, $level = Logger::DEBUG, $bubble = true)
{
......@@ -87,7 +88,7 @@ class SocketHandler extends AbstractProcessingHandler
*/
public function setPersistent($persistent)
{
$this->persistent = (boolean) $persistent;
$this->persistent = (bool) $persistent;
}
/**
......@@ -128,6 +129,16 @@ class SocketHandler extends AbstractProcessingHandler
}
/**
* Set chunk size. Only has effect during connection in the writing cycle.
*
* @param float $bytes
*/
public function setChunkSize($bytes)
{
$this->chunkSize = $bytes;
}
/**
* Get current connection string
*
* @return string
......@@ -178,6 +189,16 @@ class SocketHandler extends AbstractProcessingHandler
}
/**
* Get current chunk size
*
* @return float
*/
public function getChunkSize()
{
return $this->chunkSize;
}
/**
* 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.
......@@ -221,6 +242,16 @@ class SocketHandler extends AbstractProcessingHandler
/**
* Wrapper to allow mocking
*
* @see http://php.net/manual/en/function.stream-set-chunk-size.php
*/
protected function streamSetChunkSize()
{
return stream_set_chunk_size($this->resource, $this->chunkSize);
}
/**
* Wrapper to allow mocking
*/
protected function fwrite($data)
{
......@@ -268,6 +299,7 @@ class SocketHandler extends AbstractProcessingHandler
{
$this->createSocketResource();
$this->setSocketTimeout();
$this->setStreamChunkSize();
}
private function createSocketResource()
......@@ -290,6 +322,13 @@ class SocketHandler extends AbstractProcessingHandler
}
}
private function setStreamChunkSize()
{
if ($this->chunkSize && !$this->streamSetChunkSize()) {
throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()");
}
}
private function writeToSocket($data)
{
$length = strlen($data);
......
......@@ -32,9 +32,9 @@ class StreamHandler extends AbstractProcessingHandler
/**
* @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 bool $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
* @param bool $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
......@@ -167,7 +167,7 @@ class StreamHandler extends AbstractProcessingHandler
set_error_handler(array($this, 'customErrorHandler'));
$status = mkdir($dir, 0777, true);
restore_error_handler();
if (false === $status) {
if (false === $status && !is_dir($dir)) {
throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir));
}
}
......
......@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Swift;
......@@ -29,7 +30,7 @@ class SwiftMailerHandler extends MailHandler
* @param \Swift_Mailer $mailer The mailer to use
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true)
{
......@@ -48,6 +49,17 @@ class SwiftMailerHandler extends MailHandler
}
/**
* Gets the formatter for the Swift_Message subject.
*
* @param string $format The format of the subject
* @return FormatterInterface
*/
protected function getSubjectFormatter($format)
{
return new LineFormatter($format);
}
/**
* Creates instance of Swift_Message to be sent
*
* @param string $content formatted email body to be sent
......@@ -69,7 +81,7 @@ class SwiftMailerHandler extends MailHandler
}
if ($records) {
$subjectFormatter = new LineFormatter($message->getSubject());
$subjectFormatter = $this->getSubjectFormatter($message->getSubject());
$message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
}
......
......@@ -32,11 +32,11 @@ class SyslogHandler extends AbstractSyslogHandler
protected $logopts;
/**
* @param string $ident
* @param mixed $facility
* @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 $logopts Option flags for the openlog() call, defaults to LOG_PID
* @param string $ident
* @param mixed $facility
* @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 $logopts Option flags for the openlog() call, defaults to LOG_PID
*/
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
{
......
......@@ -25,12 +25,12 @@ class SyslogUdpHandler extends AbstractSyslogHandler
protected $ident;
/**
* @param string $host
* @param int $port
* @param mixed $facility
* @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 string $ident Program name or tag for each log message.
* @param string $host
* @param int $port
* @param mixed $facility
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param string $ident Program name or tag for each log message.
*/
public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php')
{
......
......@@ -84,14 +84,24 @@ class TestHandler extends AbstractProcessingHandler
return isset($this->recordsByLevel[$level]);
}
/**
* @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records
* @param int $level Logger::LEVEL constant value
*/
public function hasRecord($record, $level)
{
if (is_array($record)) {
$record = $record['message'];
if (is_string($record)) {
$record = array('message' => $record);
}
return $this->hasRecordThatPasses(function ($rec) use ($record) {
return $rec['message'] === $record;
if ($rec['message'] !== $record['message']) {
return false;
}
if (isset($record['context']) && $rec['context'] !== $record['context']) {
return false;
}
return true;
}, $level);
}
......
......@@ -48,6 +48,16 @@ class WhatFailureGroupHandler extends GroupHandler
*/
public function handleBatch(array $records)
{
if ($this->processors) {
$processed = array();
foreach ($records as $record) {
foreach ($this->processors as $processor) {
$processed[] = call_user_func($processor, $record);
}
}
$records = $processed;
}
foreach ($this->handlers as $handler) {
try {
$handler->handleBatch($records);
......
......@@ -19,7 +19,7 @@ use Monolog\Logger;
* @author Nick Otter
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class GitProcessor
class GitProcessor implements ProcessorInterface
{
private $level;
private static $cache;
......
......@@ -24,7 +24,7 @@ use Monolog\Logger;
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class IntrospectionProcessor
class IntrospectionProcessor implements ProcessorInterface
{
private $level;
......
......@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Rob Jensen
*/
abstract class MemoryProcessor
abstract class MemoryProcessor implements ProcessorInterface
{
/**
* @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported.
......@@ -34,8 +34,8 @@ abstract class MemoryProcessor
*/
public function __construct($realUsage = true, $useFormatting = true)
{
$this->realUsage = (boolean) $realUsage;
$this->useFormatting = (boolean) $useFormatting;
$this->realUsage = (bool) $realUsage;
$this->useFormatting = (bool) $useFormatting;
}
/**
......
......@@ -18,7 +18,7 @@ use Monolog\Logger;
*
* @author Jonathan A. Schweder <jonathanschweder@gmail.com>
*/
class MercurialProcessor
class MercurialProcessor implements ProcessorInterface
{
private $level;
private static $cache;
......
......@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Andreas Hörnicke
*/
class ProcessIdProcessor
class ProcessIdProcessor implements ProcessorInterface
{
/**
* @param array $record
......
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* An optional interface to allow labelling Monolog processors.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ProcessorInterface
{
/**
* @return array The processed records
*/
public function __invoke(array $records);
}
......@@ -11,6 +11,8 @@
namespace Monolog\Processor;
use Monolog\Utils;
/**
* Processes a record's message according to PSR-3 rules
*
......@@ -18,7 +20,7 @@ namespace Monolog\Processor;
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class PsrLogMessageProcessor
class PsrLogMessageProcessor implements ProcessorInterface
{
/**
* @param array $record
......@@ -35,7 +37,7 @@ class PsrLogMessageProcessor
if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
$replacements['{'.$key.'}'] = $val;
} elseif (is_object($val)) {
$replacements['{'.$key.'}'] = '[object '.get_class($val).']';
$replacements['{'.$key.'}'] = '[object '.Utils::getClass($val).']';
} else {
$replacements['{'.$key.'}'] = '['.gettype($val).']';
}
......
......@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Martijn Riemers
*/
class TagProcessor
class TagProcessor implements ProcessorInterface
{
private $tags;
......
......@@ -11,12 +11,14 @@
namespace Monolog\Processor;
use Monolog\ResettableInterface;
/**
* Adds a unique identifier into records
*
* @author Simon Mönch <sm@webfactory.de>
*/
class UidProcessor
class UidProcessor implements ProcessorInterface, ResettableInterface
{
private $uid;
......@@ -26,7 +28,8 @@ class UidProcessor
throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
}
$this->uid = substr(hash('md5', uniqid('', true)), 0, $length);
$this->uid = $this->generateUid($length);
}
public function __invoke(array $record)
......@@ -43,4 +46,14 @@ class UidProcessor
{
return $this->uid;
}
public function reset()
{
$this->uid = $this->generateUid(strlen($this->uid));
}
private function generateUid($length)
{
return substr(hash('md5', uniqid('', true)), 0, $length);
}
}
......@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class WebProcessor
class WebProcessor implements ProcessorInterface
{
/**
* @var array|\ArrayAccess
......
<?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 or Processor implementing this interface will be reset when Logger::reset() is called.
*
* Resetting ends a log cycle gets them back to their initial state.
*
* Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal
* state, and getting it back to a state in which it can receive log records again.
*
* This is useful in case you want to avoid logs leaking between two requests or jobs when you
* have a long running process like a worker or an application server serving multiple requests
* in one process.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
interface ResettableInterface
{
public function reset();
}
<?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 ReflectionExtension;
/**
* Monolog POSIX signal handler
*
* @author Robert Gust-Bardon <robert@gust-bardon.org>
*/
class SignalHandler
{
private $logger;
private $previousSignalHandler = array();
private $signalLevelMap = array();
private $signalRestartSyscalls = array();
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true)
{
if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) {
return $this;
}
if ($callPrevious) {
if (function_exists('pcntl_signal_get_handler')) {
$handler = pcntl_signal_get_handler($signo);
if ($handler === false) {
return $this;
}
$this->previousSignalHandler[$signo] = $handler;
} else {
$this->previousSignalHandler[$signo] = true;
}
} else {
unset($this->previousSignalHandler[$signo]);
}
$this->signalLevelMap[$signo] = $level;
$this->signalRestartSyscalls[$signo] = $restartSyscalls;
if (function_exists('pcntl_async_signals') && $async !== null) {
pcntl_async_signals($async);
}
pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
return $this;
}
public function handleSignal($signo, array $siginfo = null)
{
static $signals = array();
if (!$signals && extension_loaded('pcntl')) {
$pcntl = new ReflectionExtension('pcntl');
$constants = $pcntl->getConstants();
if (!$constants) {
// HHVM 3.24.2 returns an empty array.
$constants = get_defined_constants(true);
$constants = $constants['Core'];
}
foreach ($constants as $name => $value) {
if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) {
$signals[$value] = $name;
}
}
unset($constants);
}
$level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL;
$signal = isset($signals[$signo]) ? $signals[$signo] : $signo;
$context = isset($siginfo) ? $siginfo : array();
$this->logger->log($level, sprintf('Program received signal %s', $signal), $context);
if (!isset($this->previousSignalHandler[$signo])) {
return;
}
if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) {
if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch')
&& extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) {
$restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true;
pcntl_signal($signo, SIG_DFL, $restartSyscalls);
pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset);
posix_kill(posix_getpid(), $signo);
pcntl_signal_dispatch();
pcntl_sigprocmask(SIG_SETMASK, $oldset);
pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
}
} elseif (is_callable($this->previousSignalHandler[$signo])) {
if (PHP_VERSION_ID >= 70100) {
$this->previousSignalHandler[$signo]($signo, $siginfo);
} else {
$this->previousSignalHandler[$signo]($signo);
}
}
}
}
<?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;
class Utils
{
/**
* @internal
*/
public static function getClass($object)
{
$class = \get_class($object);
return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
}
}
......@@ -40,7 +40,7 @@ class FluentdFormatterTest extends TestCase
$formatter = new FluentdFormatter();
$this->assertEquals(
'["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]',
'["test",0,{"message":"test","context":[],"extra":[],"level":300,"level_name":"WARNING"}]',
$formatter->format($record)
);
}
......@@ -55,7 +55,7 @@ class FluentdFormatterTest extends TestCase
$formatter = new FluentdFormatter(true);
$this->assertEquals(
'["test.error",0,{"message":"test","extra":[]}]',
'["test.error",0,{"message":"test","context":[],"extra":[]}]',
$formatter->format($record)
);
}
......
......@@ -180,4 +180,40 @@ class JsonFormatterTest extends TestCase
'}';
return $formattedException;
}
public function testNormalizeHandleLargeArraysWithExactly1000Items()
{
$formatter = new NormalizerFormatter();
$largeArray = range(1, 1000);
$res = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'test',
'message' => 'bar',
'context' => array($largeArray),
'datetime' => new \DateTime,
'extra' => array(),
));
$this->assertCount(1000, $res['context'][0]);
$this->assertArrayNotHasKey('...', $res['context'][0]);
}
public function testNormalizeHandleLargeArrays()
{
$formatter = new NormalizerFormatter();
$largeArray = range(1, 2000);
$res = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'test',
'message' => 'bar',
'context' => array($largeArray),
'datetime' => new \DateTime,
'extra' => array(),
));
$this->assertCount(1001, $res['context'][0]);
$this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
}
}
......@@ -193,6 +193,15 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(@json_encode(array($foo, $bar)), $res);
}
public function testCanNormalizeReferences()
{
$formatter = new NormalizerFormatter();
$x = array('foo' => 'bar');
$y = array('x' => &$x);
$x['y'] = &$y;
$formatter->format($y);
}
public function testIgnoresInvalidTypes()
{
// set up the recursion
......@@ -217,6 +226,24 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(@json_encode(array($resource)), $res);
}
public function testNormalizeHandleLargeArraysWithExactly1000Items()
{
$formatter = new NormalizerFormatter();
$largeArray = range(1, 1000);
$res = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'test',
'message' => 'bar',
'context' => array($largeArray),
'datetime' => new \DateTime,
'extra' => array(),
));
$this->assertCount(1000, $res['context'][0]);
$this->assertArrayNotHasKey('...', $res['context'][0]);
}
public function testNormalizeHandleLargeArrays()
{
$formatter = new NormalizerFormatter();
......@@ -231,7 +258,7 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
'extra' => array(),
));
$this->assertCount(1000, $res['context'][0]);
$this->assertCount(1001, $res['context'][0]);
$this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
}
......@@ -380,6 +407,29 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
$result['context']['exception']['trace'][0]
);
}
public function testExceptionTraceDoesNotLeakCallUserFuncArgs()
{
try {
$arg = new TestInfoLeak;
call_user_func(array($this, 'throwHelper'), $arg, $dt = new \DateTime());
} catch (\Exception $e) {
}
$formatter = new NormalizerFormatter();
$record = array('context' => array('exception' => $e));
$result = $formatter->format($record);
$this->assertSame(
'{"function":"throwHelper","class":"Monolog\\\\Formatter\\\\NormalizerFormatterTest","type":"->","args":["[object] (Monolog\\\\Formatter\\\\TestInfoLeak)","'.$dt->format('Y-m-d H:i:s').'"]}',
$result['context']['exception']['trace'][0]
);
}
private function throwHelper($arg)
{
throw new \RuntimeException('Thrown');
}
}
class TestFooNorm
......@@ -421,3 +471,11 @@ class TestToStringError
throw new \RuntimeException('Could not convert to string');
}
}
class TestInfoLeak
{
public function __toString()
{
return 'Sensitive information';
}
}
......@@ -21,7 +21,7 @@ class BrowserConsoleHandlerTest extends TestCase
{
protected function setUp()
{
BrowserConsoleHandler::reset();
BrowserConsoleHandler::resetStatic();
}
protected function generateScript()
......
......@@ -21,7 +21,7 @@ class ChromePHPHandlerTest extends TestCase
{
protected function setUp()
{
TestChromePHPHandler::reset();
TestChromePHPHandler::resetStatic();
$_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0';
}
......@@ -136,7 +136,7 @@ class TestChromePHPHandler extends ChromePHPHandler
{
protected $headers = array();
public static function reset()
public static function resetStatic()
{
self::$initialized = false;
self::$overflowed = false;
......
......@@ -58,7 +58,7 @@ class FingersCrossedHandlerTest extends TestCase
* @covers Monolog\Handler\FingersCrossedHandler::activate
* @covers Monolog\Handler\FingersCrossedHandler::reset
*/
public function testHandleRestartBufferingAfterReset()
public function testHandleResetBufferingAfterReset()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test);
......@@ -76,7 +76,7 @@ class FingersCrossedHandlerTest extends TestCase
* @covers Monolog\Handler\FingersCrossedHandler::handle
* @covers Monolog\Handler\FingersCrossedHandler::activate
*/
public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false);
......
......@@ -21,7 +21,7 @@ class FirePHPHandlerTest extends TestCase
{
public function setUp()
{
TestFirePHPHandler::reset();
TestFirePHPHandler::resetStatic();
$_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0';
}
......@@ -77,7 +77,7 @@ class TestFirePHPHandler extends FirePHPHandler
{
protected $headers = array();
public static function reset()
public static function resetStatic()
{
self::$initialized = false;
self::$sendHeaders = true;
......
<?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\TestCase;
use Monolog\Logger;
/**
* @author Robert Kaufmann III <rok3@rok3.me>
* @author Gabriel Machado <gabriel.ms1@hotmail.com>
*/
class InsightOpsHandlerTest extends TestCase
{
/**
* @var resource
*/
private $resource;
/**
* @var LogEntriesHandler
*/
private $handler;
public function testWriteContent()
{
$this->createHandler();
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test'));
fseek($this->resource, 0);
$content = fread($this->resource, 1024);
$this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content);
}
public function testWriteBatchContent()
{
$this->createHandler();
$this->handler->handleBatch($this->getMultipleRecords());
fseek($this->resource, 0);
$content = fread($this->resource, 1024);
$this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content);
}
private function createHandler()
{
$useSSL = extension_loaded('openssl');
$args = array('testToken', 'us', $useSSL, Logger::DEBUG, true);
$this->resource = fopen('php://memory', 'a');
$this->handler = $this->getMock(
'\Monolog\Handler\InsightOpsHandler',
array('fsockopen', 'streamSetTimeout', 'closeSocket'),
$args
);
$reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($this->handler, 'localhost:1234');
$this->handler->expects($this->any())
->method('fsockopen')
->will($this->returnValue($this->resource));
$this->handler->expects($this->any())
->method('streamSetTimeout')
->will($this->returnValue(true));
$this->handler->expects($this->any())
->method('closeSocket')
->will($this->returnValue(true));
}
}
......@@ -191,6 +191,40 @@ class RotatingFileHandlerTest extends TestCase
);
}
/**
* @dataProvider rotationWhenSimilarFilesExistTests
*/
public function testRotationWhenSimilarFileNamesExist($dateFormat)
{
touch($old1 = __DIR__.'/Fixtures/foo-foo-'.date($dateFormat).'.rot');
touch($old2 = __DIR__.'/Fixtures/foo-bar-'.date($dateFormat).'.rot');
$log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot';
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
$handler->setFormatter($this->getIdentityFormatter());
$handler->setFilenameFormat('{filename}-{date}', $dateFormat);
$handler->handle($this->getRecord());
$handler->close();
$this->assertTrue(file_exists($log));
}
public function rotationWhenSimilarFilesExistTests()
{
return array(
'Rotation is triggered when the file of the current day is not present but similar exists'
=> array(RotatingFileHandler::FILE_PER_DAY),
'Rotation is triggered when the file of the current month is not present but similar exists'
=> array(RotatingFileHandler::FILE_PER_MONTH),
'Rotation is triggered when the file of the current year is not present but similar exists'
=> array(RotatingFileHandler::FILE_PER_YEAR),
);
}
public function testReuseCurrentFile()
{
$log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
......
......@@ -320,12 +320,12 @@ class SlackRecordTest extends TestCase
'short' => false,
),
array(
'title' => 'tags',
'title' => 'Tags',
'value' => sprintf('```%s```', json_encode($extra['tags'])),
'short' => false
),
array(
'title' => 'test',
'title' => 'Test',
'value' => $context['test'],
'short' => false
)
......@@ -353,6 +353,14 @@ class SlackRecordTest extends TestCase
$this->assertSame($record['datetime']->getTimestamp(), $attachment['ts']);
}
public function testContextHasException()
{
$record = $this->getRecord(Logger::CRITICAL, 'This is a critical message.', array('exception' => new \Exception()));
$slackRecord = new SlackRecord(null, null, true, null, false, true);
$data = $slackRecord->getSlackData($record);
$this->assertInternalType('string', $data['attachments'][0]['fields'][1]['value']);
}
public function testExcludeExtraAndContextFields()
{
$record = $this->getRecord(
......@@ -368,12 +376,12 @@ class SlackRecordTest extends TestCase
$expected = array(
array(
'title' => 'info',
'title' => 'Info',
'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)),
'short' => false
),
array(
'title' => 'tags',
'title' => 'Tags',
'value' => sprintf('```%s```', json_encode(array('web'))),
'short' => false
),
......
......@@ -77,6 +77,13 @@ class SocketHandlerTest extends TestCase
$this->assertEquals(10.25, $this->handler->getWritingTimeout());
}
public function testSetChunkSize()
{
$this->createHandler('localhost:1234');
$this->handler->setChunkSize(1025);
$this->assertEquals(1025, $this->handler->getChunkSize());
}
public function testSetConnectionString()
{
$this->createHandler('tcp://localhost:9090');
......@@ -121,6 +128,19 @@ class SocketHandlerTest extends TestCase
}
/**
* @expectedException UnexpectedValueException
*/
public function testExceptionIsThrownIfCannotSetChunkSize()
{
$this->setMockHandler(array('streamSetChunkSize'));
$this->handler->setChunkSize(8192);
$this->handler->expects($this->once())
->method('streamSetChunkSize')
->will($this->returnValue(false));
$this->writeRecord('Hello world');
}
/**
* @expectedException RuntimeException
*/
public function testWriteFailsOnIfFwriteReturnsFalse()
......@@ -304,6 +324,12 @@ class SocketHandlerTest extends TestCase
->will($this->returnValue(true));
}
if (!in_array('streamSetChunkSize', $methods)) {
$this->handler->expects($this->any())
->method('streamSetChunkSize')
->will($this->returnValue(8192));
}
$this->handler->setFormatter($this->getIdentityFormatter());
}
}
......@@ -54,6 +54,52 @@ class TestHandlerTest extends TestCase
$this->assertEquals(array($record), $records);
}
public function testHandlerAssertEmptyContext() {
$handler = new TestHandler;
$record = $this->getRecord(Logger::WARNING, 'test', array());
$this->assertFalse($handler->hasWarning(array(
'message' => 'test',
'context' => array(),
)));
$handler->handle($record);
$this->assertTrue($handler->hasWarning(array(
'message' => 'test',
'context' => array(),
)));
$this->assertFalse($handler->hasWarning(array(
'message' => 'test',
'context' => array(
'foo' => 'bar'
),
)));
}
public function testHandlerAssertNonEmptyContext() {
$handler = new TestHandler;
$record = $this->getRecord(Logger::WARNING, 'test', array('foo' => 'bar'));
$this->assertFalse($handler->hasWarning(array(
'message' => 'test',
'context' => array(
'foo' => 'bar'
),
)));
$handler->handle($record);
$this->assertTrue($handler->hasWarning(array(
'message' => 'test',
'context' => array(
'foo' => 'bar'
),
)));
$this->assertFalse($handler->hasWarning(array(
'message' => 'test',
'context' => array(),
)));
}
public function methodProvider()
{
return array(
......
......@@ -88,6 +88,29 @@ class WhatFailureGroupHandlerTest extends TestCase
}
/**
* @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch
*/
public function testHandleBatchUsesProcessors()
{
$testHandlers = array(new TestHandler(), new TestHandler());
$handler = new WhatFailureGroupHandler($testHandlers);
$handler->pushProcessor(function ($record) {
$record['extra']['foo'] = true;
return $record;
});
$handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)));
foreach ($testHandlers as $test) {
$this->assertTrue($test->hasDebugRecords());
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue(count($test->getRecords()) === 2);
$records = $test->getRecords();
$this->assertTrue($records[0]['extra']['foo']);
$this->assertTrue($records[1]['extra']['foo']);
}
}
/**
* @covers Monolog\Handler\WhatFailureGroupHandler::handle
*/
public function testHandleException()
......
......@@ -545,4 +545,146 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'),
);
}
/**
* @covers Monolog\Logger::setExceptionHandler
*/
public function testSetExceptionHandler()
{
$logger = new Logger(__METHOD__);
$this->assertNull($logger->getExceptionHandler());
$callback = function ($ex) {
};
$logger->setExceptionHandler($callback);
$this->assertEquals($callback, $logger->getExceptionHandler());
}
/**
* @covers Monolog\Logger::setExceptionHandler
* @expectedException InvalidArgumentException
*/
public function testBadExceptionHandlerType()
{
$logger = new Logger(__METHOD__);
$logger->setExceptionHandler(false);
}
/**
* @covers Monolog\Logger::handleException
* @expectedException Exception
*/
public function testDefaultHandleException()
{
$logger = new Logger(__METHOD__);
$handler = $this->getMock('Monolog\Handler\HandlerInterface');
$handler->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler->expects($this->any())
->method('handle')
->will($this->throwException(new \Exception('Some handler exception')))
;
$logger->pushHandler($handler);
$logger->info('test');
}
/**
* @covers Monolog\Logger::handleException
* @covers Monolog\Logger::addRecord
*/
public function testCustomHandleException()
{
$logger = new Logger(__METHOD__);
$that = $this;
$logger->setExceptionHandler(function ($e, $record) use ($that) {
$that->assertEquals($e->getMessage(), 'Some handler exception');
$that->assertTrue(is_array($record));
$that->assertEquals($record['message'], 'test');
});
$handler = $this->getMock('Monolog\Handler\HandlerInterface');
$handler->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler->expects($this->any())
->method('handle')
->will($this->throwException(new \Exception('Some handler exception')))
;
$logger->pushHandler($handler);
$logger->info('test');
}
public function testReset()
{
$logger = new Logger('app');
$testHandler = new Handler\TestHandler();
$bufferHandler = new Handler\BufferHandler($testHandler);
$groupHandler = new Handler\GroupHandler(array($bufferHandler));
$fingersCrossedHandler = new Handler\FingersCrossedHandler($groupHandler);
$logger->pushHandler($fingersCrossedHandler);
$processorUid1 = new Processor\UidProcessor(10);
$uid1 = $processorUid1->getUid();
$groupHandler->pushProcessor($processorUid1);
$processorUid2 = new Processor\UidProcessor(5);
$uid2 = $processorUid2->getUid();
$logger->pushProcessor($processorUid2);
$getProperty = function ($object, $property) {
$reflectionProperty = new \ReflectionProperty(get_class($object), $property);
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($object);
};
$that = $this;
$assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler, $that) {
$that->assertEmpty($getProperty($bufferHandler, 'buffer'));
};
$assertBuffersEmpty = function() use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler, $that) {
$assertBufferOfBufferHandlerEmpty();
$that->assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));
};
$logger->debug('debug');
$logger->reset();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasDebugRecords());
$this->assertFalse($testHandler->hasErrorRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
$logger->debug('debug');
$logger->error('error');
$logger->reset();
$assertBuffersEmpty();
$this->assertTrue($testHandler->hasDebugRecords());
$this->assertTrue($testHandler->hasErrorRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
$logger->info('info');
$this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer'));
$assertBufferOfBufferHandlerEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$logger->reset();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
$logger->notice('notice');
$logger->emergency('emergency');
$logger->reset();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$this->assertTrue($testHandler->hasNoticeRecords());
$this->assertTrue($testHandler->hasEmergencyRecords());
$this->assertNotSame($uid1, $processorUid1->getUid());
$this->assertNotSame($uid2, $processorUid2->getUid());
}
}
......@@ -25,9 +25,12 @@
"symfony/translation": "~2.6 || ~3.0 || ~4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2",
"phpunit/phpunit": "^4.8.35 || ^5.7"
},
"suggest": {
"friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.",
"phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors."
},
"autoload": {
"psr-4": {
"": "src/"
......
......@@ -195,7 +195,7 @@ class Carbon extends DateTime implements JsonSerializable
't' => '(2[89]|3[01])',
'L' => '(0|1)',
'o' => '([1-9][0-9]{0,4})',
'Y' => '([1-9][0-9]{0,4})',
'Y' => '([1-9]?[0-9]{4})',
'y' => '([0-9]{2})',
'a' => '(am|pm)',
'A' => '(AM|PM)',
......@@ -217,8 +217,8 @@ class Carbon extends DateTime implements JsonSerializable
'U' => '([0-9]*)',
// The formats below are combinations of the above formats.
'c' => '(([1-9][0-9]{0,4})\-(1[012]|0[1-9])\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\+\-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP
'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9][0-9]{0,4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\+\-](1[012]|0[0-9])([0134][05]))', // D, j M Y H:i:s O
'c' => '(([1-9]?[0-9]{4})\-(1[012]|0[1-9])\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\+\-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP
'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\+\-](1[012]|0[0-9])([0134][05]))', // D, j M Y H:i:s O
);
/**
......@@ -3976,7 +3976,13 @@ class Carbon extends DateTime implements JsonSerializable
$value = $diff->days * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE +
$diff->h * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE +
$diff->i * static::SECONDS_PER_MINUTE +
$diff->s;
$diff->s - (
version_compare(PHP_VERSION, '7.1.8-dev', '>=') &&
property_exists($diff, 'f') &&
$diff->f < 0
? 1
: 0
);
return $absolute || !$diff->invert ? $value : -$value;
}
......
......@@ -498,6 +498,11 @@ class CarbonInterval extends DateInterval
{
$instance = new static(static::getDateIntervalSpec($di));
$instance->invert = $di->invert;
foreach (array('y', 'm', 'd', 'h', 'i', 's') as $unit) {
if ($di->$unit < 0) {
$instance->$unit *= -1;
}
}
return $instance;
}
......@@ -961,15 +966,15 @@ class CarbonInterval extends DateInterval
public static function getDateIntervalSpec(DateInterval $interval)
{
$date = array_filter(array(
static::PERIOD_YEARS => $interval->y,
static::PERIOD_MONTHS => $interval->m,
static::PERIOD_DAYS => $interval->d,
static::PERIOD_YEARS => abs($interval->y),
static::PERIOD_MONTHS => abs($interval->m),
static::PERIOD_DAYS => abs($interval->d),
));
$time = array_filter(array(
static::PERIOD_HOURS => $interval->h,
static::PERIOD_MINUTES => $interval->i,
static::PERIOD_SECONDS => $interval->s,
static::PERIOD_HOURS => abs($interval->h),
static::PERIOD_MINUTES => abs($interval->i),
static::PERIOD_SECONDS => abs($interval->s),
));
$specString = static::PERIOD_PREFIX;
......
# monitorDing
Laravel 插件,用于给钉钉自定义机器人发送消息
# 引入步骤
发布视图
```
php artisan vendor:publish
```
`config/app.php` 添加
```
Redgo\MonitorDing\MonitorDingServiceProvider::class,
```
如果需要配置门面模式, 在 `config/app.php` 添加
```
'MonitorDing' => Redgo\MonitorDing\Facades\MonitorDing::class,
```
# 使用
1. 首先在配置文件 `config/monitorDing` 修改 `webhook`
{
"name": "redgo/monitor-ding",
"license": "MIT",
"description": "用于给钉钉自定义机器人发送消息",
"type": "library",
"authors": [
{
"name": "redgo",
"email": "1223858310@qq.com"
}
],
"require": {
"laravel/framework": "^5.1"
},
"autoload": {
"psr-4": {
"Redgo\\MonitorDing\\": "src/"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
<?php namespace Redgo\MonitorDing\Exceptions;
use Exception;
class SendErrorException extends Exception {
}
\ No newline at end of file
<?php namespace Redgo\MonitorDing\Facades;
use Illuminate\Support\Facades\Facade;
class MonitorDing extends Facade {
/**
* Return facade accessor
* @return string
*/
protected static function getFacadeAccessor()
{
return 'monitorDing';
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment