diff --git a/src/Monolog/Formatter/GelfMessageFormatter.php b/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 00000000..9ad65319 --- /dev/null +++ b/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,102 @@ + + * + * 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 Gelf\Message; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Matt Lehner + */ +class GelfMessageFormatter implements FormatterInterface +{ + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contentPrefix; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contentPrefix = 'ctxt_') + { + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contentPrefix = $contentPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $message = new Message(); + $message + ->setTimestamp($record['datetime']->format('U.u')) + ->setShortMessage((string) $record['message']) + ->setFacility($record['channel']) + ->setHost($this->systemName) + ->setLine(isset($record['extra']['line']) ? $record['extra']['line'] : null) + ->setFile(isset($record['extra']['file']) ? $record['extra']['file'] : null) + ->setLevel($this->logLevels[ $record['level'] ]); + + // Do not duplicate these values in the additional fields + unset($record['extra']['line']); + unset($record['extra']['file']); + + foreach ($record['extra'] as $key => $val) { + $message->setAdditional($this->extraPrefix . $key, is_scalar($val) ? $val : json_encode($val)); + } + + foreach ($record['context'] as $key => $val) { + $message->setAdditional($this->contentPrefix . $key, is_scalar($val) ? $val : json_encode($val)); + } + + return $message; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + $messages[] = $this->format($record); + } + + return $messages; + } +} diff --git a/src/Monolog/Handler/GelfHandler.php b/src/Monolog/Handler/GelfHandler.php index ad4aaabe..2eec8fc1 100644 --- a/src/Monolog/Handler/GelfHandler.php +++ b/src/Monolog/Handler/GelfHandler.php @@ -11,10 +11,10 @@ namespace Monolog\Handler; -use Gelf\Message; -use Gelf\MessagePublisher; +use Gelf\IMessagePublisher; use Monolog\Logger; use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Formatter\GelfMessageFormatter; /** * Handler to send messages to a Graylog2 (http://www.graylog2.org) server @@ -24,55 +24,28 @@ use Monolog\Handler\AbstractProcessingHandler; class GelfHandler extends AbstractProcessingHandler { /** - * @var Gelf\MessagePublisher the publisher object that sends the message to the server + * @var Gelf\IMessagePublisher the publisher object that sends the message to the server */ protected $publisher; /** - * @var string the name of the system for the Gelf log message - */ - protected $systemName; - - /** - * @var string a prefix for 'extra' fields from the Monolog record (optional) - */ - protected $extraPrefix; - - /** - * @var string a prefix for 'context' fields from the Monolog record (optional) - */ - protected $contentPrefix; - - /** - * Translates Monolog log levels to Graylog2 log priorities. - */ - private $logLevels = array( - Logger::DEBUG => LOG_DEBUG, - Logger::INFO => LOG_INFO, - Logger::WARNING => LOG_WARNING, - Logger::ERROR => LOG_ERR, - Logger::CRITICAL => LOG_CRIT, - Logger::ALERT => LOG_ALERT, - ); - - /** - * @param Gelf\MessagePublisher $publisher a publisher object - * @param string $systemName the name of the system sending messages + * @param Gelf\IMessagePublisher $publisher a publisher object * @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 - * @param string $extraPrefix a string to prefix for all the 'extra' fields from Monolog record - * @oaram string $contentPrefix a string to prefix for all the 'context' fields from a Monolog record */ - public function __construct(MessagePublisher $publisher, $systemName = null, $level = Logger::DEBUG, - $bubble = true, $extraPrefix = null, $contentPrefix = 'ctxt_') + public function __construct(IMessagePublisher $publisher, $level = Logger::DEBUG, $bubble = true) { parent::__construct($level, $bubble); $this->publisher = $publisher; - $this->systemName = $systemName ?: gethostname(); + } - $this->extraPrefix = $extraPrefix; - $this->contentPrefix = $contentPrefix; + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); } /** @@ -88,29 +61,6 @@ class GelfHandler extends AbstractProcessingHandler */ public function write(array $record) { - $message = new Message(); - $message - ->setTimestamp($record['datetime']->format('U.u')) - ->setShortMessage((string) $record['message']) - ->setFullMessage((string) $record['formatted']) - ->setFacility($record['channel']) - ->setHost($this->systemName) - ->setLine(isset($record['extra']['line']) ? $record['extra']['line'] : null) - ->setFile(isset($record['extra']['file']) ? $record['extra']['file'] : null) - ->setLevel($this->logLevels[ $record['level'] ]); - - // Do not duplicate these values in the additional fields - unset($record['extra']['line']); - unset($record['extra']['file']); - - foreach ($record['extra'] as $key => $val) { - $message->setAdditional($this->extraPrefix . $key, is_scalar($val) ? $val : json_encode($val)); - } - - foreach ($record['context'] as $key => $val) { - $message->setAdditional($this->contentPrefix . $key, is_scalar($val) ? $val : json_encode($val)); - } - - $this->publisher->publish($message); + $this->publisher->publish($record['formatted']); } } diff --git a/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 00000000..d03fc5c1 --- /dev/null +++ b/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,150 @@ + + * + * 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; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals(LOG_ERR, $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } +} diff --git a/tests/Monolog/Handler/GelfHandlerTest.php b/tests/Monolog/Handler/GelfHandlerTest.php index 81a1ca15..3e9420a3 100644 --- a/tests/Monolog/Handler/GelfHandlerTest.php +++ b/tests/Monolog/Handler/GelfHandlerTest.php @@ -47,7 +47,6 @@ class GelfHandlerTest extends TestCase protected function getHandler($messagePublisher) { $handler = new GelfHandler($messagePublisher); - $handler->setFormatter($this->getIdentityFormatter()); return $handler; } @@ -67,7 +66,7 @@ class GelfHandlerTest extends TestCase $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); - $this->assertEquals($record['message'], $messagePublisher->lastMessage->getFullMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); } public function testWarning() @@ -81,6 +80,6 @@ class GelfHandlerTest extends TestCase $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); - $this->assertEquals($record['message'], $messagePublisher->lastMessage->getFullMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); } }