From 12ab07447d6a5e6619ab4740e39589e85cc58f1c Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Apr 2011 15:33:39 -0700 Subject: [PATCH] Refactored FirePHPHandler a bit to support Closures & Methods to override how headers are sent. (Defaults to PHP's `header` function) --- src/Monolog/Handler/FirePHPHandler.php | 175 ++++++++++++++----- tests/Monolog/Handler/FirePHPHandlerTest.php | 75 ++++++-- 2 files changed, 198 insertions(+), 52 deletions(-) diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php index 774bd084..a31a0a6c 100644 --- a/src/Monolog/Handler/FirePHPHandler.php +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -22,16 +22,60 @@ use Monolog\Formatter\WildfireFormatter; class FirePHPHandler extends AbstractHandler { + /** + * WildFire JSON header message format + */ const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + /** + * FirePHP structure for parsing messages & their presentation + */ const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/ZendFramework/FirePHP/1.6.2'; + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + private $initialized = false; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ private $prefix = 'X-Wf'; - private $records = array(); + /** + * Shared static message index between potentially multiple handlers + */ + private static $messageIndex = 1; + /** + * Function, Method or Closure for sending the header + */ + private $writer = 'header'; + + /** + * @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 mixed $writer Function, Method or Closure to use for sending headers + */ + public function __construct($level = Logger::DEBUG, $bubble = false, $writer = 'header') + { + $this->level = $level; + $this->bubble = $bubble; + $this->writer = $writer; + } + + /** + * Base header creation function used by init headers & record headers + * + * @var Array $meta Wildfire Plugin, Protocol & Structure Indexes + * @var String $message Log message + * @return String Complete header string ready for the client + */ protected function createHeader(Array $meta, $message) { return sprintf( @@ -42,49 +86,20 @@ class FirePHPHandler extends AbstractHandler ); } - protected function write(Array $record) - { - $this->records[] = $record; - } - - public function close() - { - if (headers_sent()) { - return false; - } else { - foreach ($this->getHeaders() as $header) { - header($header); - } - - return true; - } - } - - public function getHeaders() + /** + * Creates message header from record + * + * @see createHeader() + * @var Array $record + */ + protected function createRecordHeader(Array $record) { // Wildfire is extensible to support multiple protocols & plugins in a single request, - // but we're not taking advantage of that (yet) for simplicity's sake. - // (It does help understanding header formatting, though!) - $protocolIndex = 1; - $structureIndex = 1; - $pluginIndex = 1; - $messageIndex = 1; - - // Initial payload consists of required headers for Wildfire - $headers = array( - $this->createHeader(array('Protocol', $protocolIndex), self::PROTOCOL_URI), - $this->createHeader(array($protocolIndex, 'Structure', $structureIndex), self::STRUCTURE_URI), - $this->createHeader(array($protocolIndex, 'Plugin', $pluginIndex), self::PLUGIN_URI), + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['message'] ); - - foreach ($this->records as $record) { - $headers[] = $this->createHeader( - array($protocolIndex, $structureIndex, $pluginIndex, $messageIndex++), - $record['message'] - ); - } - - return $headers; } protected function getDefaultFormatter() @@ -92,4 +107,82 @@ class FirePHPHandler extends AbstractHandler return new WildfireFormatter(); } + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return Array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI), + ); + } + + /** + * Send header string to the client + * + * @var String $header + * @return Boolean False if headers are already sent, true if header are sent successfully + */ + protected function sendHeader($header) + { + if (headers_sent()) { + return false; + } else { + $writer = $this->getWriter(); + + if ($writer instanceof \Closure) { + $writer($header); + } else { + call_user_func($writer, $header); + } + + return true; + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @var Array $record + */ + protected function write(Array $record) + { + // WildFire-specific headers must be sent prior to any messages + if (! $this->initialized) { + foreach ($this->getInitHeaders() as $header) { + $this->sendHeader($header); + } + + $this->initialized = true; + } + + $header = $this->createRecordHeader($record); + $this->sendHeader($header); + } + + /** + * @return mixed Writer used for sending headers + */ + public function getWriter() + { + return $this->writer; + } + + /** + * @var mixed Function, Method or Closure to use for sending headers + */ + public function setWriter($writer) + { + $this->writer = $writer; + } + } \ No newline at end of file diff --git a/tests/Monolog/Handler/FirePHPHandlerTest.php b/tests/Monolog/Handler/FirePHPHandlerTest.php index 9f992849..af789a8a 100644 --- a/tests/Monolog/Handler/FirePHPHandlerTest.php +++ b/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -20,24 +20,77 @@ class FirePHPHandlerTest extends TestCase /** * @dataProvider handlerProvider */ - public function testCloseReturnsFalseWhenHeadersAlreadySent($handler) + public function testCloseReturnsHeadersSent($handler) { - $this->assertFalse($handler->close()); - } - - public function testEmptyHandlerHasProtocolStructureAndPluginHeaders() - { - $handler = new FirePHPHandler(); - - $this->assertEquals(3, count($handler->getHeaders())); + $this->assertEquals(headers_sent(), $handler->close()); } /** * @dataProvider handlerProvider */ - public function testHandlerHasWildFireAndRecordHeaders($handler) + public function testDefaultWriterIsClosure($handler) { - $this->assertEquals(7, count($handler->getHeaders())); + $this->assertEquals('header', $handler->getWriter()); + } + + public function testConstructWithWriter() + { + $writer = array($this, 'testWriter'); + + $handler = new FirePHPHandler(Logger::DEBUG, false, $writer); + + $this->assertEquals($writer, $handler->getWriter()); + } + + /** + * @dataProvider handlerProvider + */ + public function testWriterIsSettable($handler) + { + $writer = array($this, 'testWriter'); + $handler->setWriter($writer); + + $this->assertNotEquals('header', $handler->getWriter()); + $this->assertEquals($writer, $handler->getWriter()); + } + + public function testMethodWriter() + { + $handler = new FirePHPHandler; + $handler->setWriter(array($this, 'writerForTestMethodWriter')); + + $handler->handle($this->getRecord(Logger::DEBUG)); + } + + public function writerForTestMethodWriter($header) + { + $valid = array( + 'X-Wf-Protocol-1: http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1: http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1: http://meta.firephp.org/Wildfire/Plugin/ZendFramework/FirePHP/1.6.2', + 'X-Wf-1-1-1-5: 50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', + ); + + $this->assertTrue(in_array($header, $valid)); + } + + public function testClosureWriter() + { + $headers = array(); + + $handler = new FirePHPHandler; + $handler->setWriter(function($header) use (&$headers) { + $headers[] = $header; + }); + + $handler->handle($this->getRecord(Logger::DEBUG)); + + $this->assertEquals( + 'X-Wf-1-1-1-5: 50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', + end($headers) + ); + + $this->assertEquals(4, count($headers), "There should be 3 init headers & 1 message header"); } public function handlerProvider()