Bladeren bron

Major refactoring to follow the Logbook model

Jordi Boggiano 15 jaren geleden
bovenliggende
commit
3fa6e4b91f

+ 14 - 17
README.mdown

@@ -5,28 +5,20 @@ Usage
 -----
 -----
 
 
     use Monolog\Logger;
     use Monolog\Logger;
-    use Monolog\Log;
-    use Monolog\Writer\FileWriter;
-    use Monolog\Formatter\SimpleFormatter;
+    use Monolog\Handler\FileHandler;
 
 
-    // create a log with writers/formatters
-    $writer = new FileWriter('path/to/your.log');
-    $writer->setFormatter(new SimpleFormatter());
-    $log = new Log('name', Logger::WARN, $writer);
+    // create a log channel
+    $log = new Logger('name');
+    $log->pushHandler(new FileHandler('path/to/your.log', Logger::WARNING));
 
 
-    // create a logger with one or more logs
-    $logger = new Logger(array($log));
-
-    // write to all logs
-    $logger->addWarning('Foo');
-
-    // write only to the 'name' log
-    $logger->addError('Bar', 'name');
+    // add messages to the log
+    $log->addWarning('Foo');
+    $log->addError('Bar');
 
 
 Todo
 Todo
 ----
 ----
 
 
-- Log rotation for FileWriter
+- Log rotation for RotatingFileHandler
 - FirePHP writer
 - FirePHP writer
 - Syslog writer
 - Syslog writer
 
 
@@ -43,4 +35,9 @@ Monolog is licensed under the MIT License - see the LICENSE file for details
 Requirements
 Requirements
 ------------
 ------------
 
 
-Any flavor of PHP5.3 should do
+Any flavor of PHP 5.3 should do
+
+Acknowledgements
+----------------
+
+This library is heavily inspired by Python's Logbook library, although it has been adapted to fit in PHP.

+ 1 - 1
src/Monolog/Formatter/FormatterInterface.php

@@ -13,5 +13,5 @@ namespace Monolog\Formatter;
 
 
 interface FormatterInterface
 interface FormatterInterface
 {
 {
-    function format($log, $message);
+    function format($message);
 }
 }

+ 23 - 0
src/Monolog/Formatter/JsonFormatter.php

@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 JsonFormatter implements FormatterInterface
+{
+    public function format($message)
+    {
+        $message['message'] = json_encode($message['message']);
+        return $message;
+    }
+}

+ 15 - 14
src/Monolog/Formatter/SimpleFormatter.php → src/Monolog/Formatter/LineFormatter.php

@@ -13,9 +13,9 @@ namespace Monolog\Formatter;
 
 
 use Monolog\Logger;
 use Monolog\Logger;
 
 
-class SimpleFormatter implements FormatterInterface
+class LineFormatter implements FormatterInterface
 {
 {
-    const SIMPLE_FORMAT = "[%date%] %log%.%level%: %message%\n";
+    const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message%\n";
     const SIMPLE_DATE = "Y-m-d H:i:s";
     const SIMPLE_DATE = "Y-m-d H:i:s";
 
 
     protected $format;
     protected $format;
@@ -27,25 +27,26 @@ class SimpleFormatter implements FormatterInterface
         $this->dateFormat = $dateFormat ?: self::SIMPLE_DATE;
         $this->dateFormat = $dateFormat ?: self::SIMPLE_DATE;
     }
     }
 
 
-    public function format($log, $message)
+    public function format($message)
     {
     {
-        $defaults = array(
-            'log' => $log,
-            'level' => Logger::getLevelName($message['level']),
-            'date' => date($this->dateFormat),
-        );
+        $vars = $message;
+        $vars['datetime'] = $vars['datetime']->format($this->dateFormat);
 
 
         if (is_array($message['message'])) {
         if (is_array($message['message'])) {
-            $vars = array_merge($defaults, $message['message']);
-        } else {
-            $vars = $defaults;
-            $vars['message'] = $message['message'];
+            unset($vars['message']);
+            $vars = array_merge($vars, $message['message']);
         }
         }
 
 
-        $message = $this->format;
+        $output = $this->format;
         foreach ($vars as $var => $val) {
         foreach ($vars as $var => $val) {
-            $message = str_replace('%'.$var.'%', $val, $message);
+            if (!is_array($val)) {
+                $output = str_replace('%'.$var.'%', $val, $output);
+            }
         }
         }
+        foreach ($vars['extra'] as $var => $val) {
+            $output = str_replace('%extra.'.$var.'%', $val, $output);
+        }
+        $message['message'] = $output;
         return $message;
         return $message;
     }
     }
 }
 }

+ 121 - 0
src/Monolog/Handler/AbstractHandler.php

@@ -0,0 +1,121 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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\LineFormatter;
+
+abstract class AbstractHandler implements HandlerInterface
+{
+    protected $level;
+    protected $bubble;
+    protected $parent;
+
+    protected $formatter;
+    protected $processor;
+
+    public function __construct($level = Logger::DEBUG, $bubble = false)
+    {
+        $this->level = $level;
+        $this->bubble = $bubble;
+    }
+
+    public function handle($message)
+    {
+        if ($message['level'] < $this->level) {
+            return false;
+        }
+
+        if ($this->processor) {
+            $message = call_user_func($this->processor, $message, $this);
+        }
+
+        if (!$this->formatter) {
+            $this->formatter = $this->getDefaultFormatter();
+        }
+        $message = $this->formatter->format($message);
+
+        $this->write($message);
+        return false === $this->bubble;
+    }
+
+    abstract public function write($message);
+
+    public function close()
+    {
+    }
+
+    public function setProcessor($callback)
+    {
+        $this->processor = $callback;
+    }
+
+    public function getProcessor()
+    {
+        return $this->processor;
+    }
+
+    public function setFormatter($formatter)
+    {
+        $this->formatter = $formatter;
+    }
+
+    public function getFormatter()
+    {
+        return $this->formatter;
+    }
+
+    public function setLevel($level)
+    {
+        $this->level = $level;
+    }
+
+    public function getLevel()
+    {
+        return $this->level;
+    }
+
+    public function setBubble($bubble)
+    {
+        $this->bubble = $bubble;
+    }
+
+    public function getBubble()
+    {
+        return $this->bubble;
+    }
+
+    public function getParent()
+    {
+        return $this->parent;
+    }
+
+    /**
+     * Sets the parent handler
+     *
+     * @param Monolog\Handler\HandlerInterface
+     */
+    public function setParent(HandlerInterface $parent)
+    {
+        $this->parent = $parent;
+    }
+
+    public function __destruct()
+    {
+        $this->close();
+    }
+
+    protected function getDefaultFormatter()
+    {
+        return new LineFormatter();
+    }
+}

+ 29 - 0
src/Monolog/Handler/HandlerInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+interface HandlerInterface
+{
+    public function handle($message);
+
+    public function setLevel($level);
+
+    public function getLevel();
+
+    public function setBubble($bubble);
+
+    public function getBubble();
+
+    public function getParent();
+
+    public function setParent(HandlerInterface $parent);
+}

+ 29 - 0
src/Monolog/Handler/NullHandler.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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;
+
+class NullHandler extends AbstractHandler
+{
+    public function handle($message)
+    {
+        if ($message['level'] < $this->level) {
+            return false;
+        }
+        return false === $this->bubble;
+    }
+
+    public function write($message)
+    {
+    }
+}

+ 2 - 9
src/Monolog/Writer/FileWriter.php → src/Monolog/Handler/RotatingFileHandler.php

@@ -9,20 +9,13 @@
  * file that was distributed with this source code.
  * file that was distributed with this source code.
  */
  */
 
 
-namespace Monolog\Writer;
+namespace Monolog\Handler;
 
 
-class FileWriter extends StreamWriter
+class RotatingFileHandler extends StreamHandler
 {
 {
     protected $rotation;
     protected $rotation;
     protected $maxAge;
     protected $maxAge;
 
 
-    public function __construct($file, $rotation = null, $maxAge = null)
-    {
-        parent::__construct($file);
-        $this->rotation = $rotation;
-        $this->maxAge = $maxAge;
-    }
-
     public function close()
     public function close()
     {
     {
         parent::close();
         parent::close();

+ 53 - 0
src/Monolog/Handler/StreamHandler.php

@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\SimpleFormatter;
+use Monolog\Logger;
+
+class StreamHandler extends AbstractHandler
+{
+    protected $stream;
+    protected $url;
+
+    public function __construct($stream, $level = Logger::DEBUG, $bubble = true)
+    {
+        parent::__construct($level, $bubble);
+        if (is_resource($stream)) {
+            $this->stream = $stream;
+        } else {
+            $this->url = $stream;
+        }
+    }
+
+    public function write($message)
+    {
+        if (null === $this->stream) {
+            if (!$this->url) {
+                throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
+            }
+            $this->stream = fopen($this->url, 'a');
+            if (!is_resource($this->stream)) {
+                throw new \UnexpectedValueException('The stream could not be opened, "'.$this->url.'" may be an invalid url.');
+            }
+        }
+        fwrite($this->stream, (string) $message['message']);
+    }
+
+    public function close()
+    {
+        if (null !== $this->stream) {
+            fclose($this->stream);
+            $this->stream = null;
+        }
+    }
+}

+ 0 - 59
src/Monolog/Log.php

@@ -1,59 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Monolog;
-
-use Monolog\Writer\WriterInterface;
-
-class Log
-{
-    protected $level;
-    protected $name;
-    protected $writers;
-
-    public function __construct($name, $level = Logger::WARNING, $writers = array())
-    {
-        $this->name = $name;
-        // TODO move level down to the writers
-        $this->level = $level;
-        $this->writers = is_array($writers) ? $writers : array($writers);
-    }
-
-    public function getName()
-    {
-        return $this->name;
-    }
-
-    public function addWriter(WriterInterface $writer)
-    {
-        $this->writers[] = $writer;
-    }
-
-    public function addMessage($level, $message)
-    {
-        if ($level < $this->level) {
-            return;
-        }
-        foreach ($this->writers as $writer) {
-            $writer->write($this->name, $level, $message);
-        }
-    }
-
-    public function setLevel($level)
-    {
-        $this->level = $level;
-    }
-
-    public function getLevel()
-    {
-        return $this->level;
-    }
-}

+ 66 - 43
src/Monolog/Logger.php

@@ -11,6 +11,9 @@
 
 
 namespace Monolog;
 namespace Monolog;
 
 
+use Monolog\Handler\HandlerInterface;
+use Monolog\Handler\StreamHandler;
+
 class Logger
 class Logger
 {
 {
     /**
     /**
@@ -42,104 +45,124 @@ class Logger
         400 => 'ERROR',
         400 => 'ERROR',
     );
     );
 
 
-    protected $logs;
+    protected $name;
+
+    /**
+     * The handler instance at the top of the handler stack
+     *
+     * @var Monolog\Handler\HandlerInterface
+     */
+    protected $handler;
 
 
-    public function __construct($logs = array())
+    public function __construct($name)
     {
     {
-        $this->logs = array();
-        if (!is_array($logs)) {
-            $logs = array($logs);
-        }
-        foreach ($logs as $log) {
-            $this->logs[$log->getName()] = $log;
+        $this->name = $name;
+    }
+
+    public function pushHandler(HandlerInterface $handler)
+    {
+        if ($this->handler) {
+            $handler->setParent($this->handler);
         }
         }
+        $this->handler = $handler;
     }
     }
 
 
-    public function addLog(Log $log)
+    public function popHandler()
     {
     {
-        $this->logs[$log->getName()] = $log;
+        if (null === $this->handler) {
+            throw new \LogicException('You tried to pop from an empty handler stack.');
+        }
+        $top = $this->handler;
+        $this->handler = $top->getParent();
+        return $top;
     }
     }
 
 
-    public function addMessage($level, $message, $log = null)
+    public function addMessage($level, $message)
     {
     {
+        if (null === $this->handler) {
+            $this->pushHandler(new StreamHandler('php://stderr', self::DEBUG));
+        }
         $message = array(
         $message = array(
             'message' => $message,
             'message' => $message,
             'level' => $level,
             'level' => $level,
+            'level_name' => $this->getLevelName($level),
+            'channel' => $this->name,
+            'datetime' => new \DateTime(),
+            'extra' => array(),
         );
         );
-        if (null === $log) {
-            $logs = $this->logs;
-        } else {
-            $logs = is_array($log) ? array_flip($log) : array($log => true);
-        }
-        foreach ($logs as $log => $dummy) {
-            $this->logs[$log]->log($level, $message);
+        $handled = false;
+        $handler = $this->handler;
+        while ($handler && true !== $handled) {
+            $handled = (bool) $handler->handle($message);
+            $handler = $handler->getParent();
         }
         }
+        return $handled;
     }
     }
 
 
-    public function addDebug($message, $log = null)
+    public function addDebug($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::DEBUG, $message, $log);
+        $this->addMessage(self::DEBUG, $message, $channel);
     }
     }
 
 
-    public function addInfo($message, $log = null)
+    public function addInfo($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::INFO, $message, $log);
+        $this->addMessage(self::INFO, $message, $channel);
     }
     }
 
 
-    public function addWarning($message, $log = null)
+    public function addWarning($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::WARNING, $message, $log);
+        $this->addMessage(self::WARNING, $message, $channel);
     }
     }
 
 
-    public function addError($message, $log = null)
+    public function addError($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::ERROR, $message, $log);
+        $this->addMessage(self::ERROR, $message, $channel);
     }
     }
 
 
-    public static function getLevelName($level)
+    public function getLevelName($level)
     {
     {
         return self::$levels[$level];
         return self::$levels[$level];
     }
     }
 
 
     // ZF Logger Compat
     // ZF Logger Compat
 
 
-    public function debug($message, $log = null)
+    public function debug($message, $channel = null)
     {
     {
-        $this->addMessage(self::DEBUG, $message, $log);
+        $this->addMessage(self::DEBUG, $message, $channel);
     }
     }
 
 
-    public function info($message, $log = null)
+    public function info($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::INFO, $message, $log);
+        $this->addMessage(self::INFO, $message, $channel);
     }
     }
 
 
-    public function notice($message, $log = null)
+    public function notice($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::INFO, $message, $log);
+        $this->addMessage(self::INFO, $message, $channel);
     }
     }
 
 
-    public function warn($message, $log = null)
+    public function warn($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::WARNING, $message, $log);
+        $this->addMessage(self::WARNING, $message, $channel);
     }
     }
 
 
-    public function err($message, $log = null)
+    public function err($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::ERROR, $message, $log);
+        $this->addMessage(self::ERROR, $message, $channel);
     }
     }
 
 
-    public function crit($message, $log = null)
+    public function crit($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::ERROR, $message, $log);
+        $this->addMessage(self::ERROR, $message, $channel);
     }
     }
 
 
-    public function alert($message, $log = null)
+    public function alert($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::ERROR, $message, $log);
+        $this->addMessage(self::ERROR, $message, $channel);
     }
     }
 
 
-    public function emerg($message, $log = null)
+    public function emerg($message, $channel = 'default')
     {
     {
-        $this->addMessage(self::ERROR, $message, $log);
+        $this->addMessage(self::ERROR, $message, $channel);
     }
     }
 }
 }

+ 28 - 0
src/Monolog/Processor/WebProcessor.php

@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+class WebProcessor
+{
+    public function __invoke($message, $handler)
+    {
+        $message['extra'] = array_merge(
+            $message['extra'],
+            array(
+                'url' => $_SERVER['REQUEST_URI'],
+                'ip' => $_SERVER['REMOTE_ADDR'],
+                'method' => $_SERVER['REQUEST_METHOD'],
+            )
+        );
+        return $message;
+    }
+}

+ 0 - 29
src/Monolog/Writer/NullWriter.php

@@ -1,29 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Monolog\Writer;
-
-use Monolog\Formatter\FormatterInterface;
-
-class NullWriter implements WriterInterface
-{
-    public function write($log, $message)
-    {
-    }
-
-    public function close()
-    {
-    }
-
-    public function setFormatter(FormatterInterface $formatter)
-    {
-    }
-}

+ 0 - 59
src/Monolog/Writer/StreamWriter.php

@@ -1,59 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Monolog\Writer;
-
-use Monolog\Formatter\FormatterInterface;
-
-class StreamWriter implements WriterInterface
-{
-    protected $formatter;
-    protected $stream;
-    protected $url;
-
-    public function __construct($streamUrl)
-    {
-        if (is_resource($streamUrl)) {
-            $this->stream = $streamUrl;
-        } else {
-            $this->url = $streamUrl;
-        }
-    }
-
-    public function write($log, $message)
-    {
-        if (null === $this->stream) {
-            $this->stream = fopen($this->url, 'a');
-        }
-        if ($this->formatter) {
-            $message = $this->formatter->format($log, $message);
-        }
-        fwrite($this->stream, (string) $message['message']);
-    }
-
-    public function close()
-    {
-        fclose($this->stream);
-        $this->stream = null;
-    }
-
-    public function setFormatter(FormatterInterface $formatter)
-    {
-        $this->formatter = $formatter;
-    }
-
-    public function __destruct()
-    {
-        if (null !== $this->stream) {
-            $this->close();
-        }
-    }
-}

+ 0 - 21
src/Monolog/Writer/WriterInterface.php

@@ -1,21 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Monolog\Writer;
-
-use Monolog\Formatter\FormatterInterface;
-
-interface WriterInterface
-{
-    function setFormatter(FormatterInterface $formatter);
-    function write($log, $message);
-    function close();
-}

+ 30 - 0
tests/Monolog/Formatter/JsonFormatterTest.php

@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 JsonFormatterTest extends \PHPUnit_Framework_TestCase
+{
+    public function testFormat()
+    {
+        $formatter = new JsonFormatter();
+        $message = $formatter->format(array(
+            'level_name' => 'WARNING',
+            'channel' => 'log',
+            'message' => array('foo'),
+            'datetime' => new \DateTime,
+            'extra' => array(),
+        ));
+        $this->assertEquals(json_encode(array('foo')), $message['message']);
+    }
+}

+ 47 - 0
tests/Monolog/Formatter/LineFormatterTest.php

@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 LineFormatterTest extends \PHPUnit_Framework_TestCase
+{
+    public function testDefFormatWithString()
+    {
+        $formatter = new LineFormatter(null, 'Y-m-d');
+        $message = $formatter->format(array(
+            'level_name' => 'WARNING',
+            'channel' => 'log',
+            'message' => 'foo',
+            'datetime' => new \DateTime,
+            'extra' => array(),
+        ));
+        $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo'."\n", $message['message']);
+    }
+
+    public function testDefFormatWithArray()
+    {
+        $formatter = new LineFormatter(null, 'Y-m-d');
+        $message = $formatter->format(array(
+            'level_name' => 'ERROR',
+            'channel' => 'meh',
+            'datetime' => new \DateTime,
+            'extra' => array(),
+            'message' => array(
+                'channel' => 'log',
+                'level_name' => 'WARNING',
+                'message' => 'foo',
+            )
+        ));
+        $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo'."\n", $message['message']);
+    }
+}

+ 0 - 38
tests/Monolog/Formatter/SimpleFormatterTest.php

@@ -1,38 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * 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 SimpleFormatterTest extends \PHPUnit_Framework_TestCase
-{
-    public function testDefFormatWithString()
-    {
-        $formatter = new SimpleFormatter(null, 'Y-m-d');
-        $message = $formatter->format('log', array('level' => Logger::WARNING, 'message' => 'foo'));
-        $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo'."\n", $message);
-    }
-
-    public function testDefFormatWithArray()
-    {
-        $formatter = new SimpleFormatter(null, 'Y-m-d');
-        $message = $formatter->format('xx', array(
-            'level' => Logger::ERROR,
-            'message' => array(
-                'log' => 'log',
-                'level' => 'WARNING',
-                'message' => 'foo',
-            )
-        ));
-        $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo'."\n", $message);
-    }
-}

+ 54 - 0
tests/Monolog/Handler/AbstractHandlerTest.php

@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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;
+
+class AbstractHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testHandle()
+    {
+        $handler = new TestHandler();
+        $this->assertTrue($handler->handle($this->getMessage()));
+    }
+
+    public function testHandleLowerLevelMessage()
+    {
+        $handler = new TestHandler();
+        $this->assertFalse($handler->handle($this->getMessage(Logger::DEBUG)));
+    }
+
+    public function testHandleBubbling()
+    {
+        $handler = new TestHandler(Logger::DEBUG, true);
+        $this->assertFalse($handler->handle($this->getMessage()));
+    }
+
+    protected function getMessage($level = Logger::WARNING)
+    {
+        return array(
+            'level' => $level,
+            'level_name' => 'WARNING',
+            'channel' => 'log',
+            'message' => 'foo',
+            'datetime' => new \DateTime,
+            'extra' => array(),
+        );
+    }
+}
+
+class TestHandler extends AbstractHandler
+{
+    public function write($message)
+    {
+    }
+}

+ 51 - 0
tests/Monolog/Handler/NullHandlerTest.php

@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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;
+
+class NullHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testHandle()
+    {
+        $handler = new NullHandler();
+        $this->assertTrue($handler->handle($this->getMessage()));
+    }
+
+    public function testHandleLowerLevelMessage()
+    {
+        $handler = new NullHandler(Logger::WARNING);
+        $this->assertFalse($handler->handle($this->getMessage(Logger::DEBUG)));
+    }
+
+    public function testHandleBubbling()
+    {
+        $handler = new NullHandler(Logger::DEBUG, true);
+        $this->assertFalse($handler->handle($this->getMessage()));
+    }
+
+    /**
+     * No-op test for coverage
+     */
+    public function testWrite()
+    {
+        $handler = new NullHandler();
+        $handler->write($this->getMessage());
+    }
+
+    protected function getMessage($level = Logger::WARNING)
+    {
+        return array(
+            'level' => $level,
+        );
+    }
+}

+ 61 - 0
tests/Monolog/Handler/StreamHandlerTest.php

@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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;
+
+class StreamHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testWrite()
+    {
+        $handle = fopen('php://memory', 'a+');
+        $handler = new StreamHandler($handle);
+        $handler->write(array('message' => 'test'));
+        $handler->write(array('message' => 'test2'));
+        $handler->write(array('message' => 'test3'));
+        fseek($handle, 0);
+        $this->assertEquals('testtest2test3', fread($handle, 100));
+    }
+
+    public function testClose()
+    {
+        $handle = fopen('php://memory', 'a+');
+        $handler = new StreamHandler($handle);
+        $this->assertTrue(is_resource($handle));
+        $handler->close();
+        $this->assertFalse(is_resource($handle));
+    }
+
+    public function testWriteCreatesTheStreamResource()
+    {
+        $handler = new StreamHandler('php://memory');
+        $handler->write(array('message' => 'test'));
+    }
+
+    /**
+     * @expectedException LogicException
+     */
+    public function testWriteMissingResource()
+    {
+        $handler = new StreamHandler(null);
+        $handler->write(array('message' => 'test'));
+    }
+
+    /**
+     * @expectedException UnexpectedValueException
+     */
+    public function testWriteInvalidResource()
+    {
+        $handler = new StreamHandler('bogus://url');
+        @$handler->write(array('message' => 'test'));
+    }
+}

+ 0 - 44
tests/Monolog/LogTest.php

@@ -1,44 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Monolog;
-
-class LogTest extends \PHPUnit_Framework_TestCase
-{
-    public function testLog()
-    {
-        $logger = new Log('bob');
-        $writer1 = $this->getMock('Monolog\Writer\NullWriter', array('write'));
-        $writer1->expects($this->once())
-            ->method('write')
-            ->with('bob', Logger::WARNING, 'test');
-        $writer2 = $this->getMock('Monolog\Writer\NullWriter', array('write'));
-        $writer2->expects($this->once())
-            ->method('write')
-            ->with('bob', Logger::WARNING, 'test');
-        $logger->addWriter($writer1);
-        $logger->addWriter($writer2);
-        $logger->addMessage(Logger::WARNING, 'test');
-    }
-
-    public function testLogLowLevel()
-    {
-        $logger = new Log('bob');
-        $logger->setLevel(Logger::ERROR);
-        $this->assertEquals(Logger::ERROR, $logger->getLevel());
-
-        $writer1 = $this->getMock('Monolog\Writer\NullWriter', array('write'));
-        $writer1->expects($this->never())
-            ->method('write');
-        $logger->addWriter($writer1);
-        $logger->addMessage(Logger::WARNING, 'test');
-    }
-}

+ 48 - 24
tests/Monolog/LoggerTest.php

@@ -13,33 +13,57 @@ namespace Monolog;
 
 
 class LoggerTest extends \PHPUnit_Framework_TestCase
 class LoggerTest extends \PHPUnit_Framework_TestCase
 {
 {
-    public function testLogAll()
+    public function testLog()
     {
     {
-        $logger = new Logger();
-        $log1 = $this->getMock('Monolog\Log', array('log'), array('a'));
-        $log1->expects($this->once())
-            ->method('log');
-        $log2 = $this->getMock('Monolog\Log', array('log'), array('b'));
-        $log2->expects($this->once())
-            ->method('log');
-        $logger->addLog($log1);
-        $logger->addLog($log2);
-        $logger->warn('test');
+        $logger = new Logger(__METHOD__);
+
+        $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+        $handler->expects($this->once())
+            ->method('handle');
+        $logger->pushHandler($handler);
+
+        $logger->addWarning('test');
     }
     }
 
 
-    public function testLogFiltered()
+    /**
+     * @dataProvider logValues
+     */
+    public function testLogUntilHandled($bubble)
     {
     {
-        $logger = new Logger();
-        $log1 = $this->getMock('Monolog\Log', array('log'), array('a'));
-        $log1->expects($this->exactly(2))
-            ->method('log');
-        $log2 = $this->getMock('Monolog\Log', array('log'), array('b'));
-        $log2->expects($this->never())
-            ->method('log');
-        $logger->addLog($log1);
-        $logger->addLog($log2);
-
-        $logger->warn('test', 'a');
-        $logger->warn('test', array('a'));
+        $logger = new Logger(__METHOD__);
+
+        $bottomHandler = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+        $bottomHandler->expects($bubble ? $this->once() : $this->never())
+            ->method('handle');
+        $logger->pushHandler($bottomHandler);
+
+        $topHandler = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+        $topHandler->expects($this->once())
+            ->method('handle')
+            ->will($this->returnValue(!$bubble));
+        $logger->pushHandler($topHandler);
+
+        $logger->addWarning('test');
+    }
+
+    public function logValues()
+    {
+        return array(array(true), array(false));
+    }
+
+    public function testPushPopHandler()
+    {
+        $logger = new Logger(__METHOD__);
+        $handler1 = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+        $handler2 = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+        $handler3 = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+
+        $logger->pushHandler($handler1);
+        $logger->pushHandler($handler2);
+        $logger->pushHandler($handler3);
+
+        $this->assertEquals($handler3, $logger->popHandler());
+        $this->assertEquals($handler2, $logger->popHandler());
+        $this->assertEquals($handler1, $logger->popHandler());
     }
     }
 }
 }

+ 0 - 37
tests/Monolog/Writer/StreamWriterTest.php

@@ -1,37 +0,0 @@
-<?php
-
-/*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Monolog\Writer;
-
-use Monolog\Logger;
-
-class StreamWritterTest extends \PHPUnit_Framework_TestCase
-{
-    public function testWrite()
-    {
-        $handle = fopen('php://memory', 'a+');
-        $writer = new StreamWriter($handle);
-        $writer->write('log', array('level' => Logger::WARNING, 'message' => 'test'));
-        $writer->write('log', array('level' => Logger::WARNING, 'message' => 'test2'));
-        $writer->write('log', array('level' => Logger::WARNING, 'message' => 'test3'));
-        fseek($handle, 0);
-        $this->assertEquals('testtest2test3', fread($handle, 100));
-    }
-
-    public function testClose()
-    {
-        $handle = fopen('php://memory', 'a+');
-        $writer = new StreamWriter($handle);
-        $this->assertTrue(is_resource($handle));
-        $writer->close();
-        $this->assertFalse(is_resource($handle));
-    }
-}