diff --git a/composer.json b/composer.json index 49ed73e8..62297d3c 100644 --- a/composer.json +++ b/composer.json @@ -22,13 +22,15 @@ "raven/raven": "~0.5", "ruflin/elastica": "0.90.*", "doctrine/couchdb": "~1.0@dev", - "aws/aws-sdk-php": "~2.4, >2.4.8" + "aws/aws-sdk-php": "~2.4, >2.4.8", + "videlalvaro/php-amqplib": "~2.4" }, "suggest": { "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "raven/raven": "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", + "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", "ext-mongo": "Allow sending log messages to a MongoDB server", "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", diff --git a/src/Monolog/Handler/PhpAmqpLibHandler.php b/src/Monolog/Handler/PhpAmqpLibHandler.php new file mode 100644 index 00000000..c2a1d06e --- /dev/null +++ b/src/Monolog/Handler/PhpAmqpLibHandler.php @@ -0,0 +1,84 @@ + + * + * 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\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; + +/** + * Handler to send messages to AMQP using php-amqplib + * + * @author Giorgio Premi + * @author Jordi Boggiano + */ +class PhpAmqpLibHandler extends AbstractProcessingHandler +{ + /** + * @var \PhpAmqpLib\Channel\AMQPChannel $channel + */ + protected $channel; + + /** + * @var string $exchangeName + */ + protected $exchangeName; + + /** + * @param AMQPChannel $channel AMQP channel, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(AMQPChannel $channel, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + $this->channel = $channel; + $this->exchangeName = $exchangeName; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + + $routingKey = sprintf( + '%s.%s', + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + $this->channel->basic_publish( + new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json' + ) + ), + $this->exchangeName, + strtolower($routingKey) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/tests/Monolog/Handler/PhpAmqpLibHandlerTest.php b/tests/Monolog/Handler/PhpAmqpLibHandlerTest.php new file mode 100644 index 00000000..b5996edd --- /dev/null +++ b/tests/Monolog/Handler/PhpAmqpLibHandlerTest.php @@ -0,0 +1,82 @@ + + * + * 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 PhpAmqpLib\Message\AMQPMessage; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class PhpAmqpLibHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) { + $this->markTestSkipped("php-amqplib not installed"); + } + } + + public function testHandle() + { + $messages = array(); + + $exchange = $this->getMock('PhpAmqpLib\Channel\AMQPChannel', array('basic_publish', '__destruct'), array(), '', false); + + $exchange->expects($this->any()) + ->method('basic_publish') + ->will($this->returnCallback(function (AMQPMessage $msg, $exchange = "", $routing_key = "", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) { + $messages[] = array($msg, $exchange, $routing_key, $mandatory, $immediate, $ticket); + })) + ; + + $handler = new PhpAmqpLibHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'log', + 'warn.test', + false, + false, + null, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json' + ) + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + + /* @var $msg AMQPMessage */ + $msg = $messages[0][0]; + $messages[0][0] = json_decode($msg->body, true); + $messages[0][] = $msg->get_properties(); + unset($messages[0][0]['datetime']); + + $this->assertEquals($expected, $messages[0]); + } +}