diff --git a/composer.json b/composer.json index 426628c0..f3451742 100644 --- a/composer.json +++ b/composer.json @@ -29,12 +29,10 @@ "predis/predis": "^1.1", "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^1.9", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "sentry/sentry": "Allow sending log messages to a Sentry server", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", "ruflin/elastica": "Allow sending log messages to an Elastic Search server", "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", diff --git a/doc/02-handlers-formatters-processors.md b/doc/02-handlers-formatters-processors.md index 2baf9731..1169473b 100644 --- a/doc/02-handlers-formatters-processors.md +++ b/doc/02-handlers-formatters-processors.md @@ -45,13 +45,11 @@ - [_SocketHandler_](../src/Monolog/Handler/SocketHandler.php): Logs records to [sockets](http://php.net/fsockopen), use this for UNIX and TCP sockets. See an [example](sockets.md). - [_AmqpHandler_](../src/Monolog/Handler/AmqpHandler.php): Logs records to an [AMQP](http://www.amqp.org/) compatible - server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+) or + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+) or [php-amqplib](https://github.com/php-amqplib/php-amqplib) library. - [_GelfHandler_](../src/Monolog/Handler/GelfHandler.php): Logs records to a [Graylog2](http://www.graylog2.org) server. Requires package [graylog2/gelf-php](https://github.com/bzikarsky/gelf-php). - [_CubeHandler_](../src/Monolog/Handler/CubeHandler.php): Logs records to a [Cube](http://square.github.com/cube/) server. -- [_RavenHandler_](../src/Monolog/Handler/RavenHandler.php): Logs records to a [Sentry](http://getsentry.com/) server using - [raven](https://packagist.org/packages/raven/raven). - [_ZendMonitorHandler_](../src/Monolog/Handler/ZendMonitorHandler.php): Logs records to the Zend Monitor present in Zend Server. - [_NewRelicHandler_](../src/Monolog/Handler/NewRelicHandler.php): Logs records to a [NewRelic](http://newrelic.com/) application. - [_LogglyHandler_](../src/Monolog/Handler/LogglyHandler.php): Logs records to a [Loggly](http://www.loggly.com/) account. @@ -61,6 +59,8 @@ - [_InsightOpsHandler_](../src/Monolog/Handler/InsightOpsHandler.php): Logs records to an [InsightOps](https://www.rapid7.com/products/insightops/) account. - [_LogmaticHandler_](../src/Monolog/Handler/LogmaticHandler.php): Logs records to a [Logmatic](http://logmatic.io/) account. - [_SqsHandler_](../src/Monolog/Handler/SqsHandler.php): Logs records to an [AWS SQS](http://docs.aws.amazon.com/aws-sdk-php/v2/guide/service-sqs.html) queue. +- [_RavenHandler_](../src/Monolog/Handler/RavenHandler.php): Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). **Deprecated** and removed in Monolog 2.0, use sentry/sentry 2.x and the [Sentry\Monolog\Handler](https://github.com/getsentry/sentry-php/blob/master/src/Monolog/Handler.php) class instead. ### Logging in development @@ -128,9 +128,9 @@ - [_HandlerWrapper_](../src/Monolog/Handler/HandlerWrapper.php): A simple handler wrapper you can inherit from to create your own wrappers easily. - [_OverflowHandler_](../src/Monolog/Handler/OverflowHandler.php): This handler will buffer all the log messages it - receives, up until a configured threshold of number of messages of a certain lever is reached, after it will pass all - log messages to the wrapped handler. Useful for applying in batch processing when you're only interested in significant - failures instead of minor, single erroneous events. + receives, up until a configured threshold of number of messages of a certain lever is reached, after it will pass all + log messages to the wrapped handler. Useful for applying in batch processing when you're only interested in significant + failures instead of minor, single erroneous events. ## Formatters diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php deleted file mode 100644 index d4c261ca..00000000 --- a/src/Monolog/Handler/RavenHandler.php +++ /dev/null @@ -1,270 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Logger; -use Raven_Client; - -/** - * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server - * using sentry-php (https://github.com/getsentry/sentry-php) - * - * @author Marc Abramowitz - */ -class RavenHandler extends AbstractProcessingHandler -{ - /** - * Translates Monolog log levels to Raven log levels. - */ - protected $logLevels = [ - Logger::DEBUG => Raven_Client::DEBUG, - Logger::INFO => Raven_Client::INFO, - Logger::NOTICE => Raven_Client::INFO, - Logger::WARNING => Raven_Client::WARNING, - Logger::ERROR => Raven_Client::ERROR, - Logger::CRITICAL => Raven_Client::FATAL, - Logger::ALERT => Raven_Client::FATAL, - Logger::EMERGENCY => Raven_Client::FATAL, - ]; - - /** - * @var string the current application environment (staging|preprod|prod) - */ - protected $environment; - - /** - * @var string should represent the current version of the calling - * software. Can be any string (git commit, version number) - */ - protected $release; - - /** - * @var Raven_Client the client object that sends the message to the server - */ - protected $ravenClient; - - /** - * @var FormatterInterface The formatter to use for the logs generated via handleBatch() - */ - protected $batchFormatter; - - /** - * @param Raven_Client $ravenClient - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, bool $bubble = true) - { - @trigger_error('The Monolog\Handler\RavenHandler class is deprecated. You should rather upgrade to the sentry/sentry 2.x and use Sentry\Monolog\Handler, see https://github.com/getsentry/sentry-php/blob/master/src/Monolog/Handler.php', E_USER_DEPRECATED); - - parent::__construct($level, $bubble); - - $this->ravenClient = $ravenClient; - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records): void - { - $level = $this->level; - - // filter records based on their level - $records = array_filter($records, function ($record) use ($level) { - return $record['level'] >= $level; - }); - - if (!$records) { - return; - } - - // the record with the highest severity is the "main" one - $record = array_reduce($records, function ($highest, $record) { - if ($record['level'] > $highest['level']) { - return $record; - } - - return $highest; - }); - - // the other ones are added as a context item - $logs = []; - foreach ($records as $r) { - $logs[] = $this->processRecord($r); - } - - if ($logs) { - $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); - } - - $this->handle($record); - } - - /** - * Sets the formatter for the logs generated by handleBatch(). - * - * @param FormatterInterface $formatter - */ - public function setBatchFormatter(FormatterInterface $formatter): self - { - $this->batchFormatter = $formatter; - - return $this; - } - - /** - * Gets the formatter for the logs generated by handleBatch(). - */ - public function getBatchFormatter(): FormatterInterface - { - if (!$this->batchFormatter) { - $this->batchFormatter = $this->getDefaultBatchFormatter(); - } - - return $this->batchFormatter; - } - - /** - * {@inheritdoc} - * @suppress PhanTypeMismatchArgument - */ - protected function write(array $record): void - { - /** @var bool|null|array This is false, unless set below to null or an array of data, when we read the current user context */ - $previousUserContext = false; - $options = []; - $options['level'] = $this->logLevels[$record['level']]; - $options['tags'] = []; - if (!empty($record['extra']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); - unset($record['extra']['tags']); - } - if (!empty($record['context']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['context']['tags']); - unset($record['context']['tags']); - } - if (!empty($record['context']['fingerprint'])) { - $options['fingerprint'] = $record['context']['fingerprint']; - unset($record['context']['fingerprint']); - } - if (!empty($record['context']['logger'])) { - $options['logger'] = $record['context']['logger']; - unset($record['context']['logger']); - } else { - $options['logger'] = $record['channel']; - } - foreach ($this->getExtraParameters() as $key) { - foreach (['extra', 'context'] as $source) { - if (!empty($record[$source][$key])) { - $options[$key] = $record[$source][$key]; - unset($record[$source][$key]); - } - } - } - if (!empty($record['context'])) { - $options['extra']['context'] = $record['context']; - if (!empty($record['context']['user'])) { - $previousUserContext = $this->ravenClient->context->user; - $this->ravenClient->user_context($record['context']['user']); - unset($options['extra']['context']['user']); - } - } - if (!empty($record['extra'])) { - $options['extra']['extra'] = $record['extra']; - } - - if (!empty($this->environment) && !isset($options['environment'])) { - $options['environment'] = $this->environment; - } - - if (!empty($this->release) && !isset($options['release'])) { - $options['release'] = $this->release; - } - - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { - $options['message'] = $record['formatted']; - $this->ravenClient->captureException($record['context']['exception'], $options); - } else { - $this->ravenClient->captureMessage($record['formatted'], [], $options); - } - - // restore the user context if it was modified - if (!is_bool($previousUserContext)) { - $this->ravenClient->user_context($previousUserContext, false); - } - } - - /** - * {@inheritDoc} - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter('[%channel%] %message%'); - } - - /** - * Gets the default formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - protected function getDefaultBatchFormatter(): FormatterInterface - { - return new LineFormatter(); - } - - /** - * Gets extra parameters supported by Raven that can be found in "extra" and "context" - * - * @return array - */ - protected function getExtraParameters(): array - { - return ['contexts', 'checksum', 'release', 'environment', 'event_id']; - } - - /** - * @param string $value - * @return self - */ - public function setRelease($value): self - { - $this->release = $value; - - return $this; - } - - public function setEnvironment($value): self - { - $this->environment = $value; - - return $this; - } - - /** - * @link https://docs.sentry.io/learn/breadcrumbs/ - */ - public function addBreadcrumb(array $crumb): self - { - $this->ravenClient->breadcrumbs->record($crumb); - - return $this; - } - - public function resetBreadcrumbs(): self - { - $this->ravenClient->breadcrumbs->reset(); - - return $this; - } -} diff --git a/tests/Monolog/Handler/MockRavenClient-gte-0-16-0.php b/tests/Monolog/Handler/MockRavenClient-gte-0-16-0.php deleted file mode 100644 index 07434e49..00000000 --- a/tests/Monolog/Handler/MockRavenClient-gte-0-16-0.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Raven_Client; - -class MockRavenClient extends Raven_Client -{ - public function capture($data, $stack = null, $vars = null) - { - $data = array_merge($this->get_user_data(), $data); - $this->lastData = $data; - $this->lastStack = $stack; - } - - public $lastData; - public $lastStack; -} diff --git a/tests/Monolog/Handler/MockRavenClient.php b/tests/Monolog/Handler/MockRavenClient.php deleted file mode 100644 index 07434e49..00000000 --- a/tests/Monolog/Handler/MockRavenClient.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Raven_Client; - -class MockRavenClient extends Raven_Client -{ - public function capture($data, $stack = null, $vars = null) - { - $data = array_merge($this->get_user_data(), $data); - $this->lastData = $data; - $this->lastStack = $stack; - } - - public $lastData; - public $lastStack; -} diff --git a/tests/Monolog/Handler/RavenHandlerTest.php b/tests/Monolog/Handler/RavenHandlerTest.php deleted file mode 100644 index 491d84ac..00000000 --- a/tests/Monolog/Handler/RavenHandlerTest.php +++ /dev/null @@ -1,307 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Test\TestCase; -use Monolog\Logger; -use Monolog\Formatter\LineFormatter; -use Raven_Client; - -class RavenHandlerTest extends TestCase -{ - public function setUp() - { - if (!class_exists('Raven_Client')) { - $this->markTestSkipped('sentry/sentry not installed'); - } - - if (version_compare(Raven_Client::VERSION, '0.16.0', '>=')) { - require_once __DIR__ . '/MockRavenClient-gte-0-16-0.php'; - } else { - require_once __DIR__ . '/MockRavenClient.php'; - } - } - - /** - * @covers Monolog\Handler\RavenHandler::__construct - */ - public function testConstruct() - { - $handler = new RavenHandler($this->getRavenClient()); - $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); - } - - protected function getHandler($ravenClient) - { - return new RavenHandler($ravenClient); - } - - protected function getRavenClient() - { - $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; - - return new MockRavenClient($dsn); - } - - public function testDebug() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $record = $this->getRecord(Logger::DEBUG, 'A test debug message'); - $handler->handle($record); - - $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); - $this->assertContains($record['message'], $ravenClient->lastData['message']); - } - - public function testWarning() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $record = $this->getRecord(Logger::WARNING, 'A test warning message'); - $handler->handle($record); - - $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); - $this->assertContains($record['message'], $ravenClient->lastData['message']); - } - - public function testTag() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $tags = [1, 2, 'foo']; - $record = $this->getRecord(Logger::INFO, 'test', ['tags' => $tags]); - $handler->handle($record); - - $this->assertEquals($tags, $ravenClient->lastData['tags']); - } - - public function testExtraParameters() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $checksum = '098f6bcd4621d373cade4e832627b4f6'; - $release = '05a671c66aefea124cc08b76ea6d30bb'; - $eventId = '31423'; - $record = $this->getRecord(Logger::INFO, 'test', ['checksum' => $checksum, 'release' => $release, 'event_id' => $eventId]); - $handler->handle($record); - - $this->assertEquals($checksum, $ravenClient->lastData['checksum']); - $this->assertEquals($release, $ravenClient->lastData['release']); - $this->assertEquals($eventId, $ravenClient->lastData['event_id']); - } - - public function testFingerprint() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $fingerprint = ['{{ default }}', 'other value']; - $record = $this->getRecord(Logger::INFO, 'test', ['fingerprint' => $fingerprint]); - $handler->handle($record); - - $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']); - } - - public function testUserContext() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $recordWithNoContext = $this->getRecord(Logger::INFO, 'test with default user context'); - // set user context 'externally' - - $user = [ - 'id' => '123', - 'email' => 'test@test.com', - ]; - - $recordWithContext = $this->getRecord(Logger::INFO, 'test', ['user' => $user]); - - $ravenClient->user_context(['id' => 'test_user_id']); - // handle context - $handler->handle($recordWithContext); - $this->assertEquals($user, $ravenClient->lastData['user']); - - // check to see if its reset - $handler->handle($recordWithNoContext); - $this->assertIsArray($ravenClient->context->user); - $this->assertSame('test_user_id', $ravenClient->context->user['id']); - - // handle with null context - $ravenClient->user_context(null, false); - $handler->handle($recordWithContext); - $this->assertEquals($user, $ravenClient->lastData['user']); - - // check to see if its reset - $handler->handle($recordWithNoContext); - $this->assertNull($ravenClient->context->user); - } - - public function testException() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - try { - $this->methodThatThrowsAnException(); - } catch (\Exception $e) { - $record = $this->getRecord(Logger::ERROR, $e->getMessage(), ['exception' => $e]); - $handler->handle($record); - } - - $this->assertEquals('[test] ' . $record['message'], $ravenClient->lastData['message']); - } - - public function testHandleBatch() - { - $records = $this->getMultipleRecords(); - $records[] = $this->getRecord(Logger::WARNING, 'warning'); - $records[] = $this->getRecord(Logger::WARNING, 'warning'); - - $logFormatter = $this->createMock('Monolog\\Formatter\\FormatterInterface'); - $logFormatter->expects($this->once())->method('formatBatch'); - - $formatter = $this->createMock('Monolog\\Formatter\\FormatterInterface'); - $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) { - return $record['level'] == 400; - })); - - $handler = $this->getHandler($this->getRavenClient()); - $handler->setBatchFormatter($logFormatter); - $handler->setFormatter($formatter); - $handler->handleBatch($records); - } - - public function testHandleBatchDoNothingIfRecordsAreBelowLevel() - { - $records = [ - $this->getRecord(Logger::DEBUG, 'debug message 1'), - $this->getRecord(Logger::DEBUG, 'debug message 2'), - $this->getRecord(Logger::INFO, 'information'), - ]; - - $handler = $this->getMockBuilder('Monolog\Handler\RavenHandler') - ->setMethods(['handle']) - ->setConstructorArgs([$this->getRavenClient()]) - ->getMock(); - $handler->expects($this->never())->method('handle'); - $handler->setLevel(Logger::ERROR); - $handler->handleBatch($records); - } - - public function testHandleBatchPicksProperMessage() - { - $records = array( - $this->getRecord(Logger::DEBUG, 'debug message 1'), - $this->getRecord(Logger::DEBUG, 'debug message 2'), - $this->getRecord(Logger::INFO, 'information 1'), - $this->getRecord(Logger::ERROR, 'error 1'), - $this->getRecord(Logger::WARNING, 'warning'), - $this->getRecord(Logger::ERROR, 'error 2'), - $this->getRecord(Logger::INFO, 'information 2'), - ); - - $logFormatter = $this->createMock('Monolog\\Formatter\\FormatterInterface'); - $logFormatter->expects($this->once())->method('formatBatch'); - - $formatter = $this->createMock('Monolog\\Formatter\\FormatterInterface'); - $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) use ($records) { - return $record['message'] == 'error 1'; - })); - - $handler = $this->getHandler($this->getRavenClient()); - $handler->setBatchFormatter($logFormatter); - $handler->setFormatter($formatter); - $handler->handleBatch($records); - } - - public function testGetSetBatchFormatter() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $handler->setBatchFormatter($formatter = new LineFormatter()); - $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', ['release' => $localRelease]); - $handler->handle($record); - $this->assertEquals($localRelease, $ravenClient->lastData['release']); - } - - public function testEnvironment() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - $handler->setEnvironment('preprod'); - - $handler->handle($this->getRecord(Logger::INFO, 'Hello 👋 from PREPROD env')); - $this->assertEquals('preprod', $ravenClient->lastData['environment']); - - $handler->handle($this->getRecord(Logger::INFO, 'Hello 👋 from STAGING env', ['environment' => 'staging'])); - $this->assertEquals('staging', $ravenClient->lastData['environment']); - } - - public function testBreadcrumbs() - { - $ravenClient = $this->getRavenClient(); - $handler = $this->getHandler($ravenClient); - - $handler->addBreadcrumb($crumb1 = [ - 'category' => 'test', - 'level' => 'info', - 'message' => 'Step 1: user auth', - ]); - - $handler->addBreadcrumb($crumb2 = [ - 'category' => 'test', - 'level' => 'info', - 'message' => 'Step 2: prepare user redirect', - ]); - - $handler->handle($this->getRecord(Logger::ERROR, 'ERROR 💥')); - $breadcrumbs = $ravenClient->breadcrumbs->fetch(); - $this->assertCount(2, $breadcrumbs); - $this->assertSame('test', $breadcrumbs[0]['category']); - $this->assertSame('info', $breadcrumbs[0]['level']); - $this->assertSame('Step 1: user auth', $breadcrumbs[0]['message']); - - $this->assertSame('test', $breadcrumbs[1]['category']); - $this->assertSame('info', $breadcrumbs[1]['level']); - $this->assertSame('Step 2: prepare user redirect', $breadcrumbs[1]['message']); - - $handler->resetBreadcrumbs(); - $handler->handle($this->getRecord(Logger::INFO, 'Hello!')); - $this->assertEmpty($ravenClient->breadcrumbs->fetch()); - } - - private function methodThatThrowsAnException() - { - throw new \Exception('This is an exception'); - } -}