Просмотр исходного кода

Refactored FirePHPHandler a bit to support Closures & Methods to override how headers are sent.

(Defaults to PHP's `header` function)
Eric Clemmons 14 лет назад
Родитель
Сommit
12ab07447d
2 измененных файлов с 184 добавлено и 38 удалено
  1. 124 31
      src/Monolog/Handler/FirePHPHandler.php
  2. 60 7
      tests/Monolog/Handler/FirePHPHandlerTest.php

+ 124 - 31
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,54 +86,103 @@ class FirePHPHandler extends AbstractHandler
         );
     }
 
-    protected function write(Array $record)
+    /**
+     * Creates message header from record
+     * 
+     * @see createHeader()
+     * @var Array $record
+     */
+    protected function createRecordHeader(Array $record)
     {
-        $this->records[] = $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.
+        return $this->createHeader(
+            array(1, 1, 1, self::$messageIndex++),
+            $record['message']
+        );
     }
-    
-    public function close()
+
+    protected function getDefaultFormatter()
+    {
+        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 {
-            foreach ($this->getHeaders() as $header) {
-                header($header);
+            $writer = $this->getWriter();
+            
+            if ($writer instanceof \Closure) {
+                $writer($header);
+            } else {
+                call_user_func($writer, $header);
             }
             
             return true;
         }
     }
 
-    public function getHeaders()
+    /**
+     * 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 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']
-            );
+        // WildFire-specific headers must be sent prior to any messages
+        if (! $this->initialized) {
+            foreach ($this->getInitHeaders() as $header) {
+                $this->sendHeader($header);
+            }
+            
+            $this->initialized = true;
         }
         
-        return $headers;
+        $header = $this->createRecordHeader($record);
+        $this->sendHeader($header);
     }
 
-    protected function getDefaultFormatter()
+    /**
+     * @return mixed Writer used for sending headers
+     */
+    public function getWriter()
     {
-        return new WildfireFormatter();
+        return $this->writer;
+    }
+
+    /**
+     * @var mixed Function, Method or Closure to use for sending headers
+     */
+    public function setWriter($writer)
+    {
+        $this->writer = $writer;
     }
 
 }

+ 60 - 7
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());
+        $this->assertEquals(headers_sent(), $handler->close());
     }
 
-    public function testEmptyHandlerHasProtocolStructureAndPluginHeaders()
+    /**
+     * @dataProvider handlerProvider
+     */
+    public function testDefaultWriterIsClosure($handler)
     {
-        $handler = new FirePHPHandler();
+        $this->assertEquals('header', $handler->getWriter());
+    }
+
+    public function testConstructWithWriter()
+    {
+        $writer = array($this, 'testWriter');
         
-        $this->assertEquals(3, count($handler->getHeaders()));
+        $handler = new FirePHPHandler(Logger::DEBUG, false, $writer);
+        
+        $this->assertEquals($writer, $handler->getWriter());
     }
 
     /**
      * @dataProvider handlerProvider
      */
-    public function testHandlerHasWildFireAndRecordHeaders($handler)
+    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()
     {
-        $this->assertEquals(7, count($handler->getHeaders()));
+        $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()