-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Raven, the protocol used by Sentry (http://github.com/dcramer/sentry) #76
Changes from 13 commits
acbf574
8aeb75a
ac161a0
0e579f1
f158009
6d15811
f6e75e4
3238a74
1c59696
7a81acd
6b4b2a6
44d2441
4830b92
5d17212
368b1f0
c2850f1
10fcd61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Monolog package. | ||
* | ||
* (c) Jordi Boggiano <j.boggiano@seld.be> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Monolog\Formatter; | ||
|
||
use Monolog\Logger; | ||
use Raven_Client; | ||
|
||
/** | ||
* Serializes a log message for Raven (https://github.com/getsentry/raven-php) | ||
* | ||
* @author Marc Abramowitz <marc@marc-abramowitz.com> | ||
*/ | ||
class RavenFormatter extends NormalizerFormatter | ||
{ | ||
/** | ||
* Translates Monolog log levels to Raven log levels. | ||
*/ | ||
private $logLevels = array( | ||
Logger::DEBUG => Raven_Client::DEBUG, | ||
Logger::INFO => Raven_Client::INFO, | ||
Logger::WARNING => Raven_Client::WARNING, | ||
Logger::ERROR => Raven_Client::ERROR, | ||
Logger::CRITICAL => Raven_Client::ERROR, | ||
Logger::ALERT => Raven_Client::ERROR, | ||
); | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function format(array $record) | ||
{ | ||
$record = parent::format($record); | ||
|
||
$record['level'] = $this->logLevels[$record['level']]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of changing the level here, I would move the level map to the handler to be consistent with other handlers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and the formatter should return only the formatted message instead of returning a full record with a different message. |
||
$record['message'] = $record['channel'] . ': ' . $record['message']; | ||
|
||
return $record; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Monolog package. | ||
* | ||
* (c) Jordi Boggiano <j.boggiano@seld.be> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Monolog\Handler; | ||
|
||
use Monolog\Logger; | ||
use Monolog\Handler\AbstractProcessingHandler; | ||
use Monolog\Formatter\RavenFormatter; | ||
use \Raven_Client; | ||
|
||
/** | ||
* Handler to send messages to a Sentry (https://github.com/dcramer/sentry) server | ||
* using raven-php (https://github.com/getsentry/raven-php) | ||
* | ||
* @author Marc Abramowitz <marc@marc-abramowitz.com> | ||
*/ | ||
class RavenHandler extends AbstractProcessingHandler | ||
{ | ||
/** | ||
* @var Raven_Client the client object that sends the message to the server | ||
*/ | ||
protected $ravenClient; | ||
|
||
/** | ||
* @param Raven_Client $ravenClient | ||
* @param integer $level The minimum logging level at which this handler will be triggered | ||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not | ||
*/ | ||
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) | ||
{ | ||
parent::__construct($level, $bubble); | ||
|
||
$this->ravenClient = $ravenClient; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function close() | ||
{ | ||
$this->ravenClient = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing the dependency seems weird to me There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which dependency? I meant to remove Let me know what you meant and I'll try to turn it around quick. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are removing the dependency of the handler (the raven client) on close, which seems weird to me. This means that the handler is in a totally broken state once close (calling a method on it would trigger a fatal error). And removing the reference is not needed. PHP is able to garbage collect objects. |
||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function write(array $record) | ||
{ | ||
if ($record['level'] >= Logger::ERROR && isset($record['context']['exception'])) { | ||
$this->ravenClient->captureException($record['context']['exception']); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While having a convention about the way to provide exception in the context to allow a special handling is fine, I'm concerned with this implementation: it will log only the exception and drop the log message (which could be better to understand the issue as the dev could have explained why he catched the exception in this case and logged it). |
||
} else { | ||
$this->ravenClient->captureMessage( | ||
$record['formatted']['message'], | ||
$record, // $params | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will duplicate many things (as Thus, looking at the raven client, it seems wrong. the params array is meant to contain params to fill placeholders of the message through sprintf. This is not what the record is about. With your current implementation, the message sent to Raven will not contain any data of the context or extra array (and it will store its own timestamp instead of the timestamp provided by Monolog) |
||
$record['formatted']['level'], // $level | ||
true // $stack | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think forcing to use the stacktrace is a good idea. It should be configurable in the constructor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that for messages it's probably not needed in most cases, for exceptions though it's great. Maybe just capture if level is above ERROR? |
||
); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this capture exceptions twice? Once as a regular message and once as an exception. |
||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
protected function getDefaultFormatter() | ||
{ | ||
return new RavenFormatter(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?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 Raven_Client; | ||
|
||
class MockRavenClient extends Raven_Client | ||
{ | ||
public function capture($data, $stack) | ||
{ | ||
$this->lastData = $data; | ||
$this->lastStack = $stack; | ||
} | ||
|
||
public $lastData; | ||
public $lastStack; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?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; | ||
use Monolog\Handler\RavenHandler; | ||
|
||
class RavenHandlerTest extends TestCase | ||
{ | ||
public function setUp() | ||
{ | ||
if (!class_exists("Raven_Client")) { | ||
$this->markTestSkipped("raven/raven not installed"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an issue here: this will never be reached as a fatal error will already be thrown by the mock class definition. You would need to move MockRavenClient to a separate file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this seems to be a problem. Note that I copied this idiom from https://github.com/Seldaek/monolog/blob/master/tests/Monolog/Handler/GelfHandlerTest.php so it probably has the same problem. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I have a fix for this in 44d2441: Here's how I tested with and without raven-php available...
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like
|
||
} | ||
|
||
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) | ||
{ | ||
$handler = new RavenHandler($ravenClient); | ||
return $handler; | ||
} | ||
|
||
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 testException() | ||
{ | ||
$ravenClient = $this->getRavenClient(); | ||
$handler = $this->getHandler($ravenClient); | ||
|
||
try { | ||
$this->methodThatThrowsAnException(); | ||
} catch (\Exception $e) { | ||
$record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); | ||
$handler->handle($record); | ||
} | ||
|
||
$this->assertEquals($record['message'], $ravenClient->lastData['message']); | ||
} | ||
|
||
private function methodThatThrowsAnException() | ||
{ | ||
throw new \Exception('This is an exception'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should map CRITICAL and ALERT to ERROR as well if raven doesn't have more than ERROR, because otherwise those messages will fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See 0e579f1