From 031a39410b095669d3412e268560593d05f3b6b6 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Apr 2011 14:10:18 -0700 Subject: [PATCH 01/11] Updated the README with a note about FirePHPHandler --- README.mdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.mdown b/README.mdown index f5ffe4eb..11b53a49 100644 --- a/README.mdown +++ b/README.mdown @@ -32,12 +32,12 @@ Notable Features (non-exhaustive and incomplete) ------------------------------------------------ - _FingersCrossedHandler_: A very interesting handler. It takes a logger as parameter and will accumulate log records of all levels until a record exceeds the defined severity level. At which point it delivers all records, including those of lower severity, to the handler it wraps. This means that until an error actually happens you will not see anything in your logs, but when it happens you will have the full information, including debug and info records. This provides you with the info you need, only when you need it. +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing inline `console` messages within [FireBug](http://getfirebug.com/). Todo ---- - MailHandler -- FirePHPHandler Requirements ------------ From 0148506b2e1fca40b6748710119f6b1b023959e7 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Apr 2011 14:10:55 -0700 Subject: [PATCH 02/11] Added FirePHPHandler using a WildfireFormatter --- src/Monolog/Formatter/WildfireFormatter.php | 72 ++++++++++++++++ src/Monolog/Handler/FirePHPHandler.php | 95 +++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 src/Monolog/Formatter/WildfireFormatter.php create mode 100644 src/Monolog/Handler/FirePHPHandler.php diff --git a/src/Monolog/Formatter/WildfireFormatter.php b/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 00000000..dc4e656c --- /dev/null +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,72 @@ + + * + * 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; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + */ +class WildfireFormatter extends LineFormatter implements FormatterInterface +{ + + /** + * Similar to LineFormatter::SIMPLE_FORMAT, except without the "[%datetime%]" + */ + const SIMPLE_FORMAT = "%channel%: %message% %extra%"; + + // Convert Logger's error levels to Wildfire's + const DEBUG = 'LOG'; + const INFO = 'INFO'; + const WARNING = 'WARN'; + const ERROR = 'ERROR'; + + /** + * {@inheritdoc} + */ + public function __construct($format = null, $dateFormat = null) + { + $this->format = $format ?: self::SIMPLE_FORMAT; + $this->dateFormat = $dateFormat ?: self::SIMPLE_DATE; + } + + /** + * {@inheritdoc} + */ + public function format(Array $record) + { + // Format record according with LineFormatter + $formatted = parent::format($record); + + // Create JSON object describing the appearance of the message in the console + $json = json_encode(array( + array( + 'Type' => constant('self::' . $record['level_name']), + 'File' => '', + 'Line' => '', + ), + $formatted['message'], + )); + + // The message itself is a serialization of the above JSON object + it's length + $formatted['message'] = sprintf( + '%s|%s|', + strlen($json), + $json + ); + + return $formatted; + } + +} \ No newline at end of file diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 00000000..774bd084 --- /dev/null +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,95 @@ + + * + * 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\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractHandler +{ + + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/ZendFramework/FirePHP/1.6.2'; + + private $prefix = 'X-Wf'; + + private $records = array(); + + protected function createHeader(Array $meta, $message) + { + return sprintf( + '%s-%s: %s', + $this->prefix, + join('-', $meta), + $message + ); + } + + 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() + { + // 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), + ); + + foreach ($this->records as $record) { + $headers[] = $this->createHeader( + array($protocolIndex, $structureIndex, $pluginIndex, $messageIndex++), + $record['message'] + ); + } + + return $headers; + } + + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + +} \ No newline at end of file From f15810476244a000c6184f11e8a9082e862d96b6 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Apr 2011 14:12:12 -0700 Subject: [PATCH 03/11] Added tests for WildfireFormatter & FirePHPHandler --- .../Formatter/WildfireFormatterTest.php | 49 ++++++++++++++++ tests/Monolog/Handler/FirePHPHandlerTest.php | 56 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/Monolog/Formatter/WildfireFormatterTest.php create mode 100644 tests/Monolog/Handler/FirePHPHandlerTest.php diff --git a/tests/Monolog/Formatter/WildfireFormatterTest.php b/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 00000000..a02a709f --- /dev/null +++ b/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,49 @@ + + * + * 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 WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @dataProvider recordProvider + */ + public function testDefaultFormatIsLineFormatterWithoutNewLine($record) + { + $wildfire = new WildfireFormatter(); + + $record = $wildfire->format($record); + + $this->assertEquals( + '70|[{"Type":"ERROR","File":"","Line":""},"meh: log extra(ip: 127.0.0.1)"]|', + $record['message'] + ); + } + + public function recordProvider() + { + $record = array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + return array( + array($record), + ); + } + +} diff --git a/tests/Monolog/Handler/FirePHPHandlerTest.php b/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 00000000..9f992849 --- /dev/null +++ b/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -0,0 +1,56 @@ + + * + * 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; + +class FirePHPHandlerTest extends TestCase +{ + + /** + * @dataProvider handlerProvider + */ + public function testCloseReturnsFalseWhenHeadersAlreadySent($handler) + { + $this->assertFalse($handler->close()); + } + + public function testEmptyHandlerHasProtocolStructureAndPluginHeaders() + { + $handler = new FirePHPHandler(); + + $this->assertEquals(3, count($handler->getHeaders())); + } + + /** + * @dataProvider handlerProvider + */ + public function testHandlerHasWildFireAndRecordHeaders($handler) + { + $this->assertEquals(7, count($handler->getHeaders())); + } + + public function handlerProvider() + { + $handler = new FirePHPHandler(); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + + return array( + array($handler), + ); + } +} From 92f866b9d60c999f5234ebf23508a5350da012ba Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Apr 2011 14:12:29 -0700 Subject: [PATCH 04/11] Added "Functional" web-browser test page to verification If there's a better way to test this, let me know. Since the handler reverse-engineers the Wildfire protocol, this was the easiest way to verify functionality. --- .../Functional/Handler/FirePHPHandlerTest.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/Monolog/Functional/Handler/FirePHPHandlerTest.php diff --git a/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php b/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php new file mode 100644 index 00000000..2e4f8711 --- /dev/null +++ b/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +spl_autoload_register(function($class) +{ + $file = __DIR__.'/../../../../src/'.strtr($class, '\\', '/').'.php'; + if (file_exists($file)) { + require $file; + return true; + } +}); + +use Monolog\Logger; +use Monolog\Handler\FirePHPHandler; + +$logger = new Logger('firephp'); +$logger->pushHandler(new FirePHPHandler); + +$logger->addDebug('Debug'); +$logger->addInfo('Info'); +$logger->addWarning('Warning'); +$logger->addError('Error'); \ No newline at end of file From 12ab07447d6a5e6619ab4740e39589e85cc58f1c Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Apr 2011 15:33:39 -0700 Subject: [PATCH 05/11] 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() From 6f2c1682a724e0af515a4c9256451cfa60e77637 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Apr 2011 15:34:29 -0700 Subject: [PATCH 06/11] Added processIsolation flag to PHPUnit so headers_sent() begins as `false` for each test --- phpunit.xml.dist | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9521feb4..776d4785 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,7 @@ - + tests/Monolog/ From 275084d5d79619bede4de039a5e877fc43da5d01 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Apr 2011 17:52:32 -0700 Subject: [PATCH 07/11] Refactored header creation to use $key => $value pairs for easier support for 3rd-party Response objects --- src/Monolog/Handler/FirePHPHandler.php | 43 +++++++++----------- tests/Monolog/Handler/FirePHPHandlerTest.php | 23 ++++++----- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php index a31a0a6c..fb145753 100644 --- a/src/Monolog/Handler/FirePHPHandler.php +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -55,14 +55,14 @@ class FirePHPHandler extends AbstractHandler /** * Function, Method or Closure for sending the header */ - private $writer = 'header'; + private $writer; /** * @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') + public function __construct($level = Logger::DEBUG, $bubble = false, $writer = null) { $this->level = $level; $this->bubble = $bubble; @@ -78,12 +78,9 @@ class FirePHPHandler extends AbstractHandler */ protected function createHeader(Array $meta, $message) { - return sprintf( - '%s-%s: %s', - $this->prefix, - join('-', $meta), - $message - ); + $header = sprintf('%s-%s', $this->prefix, join('-', $meta)); + + return array($header => $message); } /** @@ -117,10 +114,10 @@ class FirePHPHandler extends AbstractHandler protected function getInitHeaders() { // Initial payload consists of required headers for Wildfire - return array( + return array_merge( $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), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) ); } @@ -128,23 +125,20 @@ class FirePHPHandler extends AbstractHandler * Send header string to the client * * @var String $header + * @var String $content * @return Boolean False if headers are already sent, true if header are sent successfully */ - protected function sendHeader($header) + protected function sendHeader($header, $content) { if (headers_sent()) { return false; + } else if ($writer = $this->getWriter()) { + call_user_func_array($writer, array($header, $content)); } else { - $writer = $this->getWriter(); - - if ($writer instanceof \Closure) { - $writer($header); - } else { - call_user_func($writer, $header); - } - - return true; + header(sprintf('%s: %s', $header, $content)); } + + return true; } /** @@ -158,15 +152,16 @@ class FirePHPHandler extends AbstractHandler { // WildFire-specific headers must be sent prior to any messages if (! $this->initialized) { - foreach ($this->getInitHeaders() as $header) { - $this->sendHeader($header); + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); } $this->initialized = true; } - $header = $this->createRecordHeader($record); - $this->sendHeader($header); + foreach ($this->createRecordHeader($record) as $header => $content) { + $this->sendHeader($header, $content); + } } /** diff --git a/tests/Monolog/Handler/FirePHPHandlerTest.php b/tests/Monolog/Handler/FirePHPHandlerTest.php index af789a8a..9252c37c 100644 --- a/tests/Monolog/Handler/FirePHPHandlerTest.php +++ b/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -28,9 +28,9 @@ class FirePHPHandlerTest extends TestCase /** * @dataProvider handlerProvider */ - public function testDefaultWriterIsClosure($handler) + public function testDefaultWriterIsNull($handler) { - $this->assertEquals('header', $handler->getWriter()); + $this->assertEquals(null, $handler->getWriter()); } public function testConstructWithWriter() @@ -62,16 +62,17 @@ class FirePHPHandlerTest extends TestCase $handler->handle($this->getRecord(Logger::DEBUG)); } - public function writerForTestMethodWriter($header) + public function writerForTestMethodWriter($header, $content) { $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 "]|', + '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)); + $this->assertTrue(array_key_exists($header, $valid)); + $this->assertEquals($valid[$header], $content); } public function testClosureWriter() @@ -79,14 +80,14 @@ class FirePHPHandlerTest extends TestCase $headers = array(); $handler = new FirePHPHandler; - $handler->setWriter(function($header) use (&$headers) { - $headers[] = $header; + $handler->setWriter(function($header, $content) use (&$headers) { + $headers[$header] = $content; }); $handler->handle($this->getRecord(Logger::DEBUG)); $this->assertEquals( - 'X-Wf-1-1-1-5: 50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', + '50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', end($headers) ); From a0b8f75b2b437633bc58e86c8c25814a1b6e732d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 25 Apr 2011 15:41:58 +0200 Subject: [PATCH 08/11] Revert "Added processIsolation flag to PHPUnit so headers_sent() begins as `false` for each test" This reverts commit 6f2c1682a724e0af515a4c9256451cfa60e77637. --- phpunit.xml.dist | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 776d4785..9521feb4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,6 @@ - + tests/Monolog/ From 2b1c68e0d0221a2f06658b0f8c68aa5b4ff0f1be Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 25 Apr 2011 15:48:52 +0200 Subject: [PATCH 09/11] Make $initialized static Removed custom writer, overriding is easy enough CS fixes Added TestFirePHPHandler class to enable testing of headers --- src/Monolog/Formatter/WildfireFormatter.php | 7 +- src/Monolog/Handler/FirePHPHandler.php | 88 +++++-------- tests/Monolog/Handler/FirePHPHandlerTest.php | 126 ++++++++----------- 3 files changed, 83 insertions(+), 138 deletions(-) diff --git a/src/Monolog/Formatter/WildfireFormatter.php b/src/Monolog/Formatter/WildfireFormatter.php index dc4e656c..8fd2fc7d 100644 --- a/src/Monolog/Formatter/WildfireFormatter.php +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -20,7 +20,6 @@ use Monolog\Logger; */ class WildfireFormatter extends LineFormatter implements FormatterInterface { - /** * Similar to LineFormatter::SIMPLE_FORMAT, except without the "[%datetime%]" */ @@ -48,7 +47,7 @@ class WildfireFormatter extends LineFormatter implements FormatterInterface { // Format record according with LineFormatter $formatted = parent::format($record); - + // Create JSON object describing the appearance of the message in the console $json = json_encode(array( array( @@ -58,14 +57,14 @@ class WildfireFormatter extends LineFormatter implements FormatterInterface ), $formatted['message'], )); - + // The message itself is a serialization of the above JSON object + it's length $formatted['message'] = sprintf( '%s|%s|', strlen($json), $json ); - + return $formatted; } diff --git a/src/Monolog/Handler/FirePHPHandler.php b/src/Monolog/Handler/FirePHPHandler.php index fb145753..3ad2f6f3 100644 --- a/src/Monolog/Handler/FirePHPHandler.php +++ b/src/Monolog/Handler/FirePHPHandler.php @@ -21,7 +21,6 @@ use Monolog\Formatter\WildfireFormatter; */ class FirePHPHandler extends AbstractHandler { - /** * WildFire JSON header message format */ @@ -37,59 +36,53 @@ class FirePHPHandler extends AbstractHandler */ 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'; + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; /** * Shared static message index between potentially multiple handlers + * @var int */ - private static $messageIndex = 1; - - /** - * Function, Method or Closure for sending the header - */ - private $writer; + protected static $messageIndex = 1; /** * @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 = null) + public function __construct($level = Logger::DEBUG, $bubble = false) { $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 + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return string Complete header string ready for the client */ - protected function createHeader(Array $meta, $message) + protected function createHeader(array $meta, $message) { - $header = sprintf('%s-%s', $this->prefix, join('-', $meta)); - + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + return array($header => $message); } /** * Creates message header from record - * + * * @see createHeader() - * @var Array $record + * @param array $record */ - protected function createRecordHeader(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), so we're using "1" for simplicity's sake. @@ -124,21 +117,14 @@ class FirePHPHandler extends AbstractHandler /** * Send header string to the client * - * @var String $header - * @var String $content - * @return Boolean False if headers are already sent, true if header are sent successfully + * @param string $header + * @param string $content */ protected function sendHeader($header, $content) { - if (headers_sent()) { - return false; - } else if ($writer = $this->getWriter()) { - call_user_func_array($writer, array($header, $content)); - } else { + if (!headers_sent()) { header(sprintf('%s: %s', $header, $content)); } - - return true; } /** @@ -146,38 +132,20 @@ class FirePHPHandler extends AbstractHandler * * @see sendHeader() * @see sendInitHeaders() - * @var Array $record + * @param array $record */ - protected function write(Array $record) + protected function write(array $record) { // WildFire-specific headers must be sent prior to any messages - if (! $this->initialized) { + if (!self::$initialized) { foreach ($this->getInitHeaders() as $header => $content) { $this->sendHeader($header, $content); } - - $this->initialized = true; + + self::$initialized = true; } - - foreach ($this->createRecordHeader($record) as $header => $content) { - $this->sendHeader($header, $content); - } - } - /** - * @return mixed Writer used for sending headers - */ - public function getWriter() - { - return $this->writer; + $header = $this->createRecordHeader($record); + $this->sendHeader(key($header), current($header)); } - - /** - * @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 9252c37c..107117b8 100644 --- a/tests/Monolog/Handler/FirePHPHandlerTest.php +++ b/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -16,95 +16,73 @@ use Monolog\Logger; class FirePHPHandlerTest extends TestCase { - - /** - * @dataProvider handlerProvider - */ - public function testCloseReturnsHeadersSent($handler) + public function setUp() { - $this->assertEquals(headers_sent(), $handler->close()); + TestFirePHPHandler::reset(); } - /** - * @dataProvider handlerProvider - */ - public function testDefaultWriterIsNull($handler) + public function testHeaders() { - $this->assertEquals(null, $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 = new TestFirePHPHandler; $handler->handle($this->getRecord(Logger::DEBUG)); - } + $handler->handle($this->getRecord(Logger::WARNING)); - public function writerForTestMethodWriter($header, $content) - { - $valid = array( + $expected = 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 "]|', + 'X-Wf-1-1-1-1' => '50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', + 'X-Wf-1-1-1-2' => '51|[{"Type":"WARN","File":"","Line":""},"test: test "]|', ); - - $this->assertTrue(array_key_exists($header, $valid)); - $this->assertEquals($valid[$header], $content); + + $this->assertEquals($expected, $handler->getHeaders()); } - public function testClosureWriter() + public function testConcurrentHandlers() { - $headers = array(); - - $handler = new FirePHPHandler; - $handler->setWriter(function($header, $content) use (&$headers) { - $headers[$header] = $content; - }); - + $handler = new TestFirePHPHandler; $handler->handle($this->getRecord(Logger::DEBUG)); - - $this->assertEquals( - '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() - { - $handler = new FirePHPHandler(); - - $handler->handle($this->getRecord(Logger::DEBUG)); - $handler->handle($this->getRecord(Logger::DEBUG)); - $handler->handle($this->getRecord(Logger::INFO)); $handler->handle($this->getRecord(Logger::WARNING)); - - return array( - array($handler), + + $handler2 = new TestFirePHPHandler; + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = 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-1' => '50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', + 'X-Wf-1-1-1-2' => '51|[{"Type":"WARN","File":"","Line":""},"test: test "]|', ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => '50|[{"Type":"LOG","File":"","Line":""},"test: test "]|', + 'X-Wf-1-1-1-4' => '51|[{"Type":"WARN","File":"","Line":""},"test: test "]|', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); } } + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} \ No newline at end of file From 9e7471f77e77ce69cfaba3e94e8f92ffb86ba5a2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 25 Apr 2011 15:49:27 +0200 Subject: [PATCH 10/11] Aligned log level conversion with the one of syslog --- src/Monolog/Formatter/WildfireFormatter.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Monolog/Formatter/WildfireFormatter.php b/src/Monolog/Formatter/WildfireFormatter.php index 8fd2fc7d..97ca7333 100644 --- a/src/Monolog/Formatter/WildfireFormatter.php +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -25,11 +25,15 @@ class WildfireFormatter extends LineFormatter implements FormatterInterface */ const SIMPLE_FORMAT = "%channel%: %message% %extra%"; - // Convert Logger's error levels to Wildfire's - const DEBUG = 'LOG'; - const INFO = 'INFO'; - const WARNING = 'WARN'; - const ERROR = 'ERROR'; + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + ); /** * {@inheritdoc} @@ -51,7 +55,7 @@ class WildfireFormatter extends LineFormatter implements FormatterInterface // Create JSON object describing the appearance of the message in the console $json = json_encode(array( array( - 'Type' => constant('self::' . $record['level_name']), + 'Type' => $this->logLevels($record['level']), 'File' => '', 'Line' => '', ), From 252058251d49e15d981a3d52b94fb3f176cb649c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 25 Apr 2011 15:51:10 +0200 Subject: [PATCH 11/11] Fixed previous commit --- src/Monolog/Formatter/WildfireFormatter.php | 2 +- tests/Monolog/Formatter/WildfireFormatterTest.php | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Monolog/Formatter/WildfireFormatter.php b/src/Monolog/Formatter/WildfireFormatter.php index 97ca7333..39512b4e 100644 --- a/src/Monolog/Formatter/WildfireFormatter.php +++ b/src/Monolog/Formatter/WildfireFormatter.php @@ -55,7 +55,7 @@ class WildfireFormatter extends LineFormatter implements FormatterInterface // Create JSON object describing the appearance of the message in the console $json = json_encode(array( array( - 'Type' => $this->logLevels($record['level']), + 'Type' => $this->logLevels[$record['level']], 'File' => '', 'Line' => '', ), diff --git a/tests/Monolog/Formatter/WildfireFormatterTest.php b/tests/Monolog/Formatter/WildfireFormatterTest.php index a02a709f..b2dfa924 100644 --- a/tests/Monolog/Formatter/WildfireFormatterTest.php +++ b/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -22,9 +22,9 @@ class WildfireFormatterTest extends \PHPUnit_Framework_TestCase public function testDefaultFormatIsLineFormatterWithoutNewLine($record) { $wildfire = new WildfireFormatter(); - + $record = $wildfire->format($record); - + $this->assertEquals( '70|[{"Type":"ERROR","File":"","Line":""},"meh: log extra(ip: 127.0.0.1)"]|', $record['message'] @@ -34,13 +34,14 @@ class WildfireFormatterTest extends \PHPUnit_Framework_TestCase public function recordProvider() { $record = array( + 'level' => Logger::ERROR, 'level_name' => 'ERROR', 'channel' => 'meh', 'datetime' => new \DateTime, 'extra' => array('ip' => '127.0.0.1'), 'message' => 'log', ); - + return array( array($record), );