Procházet zdrojové kódy

Merge branch '1.x' into rgb/signal-handler

Robert Gust-Bardon před 7 roky
rodič
revize
ad5b8f41b0
52 změnil soubory, kde provedl 695 přidání a 201 odebrání
  1. 1 1
      .travis.yml
  2. 0 1
      README.md
  3. 1 0
      doc/02-handlers-formatters-processors.md
  4. 9 1
      src/Monolog/ErrorHandler.php
  5. 1 0
      src/Monolog/Formatter/FluentdFormatter.php
  6. 1 1
      src/Monolog/Formatter/HtmlFormatter.php
  7. 9 4
      src/Monolog/Formatter/JsonFormatter.php
  8. 8 3
      src/Monolog/Formatter/NormalizerFormatter.php
  9. 2 2
      src/Monolog/Formatter/WildfireFormatter.php
  10. 6 6
      src/Monolog/Handler/AbstractHandler.php
  11. 3 3
      src/Monolog/Handler/AbstractSyslogHandler.php
  12. 22 22
      src/Monolog/Handler/BrowserConsoleHandler.php
  13. 2 2
      src/Monolog/Handler/BufferHandler.php
  14. 5 5
      src/Monolog/Handler/ChromePHPHandler.php
  15. 1 1
      src/Monolog/Handler/DeduplicationHandler.php
  16. 4 4
      src/Monolog/Handler/ElasticSearchHandler.php
  17. 4 4
      src/Monolog/Handler/ErrorLogHandler.php
  18. 2 2
      src/Monolog/Handler/FilterHandler.php
  19. 1 1
      src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php
  20. 2 2
      src/Monolog/Handler/FingersCrossedHandler.php
  21. 1 1
      src/Monolog/Handler/FirePHPHandler.php
  22. 0 8
      src/Monolog/Handler/GelfHandler.php
  23. 2 2
      src/Monolog/Handler/GroupHandler.php
  24. 2 2
      src/Monolog/Handler/HandlerInterface.php
  25. 4 4
      src/Monolog/Handler/IFTTTHandler.php
  26. 62 0
      src/Monolog/Handler/InsightOpsHandler.php
  27. 1 1
      src/Monolog/Handler/MandrillHandler.php
  28. 1 1
      src/Monolog/Handler/NewRelicHandler.php
  29. 1 1
      src/Monolog/Handler/PsrHandler.php
  30. 3 3
      src/Monolog/Handler/PushoverHandler.php
  31. 2 2
      src/Monolog/Handler/RavenHandler.php
  32. 3 3
      src/Monolog/Handler/RotatingFileHandler.php
  33. 5 5
      src/Monolog/Handler/Slack/SlackRecord.php
  34. 5 0
      src/Monolog/Handler/SlackHandler.php
  35. 5 0
      src/Monolog/Handler/SlackWebhookHandler.php
  36. 43 4
      src/Monolog/Handler/SocketHandler.php
  37. 3 3
      src/Monolog/Handler/StreamHandler.php
  38. 14 2
      src/Monolog/Handler/SwiftMailerHandler.php
  39. 5 5
      src/Monolog/Handler/SyslogHandler.php
  40. 6 6
      src/Monolog/Handler/SyslogUdpHandler.php
  41. 10 0
      src/Monolog/Handler/WhatFailureGroupHandler.php
  42. 121 74
      src/Monolog/Logger.php
  43. 2 2
      src/Monolog/Processor/MemoryProcessor.php
  44. 2 2
      tests/Monolog/Formatter/FluentdFormatterTest.php
  45. 36 0
      tests/Monolog/Formatter/JsonFormatterTest.php
  46. 28 1
      tests/Monolog/Formatter/NormalizerFormatterTest.php
  47. 80 0
      tests/Monolog/Handler/InsightOpsHandlerTest.php
  48. 34 0
      tests/Monolog/Handler/RotatingFileHandlerTest.php
  49. 12 4
      tests/Monolog/Handler/Slack/SlackRecordTest.php
  50. 26 0
      tests/Monolog/Handler/SocketHandlerTest.php
  51. 23 0
      tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
  52. 69 0
      tests/Monolog/LoggerTest.php

+ 1 - 1
.travis.yml

@@ -9,7 +9,7 @@ php:
   - 5.6
   - 7.0
   - 7.1
-  - hhvm
+  - 7.2
   - nightly
 
 matrix:

+ 0 - 1
README.md

@@ -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

+ 1 - 0
doc/02-handlers-formatters-processors.md

@@ -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
 

+ 9 - 1
src/Monolog/ErrorHandler.php

@@ -39,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);
 
     private $previousSignalHandler = array();
@@ -192,6 +193,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) {
@@ -213,7 +221,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) {

+ 1 - 0
src/Monolog/Formatter/FluentdFormatter.php

@@ -62,6 +62,7 @@ class FluentdFormatter implements FormatterInterface
 
         $message = array(
             'message' => $record['message'],
+            'context' => $record['context'],
             'extra' => $record['extra'],
         );
 

+ 1 - 1
src/Monolog/Formatter/HtmlFormatter.php

@@ -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>";
     }
 
     /**

+ 9 - 4
src/Monolog/Formatter/JsonFormatter.php

@@ -138,18 +138,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;

+ 8 - 3
src/Monolog/Formatter/NormalizerFormatter.php

@@ -55,8 +55,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 +79,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;

+ 2 - 2
src/Monolog/Formatter/WildfireFormatter.php

@@ -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);
     }
 }

+ 6 - 6
src/Monolog/Handler/AbstractHandler.php

@@ -32,8 +32,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 +141,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 +155,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()
     {

+ 3 - 3
src/Monolog/Handler/AbstractSyslogHandler.php

@@ -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)
     {

+ 22 - 22
src/Monolog/Handler/BrowserConsoleHandler.php

@@ -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,18 +58,18 @@ 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::reset();
         }
     }
 
@@ -78,7 +78,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
      */
     public static function reset()
     {
-        self::$records = array();
+        static::$records = array();
     }
 
     /**
@@ -133,18 +133,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 +154,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 +198,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 +220,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)

+ 2 - 2
src/Monolog/Handler/BufferHandler.php

@@ -34,8 +34,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)
     {

+ 5 - 5
src/Monolog/Handler/ChromePHPHandler.php

@@ -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()
     {

+ 1 - 1
src/Monolog/Handler/DeduplicationHandler.php

@@ -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)
     {

+ 4 - 4
src/Monolog/Handler/ElasticSearchHandler.php

@@ -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)
     {

+ 4 - 4
src/Monolog/Handler/ErrorLogHandler.php

@@ -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)
     {

+ 2 - 2
src/Monolog/Handler/FilterHandler.php

@@ -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)
     {

+ 1 - 1
src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php

@@ -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);
 }

+ 2 - 2
src/Monolog/Handler/FingersCrossedHandler.php

@@ -41,8 +41,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)

+ 1 - 1
src/Monolog/Handler/FirePHPHandler.php

@@ -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()
     {

+ 0 - 8
src/Monolog/Handler/GelfHandler.php

@@ -47,14 +47,6 @@ class GelfHandler extends AbstractProcessingHandler
         $this->publisher = $publisher;
     }
 
-    /**
-     * {@inheritdoc}
-     */
-    public function close()
-    {
-        $this->publisher = null;
-    }
-
     /**
      * {@inheritdoc}
      */

+ 2 - 2
src/Monolog/Handler/GroupHandler.php

@@ -23,8 +23,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)
     {

+ 2 - 2
src/Monolog/Handler/HandlerInterface.php

@@ -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);

+ 4 - 4
src/Monolog/Handler/IFTTTHandler.php

@@ -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)
     {

+ 62 - 0
src/Monolog/Handler/InsightOpsHandler.php

@@ -0,0 +1,62 @@
+<?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'];
+    }
+}

+ 1 - 1
src/Monolog/Handler/MandrillHandler.php

@@ -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)
     {

+ 1 - 1
src/Monolog/Handler/NewRelicHandler.php

@@ -84,7 +84,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 {

+ 1 - 1
src/Monolog/Handler/PsrHandler.php

@@ -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)
     {

+ 3 - 3
src/Monolog/Handler/PushoverHandler.php

@@ -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;
     }
 }

+ 2 - 2
src/Monolog/Handler/RavenHandler.php

@@ -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);

+ 3 - 3
src/Monolog/Handler/RotatingFileHandler.php

@@ -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)
     {
@@ -166,7 +166,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'])) {

+ 5 - 5
src/Monolog/Handler/Slack/SlackRecord.php

@@ -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);
         }
 

+ 5 - 0
src/Monolog/Handler/SlackHandler.php

@@ -75,6 +75,11 @@ class SlackHandler extends SocketHandler
         return $this->slackRecord;
     }
 
+    public function getToken()
+    {
+        return $this->token;
+    }
+
     /**
      * {@inheritdoc}
      *

+ 5 - 0
src/Monolog/Handler/SlackWebhookHandler.php

@@ -70,6 +70,11 @@ class SlackWebhookHandler extends AbstractProcessingHandler
         return $this->slackRecord;
     }
 
+    public function getWebhookUrl()
+    {
+        return $this->webhookUrl;
+    }
+
     /**
      * {@inheritdoc}
      *

+ 43 - 4
src/Monolog/Handler/SocketHandler.php

@@ -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;
     }
 
     /**
@@ -127,6 +128,16 @@ class SocketHandler extends AbstractProcessingHandler
         $this->writingTimeout = (float) $seconds;
     }
 
+    /**
+     * 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
      *
@@ -177,6 +188,16 @@ class SocketHandler extends AbstractProcessingHandler
         return $this->writingTimeout;
     }
 
+    /**
+     * Get current chunk size
+     *
+     * @return float
+     */
+    public function getChunkSize()
+    {
+        return $this->chunkSize;
+    }
+
     /**
      * Check to see if the socket is currently available.
      *
@@ -219,6 +240,16 @@ class SocketHandler extends AbstractProcessingHandler
         return stream_set_timeout($this->resource, $seconds, $microseconds);
     }
 
+    /**
+     * 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
      */
@@ -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);

+ 3 - 3
src/Monolog/Handler/StreamHandler.php

@@ -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));
             }
         }

+ 14 - 2
src/Monolog/Handler/SwiftMailerHandler.php

@@ -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)
     {
@@ -47,6 +48,17 @@ class SwiftMailerHandler extends MailHandler
         $this->mailer->send($this->buildMessage($content, $records));
     }
 
+    /**
+     * 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
      *
@@ -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)));
         }
 

+ 5 - 5
src/Monolog/Handler/SyslogHandler.php

@@ -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)
     {

+ 6 - 6
src/Monolog/Handler/SyslogUdpHandler.php

@@ -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')
     {

+ 10 - 0
src/Monolog/Handler/WhatFailureGroupHandler.php

@@ -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);

+ 121 - 74
src/Monolog/Logger.php

@@ -15,6 +15,7 @@ use Monolog\Handler\HandlerInterface;
 use Monolog\Handler\StreamHandler;
 use Psr\Log\LoggerInterface;
 use Psr\Log\InvalidArgumentException;
+use Exception;
 
 /**
  * Monolog log channel
@@ -133,6 +134,11 @@ class Logger implements LoggerInterface
      */
     protected $microsecondTimestamps = true;
 
+    /**
+     * @var callable
+     */
+    protected $exceptionHandler;
+
     /**
      * @param string             $name       The logging channel
      * @param HandlerInterface[] $handlers   Optional stack of handlers, the first one in the array is called first, etc.
@@ -141,7 +147,7 @@ class Logger implements LoggerInterface
     public function __construct($name, array $handlers = array(), array $processors = array())
     {
         $this->name = $name;
-        $this->handlers = $handlers;
+        $this->setHandlers($handlers);
         $this->processors = $processors;
     }
 
@@ -281,7 +287,7 @@ class Logger implements LoggerInterface
      * @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
+     * @return bool Whether the record has been processed
      */
     public function addRecord($level, $message, array $context = array())
     {
@@ -329,16 +335,20 @@ class Logger implements LoggerInterface
             'extra' => array(),
         );
 
-        foreach ($this->processors as $processor) {
-            $record = call_user_func($processor, $record);
-        }
-
-        while ($handler = current($this->handlers)) {
-            if (true === $handler->handle($record)) {
-                break;
+        try {
+            foreach ($this->processors as $processor) {
+                $record = call_user_func($processor, $record);
             }
 
-            next($this->handlers);
+            while ($handler = current($this->handlers)) {
+                if (true === $handler->handle($record)) {
+                    break;
+                }
+
+                next($this->handlers);
+            }
+        } catch (Exception $e) {
+            $this->handleException($e, $record);
         }
 
         return true;
@@ -347,9 +357,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addDebug($message, array $context = array())
     {
@@ -359,9 +369,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addInfo($message, array $context = array())
     {
@@ -371,9 +381,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addNotice($message, array $context = array())
     {
@@ -383,9 +393,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addWarning($message, array $context = array())
     {
@@ -395,9 +405,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addError($message, array $context = array())
     {
@@ -407,9 +417,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addCritical($message, array $context = array())
     {
@@ -419,9 +429,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addAlert($message, array $context = array())
     {
@@ -431,9 +441,9 @@ class Logger implements LoggerInterface
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function addEmergency($message, array $context = array())
     {
@@ -484,7 +494,7 @@ class Logger implements LoggerInterface
      * Checks whether the Logger has a handler that listens on the given level
      *
      * @param  int     $level
-     * @return Boolean
+     * @return bool
      */
     public function isHandling($level)
     {
@@ -501,15 +511,52 @@ class Logger implements LoggerInterface
         return false;
     }
 
+    /**
+     * Set a custom exception handler
+     *
+     * @param  callable $callback
+     * @return $this
+     */
+    public function setExceptionHandler($callback)
+    {
+        if (!is_callable($callback)) {
+            throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given');
+        }
+        $this->exceptionHandler = $callback;
+
+        return $this;
+    }
+
+    /**
+     * @return callable
+     */
+    public function getExceptionHandler()
+    {
+        return $this->exceptionHandler;
+    }
+
+    /**
+     * Delegates exception management to the custom exception handler,
+     * or throws the exception if no custom handler is set.
+     */
+    protected function handleException(Exception $e, array $record)
+    {
+        if (!$this->exceptionHandler) {
+            throw $e;
+        }
+
+        call_user_func($this->exceptionHandler, $e, $record);
+    }
+
     /**
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function log($level, $message, array $context = array())
     {
@@ -523,9 +570,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function debug($message, array $context = array())
     {
@@ -537,9 +584,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function info($message, array $context = array())
     {
@@ -551,9 +598,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function notice($message, array $context = array())
     {
@@ -565,9 +612,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function warn($message, array $context = array())
     {
@@ -579,9 +626,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function warning($message, array $context = array())
     {
@@ -593,9 +640,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function err($message, array $context = array())
     {
@@ -607,9 +654,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function error($message, array $context = array())
     {
@@ -621,9 +668,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function crit($message, array $context = array())
     {
@@ -635,9 +682,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function critical($message, array $context = array())
     {
@@ -649,9 +696,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function alert($message, array $context = array())
     {
@@ -663,9 +710,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function emerg($message, array $context = array())
     {
@@ -677,9 +724,9 @@ class Logger implements LoggerInterface
      *
      * 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
+     * @param  string $message The log message
+     * @param  array  $context The log context
+     * @return bool   Whether the record has been processed
      */
     public function emergency($message, array $context = array())
     {

+ 2 - 2
src/Monolog/Processor/MemoryProcessor.php

@@ -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;
     }
 
     /**

+ 2 - 2
tests/Monolog/Formatter/FluentdFormatterTest.php

@@ -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)
         );
     }

+ 36 - 0
tests/Monolog/Formatter/JsonFormatterTest.php

@@ -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]['...']);
+    }
 }

+ 28 - 1
tests/Monolog/Formatter/NormalizerFormatterTest.php

@@ -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]['...']);
     }
 

+ 80 - 0
tests/Monolog/Handler/InsightOpsHandlerTest.php

@@ -0,0 +1,80 @@
+<?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));
+    }
+}

+ 34 - 0
tests/Monolog/Handler/RotatingFileHandlerTest.php

@@ -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';

+ 12 - 4
tests/Monolog/Handler/Slack/SlackRecordTest.php

@@ -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
             ),

+ 26 - 0
tests/Monolog/Handler/SocketHandlerTest.php

@@ -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');
@@ -120,6 +127,19 @@ class SocketHandlerTest extends TestCase
         $this->writeRecord('Hello world');
     }
 
+    /**
+     * @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
      */
@@ -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());
     }
 }

+ 23 - 0
tests/Monolog/Handler/WhatFailureGroupHandlerTest.php

@@ -87,6 +87,29 @@ class WhatFailureGroupHandlerTest extends TestCase
         $this->assertTrue($records[0]['extra']['foo']);
     }
 
+    /**
+     * @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
      */

+ 69 - 0
tests/Monolog/LoggerTest.php

@@ -545,4 +545,73 @@ 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');
+    }
 }