diff --git a/doc/01-usage.md b/doc/01-usage.md index 75bc4023..8e2551f3 100644 --- a/doc/01-usage.md +++ b/doc/01-usage.md @@ -189,6 +189,9 @@ $logger->pushHandler($firephp); $securityLogger = new Logger('security'); $securityLogger->pushHandler($stream); $securityLogger->pushHandler($firephp); + +// Or clone the first one to only change the channel +$securityLogger = $logger->withName('security'); ``` ## Customizing the log format diff --git a/doc/02-handlers-formatters-processors.md b/doc/02-handlers-formatters-processors.md index 71b1ca9f..95ba8e53 100644 --- a/doc/02-handlers-formatters-processors.md +++ b/doc/02-handlers-formatters-processors.md @@ -105,6 +105,8 @@ - _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger - _TestHandler_: Used for testing, it records everything that is sent to it and has accessors to read out the information. +- _HandlerWrapper_: A simple handler wrapper you can inherit from to create + your own wrappers easily. ## Formatters diff --git a/src/Monolog/Formatter/JsonFormatter.php b/src/Monolog/Formatter/JsonFormatter.php index e5a1d2c4..06e9d13d 100644 --- a/src/Monolog/Formatter/JsonFormatter.php +++ b/src/Monolog/Formatter/JsonFormatter.php @@ -11,6 +11,8 @@ namespace Monolog\Formatter; +use Exception; + /** * Encodes whatever record data is passed to it as json * @@ -18,13 +20,17 @@ namespace Monolog\Formatter; * * @author Jordi Boggiano */ -class JsonFormatter implements FormatterInterface +class JsonFormatter extends NormalizerFormatter { const BATCH_MODE_JSON = 1; const BATCH_MODE_NEWLINES = 2; protected $batchMode; protected $appendNewline; + /** + * @var bool + */ + protected $includeStacktraces = false; /** * @param int $batchMode @@ -64,7 +70,7 @@ class JsonFormatter implements FormatterInterface */ public function format(array $record) { - return json_encode($record) . ($this->appendNewline ? "\n" : ''); + return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); } /** @@ -82,6 +88,14 @@ class JsonFormatter implements FormatterInterface } } + /** + * @param bool $include + */ + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + } + /** * Return a JSON-encoded array of records. * @@ -90,7 +104,7 @@ class JsonFormatter implements FormatterInterface */ protected function formatBatchJson(array $records) { - return json_encode($records); + return $this->toJson($this->normalize($records), true); } /** @@ -113,4 +127,76 @@ class JsonFormatter implements FormatterInterface return implode("\n", $records); } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data) + { + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items, aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof Exception) { + return $this->normalizeException($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @param Exception|Throwable $e + * + * @return array + */ + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($this->includeStacktraces) { + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->normalize($frame); + } + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } } diff --git a/src/Monolog/Formatter/LineFormatter.php b/src/Monolog/Formatter/LineFormatter.php index e99c032c..08929d2d 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -11,8 +11,6 @@ namespace Monolog\Formatter; -use Exception; - /** * Formats incoming records into a one-line string * @@ -78,6 +76,13 @@ class LineFormatter extends NormalizerFormatter } } + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + if ($this->ignoreEmptyContextAndExtra) { if (empty($vars['context'])) { unset($vars['context']); @@ -114,8 +119,13 @@ class LineFormatter extends NormalizerFormatter return $this->replaceNewlines($this->convertToString($value)); } - protected function normalizeException(Exception $e) + protected function normalizeException($e) { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + $previousText = ''; if ($previous = $e->getPrevious()) { do { diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index 2a784543..f7dc3004 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -90,7 +90,8 @@ class NormalizerFormatter implements FormatterInterface } if (is_object($data)) { - if ($data instanceof Exception) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { return $this->normalizeException($data); } @@ -112,8 +113,13 @@ class NormalizerFormatter implements FormatterInterface return '[unknown('.gettype($data).')]'; } - protected function normalizeException(Exception $e) + protected function normalizeException($e) { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + $data = array( 'class' => get_class($e), 'message' => $e->getMessage(), diff --git a/src/Monolog/Handler/AbstractSyslogHandler.php b/src/Monolog/Handler/AbstractSyslogHandler.php index 309cb89c..e2b2832d 100644 --- a/src/Monolog/Handler/AbstractSyslogHandler.php +++ b/src/Monolog/Handler/AbstractSyslogHandler.php @@ -70,6 +70,15 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler $this->facilities['local5'] = LOG_LOCAL5; $this->facilities['local6'] = LOG_LOCAL6; $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 } // convert textual description of facility to syslog constant diff --git a/src/Monolog/Handler/AmqpHandler.php b/src/Monolog/Handler/AmqpHandler.php index 7c20cfb7..bf0552c7 100644 --- a/src/Monolog/Handler/AmqpHandler.php +++ b/src/Monolog/Handler/AmqpHandler.php @@ -55,18 +55,12 @@ class AmqpHandler extends AbstractProcessingHandler protected function write(array $record) { $data = $record["formatted"]; - - $routingKey = sprintf( - '%s.%s', - // TODO 2.0 remove substr call - substr($record['level_name'], 0, 4), - $record['channel'] - ); + $routingKey = $this->getRoutingKey($record); if ($this->exchange instanceof AMQPExchange) { $this->exchange->publish( $data, - strtolower($routingKey), + $routingKey, 0, array( 'delivery_mode' => 2, @@ -75,19 +69,74 @@ class AmqpHandler extends AbstractProcessingHandler ); } else { $this->exchange->basic_publish( - new AMQPMessage( - (string) $data, - array( - 'delivery_mode' => 2, - 'content_type' => 'application/json', - ) - ), + $this->createAmqpMessage($data), $this->exchangeName, - strtolower($routingKey) + $routingKey ); } } + /** + * {@inheritDoc} + */ + public function handleBatch(array $records) + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @param array $record + * @return string + */ + private function getRoutingKey(array $record) + { + $routingKey = sprintf( + '%s.%s', + // TODO 2.0 remove substr call + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + return strtolower($routingKey); + } + + /** + * @param string $data + * @return AMQPMessage + */ + private function createAmqpMessage($data) + { + return new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } + /** * {@inheritDoc} */ diff --git a/src/Monolog/Handler/BrowserConsoleHandler.php b/src/Monolog/Handler/BrowserConsoleHandler.php index 45b54697..86a59a79 100644 --- a/src/Monolog/Handler/BrowserConsoleHandler.php +++ b/src/Monolog/Handler/BrowserConsoleHandler.php @@ -46,9 +46,9 @@ class BrowserConsoleHandler extends AbstractProcessingHandler self::$records[] = $record; // Register shutdown handler if not already done - if (PHP_SAPI !== 'cli' && !self::$initialized) { + if (!self::$initialized) { self::$initialized = true; - register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + $this->registerShutdownFunction(); } } @@ -58,26 +58,16 @@ class BrowserConsoleHandler extends AbstractProcessingHandler */ public static function send() { - $htmlTags = true; - // Check content type - foreach (headers_list() as $header) { - if (stripos($header, 'content-type:') === 0) { - // This handler only works with HTML and javascript outputs - // text/javascript is obsolete in favour of application/javascript, but still used - if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { - $htmlTags = false; - } elseif (stripos($header, 'text/html') === false) { - return; - } - break; - } + $format = self::getResponseFormat(); + if ($format === 'unknown') { + return; } if (count(self::$records)) { - if ($htmlTags) { - echo ''; - } else { - echo self::generateScript(); + if ($format === 'html') { + self::writeOutput(''); + } elseif ($format === 'js') { + self::writeOutput(self::generateScript()); } self::reset(); } @@ -91,6 +81,50 @@ class BrowserConsoleHandler extends AbstractProcessingHandler self::$records = array(); } + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction() + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + } + } + + /** + * Wrapper for echo to allow overriding + * + * @param string $str + */ + protected static function writeOutput($str) + { + echo $str; + } + + /** + * Checks the format of the response + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat() + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } elseif (stripos($header, 'text/html') !== false) { + return 'html'; + } + break; + } + } + + return 'unknown'; + } + private static function generateScript() { $script = array(); diff --git a/src/Monolog/Handler/ChromePHPHandler.php b/src/Monolog/Handler/ChromePHPHandler.php index c0e789f7..a6f30858 100644 --- a/src/Monolog/Handler/ChromePHPHandler.php +++ b/src/Monolog/Handler/ChromePHPHandler.php @@ -17,6 +17,8 @@ use Monolog\Logger; /** * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) * + * This also works out of the box with Firefox 43+ + * * @author Christophe Coevoet */ class ChromePHPHandler extends AbstractProcessingHandler @@ -175,7 +177,8 @@ class ChromePHPHandler extends AbstractProcessingHandler return false; } - return preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']); + // matches any Chrome, or Firefox 43+ + return preg_match('{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}', $_SERVER['HTTP_USER_AGENT']); } /** diff --git a/src/Monolog/Handler/GroupHandler.php b/src/Monolog/Handler/GroupHandler.php index 282bfcf0..193465fd 100644 --- a/src/Monolog/Handler/GroupHandler.php +++ b/src/Monolog/Handler/GroupHandler.php @@ -11,6 +11,8 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; + /** * Forwards records to multiple handlers * @@ -77,4 +79,16 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface $handler->handleBatch($records); } } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + foreach ($this->handlers as $handler) { + $handler->setFormatter($formatter); + } + + return $this; + } } diff --git a/src/Monolog/Handler/HandlerWrapper.php b/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 00000000..8be8e5ea --- /dev/null +++ b/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A filtering handle. Inherit from this class, override isHandling() like this + * + * public function isHandling(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->isHandling($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface +{ + /** + * @var HandlerInterface + */ + private $handler; + + /** + * HandlerWrapper constructor. + * @param HandlerInterface $handler + */ + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + return $this->handler->handle($record); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + return $this->handler->handleBatch($records); + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + $this->handler->pushProcessor($callback); + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + return $this->handler->popProcessor(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } +} \ No newline at end of file diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php index df2f557d..90758274 100644 --- a/src/Monolog/Handler/RavenHandler.php +++ b/src/Monolog/Handler/RavenHandler.php @@ -38,6 +38,12 @@ class RavenHandler extends AbstractProcessingHandler Logger::EMERGENCY => Raven_Client::FATAL, ); + /** + * @var string should represent the current version of the calling + * software. Can be any string (git commit, version number) + */ + private $release; + /** * @var Raven_Client the client object that sends the message to the server */ @@ -139,6 +145,10 @@ class RavenHandler extends AbstractProcessingHandler $options['tags'] = array_merge($options['tags'], $record['context']['tags']); unset($record['context']['tags']); } + if (!empty($record['context']['fingerprint'])) { + $options['fingerprint'] = $record['context']['fingerprint']; + unset($record['context']['fingerprint']); + } if (!empty($record['context']['logger'])) { $options['logger'] = $record['context']['logger']; unset($record['context']['logger']); @@ -165,6 +175,10 @@ class RavenHandler extends AbstractProcessingHandler $options['extra']['extra'] = $record['extra']; } + if (!empty($this->release) && !isset($options['release'])) { + $options['release'] = $this->release; + } + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { $options['extra']['message'] = $record['formatted']; $this->ravenClient->captureException($record['context']['exception'], $options); @@ -204,4 +218,14 @@ class RavenHandler extends AbstractProcessingHandler { return array('checksum', 'release'); } + + /** + * @param string $value + */ + public function setRelease($value) + { + $this->release = $value; + + return $this; + } } diff --git a/src/Monolog/Handler/RotatingFileHandler.php b/src/Monolog/Handler/RotatingFileHandler.php index c6ca2531..22e6c3dc 100644 --- a/src/Monolog/Handler/RotatingFileHandler.php +++ b/src/Monolog/Handler/RotatingFileHandler.php @@ -115,7 +115,11 @@ class RotatingFileHandler extends StreamHandler foreach (array_slice($logFiles, $this->maxFiles) as $file) { if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); unlink($file); + restore_error_handler(); } } diff --git a/src/Monolog/Logger.php b/src/Monolog/Logger.php index 819c83cf..69aa0367 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -157,6 +157,19 @@ class Logger implements LoggerInterface return $this->name; } + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + /** * Pushes a handler on to the stack. * diff --git a/tests/Monolog/Formatter/JsonFormatterTest.php b/tests/Monolog/Formatter/JsonFormatterTest.php index 69e20077..df7e35de 100644 --- a/tests/Monolog/Formatter/JsonFormatterTest.php +++ b/tests/Monolog/Formatter/JsonFormatterTest.php @@ -75,4 +75,48 @@ class JsonFormatterTest extends TestCase }); $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records)); } + + public function testDefFormatWithException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => $exception), + 'datetime' => new \DateTime(), + 'extra' => array(), + 'message' => 'foobar', + )); + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $path = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1); + } else { + $path = substr(json_encode($exception->getFile()), 1, -1); + } + $this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$path.':'.$exception->getLine().'"}},"datetime":'.json_encode(new \DateTime()).',"extra":[],"message":"foobar"}'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?')); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => $exception), + 'datetime' => new \DateTime(), + 'extra' => array(), + 'message' => 'foobar', + )); + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $pathPrevious = substr(json_encode($exception->getPrevious()->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1); + $pathException = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1); + } else { + $pathPrevious = substr(json_encode($exception->getPrevious()->getFile()), 1, -1); + $pathException = substr(json_encode($exception->getFile()), 1, -1); + } + $this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$pathException.':'.$exception->getLine().'","previous":{"class":"LogicException","message":"'.$exception->getPrevious()->getMessage().'","code":'.$exception->getPrevious()->getCode().',"file":"'.$pathPrevious.':'.$exception->getPrevious()->getLine().'"}}},"datetime":'.json_encode(new \DateTime()).',"extra":[],"message":"foobar"}'."\n", $message); + } } diff --git a/tests/Monolog/Formatter/LineFormatterTest.php b/tests/Monolog/Formatter/LineFormatterTest.php index 7116ecac..310d93ca 100644 --- a/tests/Monolog/Formatter/LineFormatterTest.php +++ b/tests/Monolog/Formatter/LineFormatterTest.php @@ -91,6 +91,20 @@ class LineFormatterTest extends \PHPUnit_Framework_TestCase $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message); } + public function testContextAndExtraReplacement() + { + $formatter = new LineFormatter('%context.foo% => %extra.foo%'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 'bar'), + 'datetime' => new \DateTime, + 'extra' => array('foo' => 'xbar'), + 'message' => 'log', + )); + $this->assertEquals('bar => xbar', $message); + } + public function testDefFormatWithObject() { $formatter = new LineFormatter(null, 'Y-m-d'); diff --git a/tests/Monolog/Handler/HandlerWrapperTest.php b/tests/Monolog/Handler/HandlerWrapperTest.php new file mode 100644 index 00000000..d8d0452c --- /dev/null +++ b/tests/Monolog/Handler/HandlerWrapperTest.php @@ -0,0 +1,130 @@ + + * + * 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; + +/** + * @author Alexey Karapetov + */ +class HandlerWrapperTest extends TestCase +{ + /** + * @var HandlerWrapper + */ + private $wrapper; + + private $handler; + + public function setUp() + { + parent::setUp(); + $this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface'); + $this->wrapper = new HandlerWrapper($this->handler); + } + + /** + * @return array + */ + public function trueFalseDataProvider() + { + return array( + array(true), + array(false), + ); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testIsHandling($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('isHandling') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->isHandling($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandle($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('handle') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handle($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandleBatch($result) + { + $records = $this->getMultipleRecords(); + $this->handler->expects($this->once()) + ->method('handleBatch') + ->with($records) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handleBatch($records)); + } + + public function testPushProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('pushProcessor') + ->with($processor); + + $this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor)); + } + + public function testPopProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('popProcessor') + ->willReturn($processor); + + $this->assertEquals($processor, $this->wrapper->popProcessor()); + } + + public function testSetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('setFormatter') + ->with($formatter); + + $this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter)); + } + + public function testGetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + + $this->assertEquals($formatter, $this->wrapper->getFormatter()); + } +} diff --git a/tests/Monolog/Handler/RavenHandlerTest.php b/tests/Monolog/Handler/RavenHandlerTest.php index 8af486f4..a7c4845f 100644 --- a/tests/Monolog/Handler/RavenHandlerTest.php +++ b/tests/Monolog/Handler/RavenHandlerTest.php @@ -99,6 +99,18 @@ class RavenHandlerTest extends TestCase $this->assertEquals($release, $ravenClient->lastData['release']); } + public function testFingerprint() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $fingerprint = array('{{ default }}', 'other value'); + $record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint)); + $handler->handle($record); + + $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']); + } + public function testUserContext() { $ravenClient = $this->getRavenClient(); @@ -192,6 +204,22 @@ class RavenHandlerTest extends TestCase $this->assertSame($formatter, $handler->getBatchFormatter()); } + public function testRelease() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + $release = 'v42.42.42'; + $handler->setRelease($release); + $record = $this->getRecord(Logger::INFO, 'test'); + $handler->handle($record); + $this->assertEquals($release, $ravenClient->lastData['release']); + + $localRelease = 'v41.41.41'; + $record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease)); + $handler->handle($record); + $this->assertEquals($localRelease, $ravenClient->lastData['release']); + } + private function methodThatThrowsAnException() { throw new \Exception('This is an exception'); diff --git a/tests/Monolog/LoggerTest.php b/tests/Monolog/LoggerTest.php index c2f049de..e4d48963 100644 --- a/tests/Monolog/LoggerTest.php +++ b/tests/Monolog/LoggerTest.php @@ -33,6 +33,19 @@ class LoggerTest extends \PHPUnit_Framework_TestCase $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); } + /** + * @covers Monolog\Logger::withName + */ + public function testWithName() + { + $first = new Logger('first', array($handler = new TestHandler())); + $second = $first->withName('second'); + + $this->assertSame('first', $first->getName()); + $this->assertSame('second', $second->getName()); + $this->assertSame($handler, $second->popHandler()); + } + /** * @covers Monolog\Logger::toMonologLevel */