2
0
Эх сурвалжийг харах

Merge pull request #724 from billybanfield/ISSUE-695

Support ext-mongodb in MongoDBHandler
Jordi Boggiano 10 жил өмнө
parent
commit
5d6c2048c8

+ 2 - 2
composer.json

@@ -35,8 +35,8 @@
         "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
         "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
         "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-        "ext-mongo": "Allow sending log messages to a MongoDB server",
-        "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+        "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+        "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
         "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
         "rollbar/rollbar": "Allow sending log messages to Rollbar",
         "php-console/php-console": "Allow sending log messages to Google Chrome"

+ 14 - 5
src/Monolog/Formatter/MongoDBFormatter.php

@@ -11,6 +11,8 @@
 
 namespace Monolog\Formatter;
 
+use MongoDB\BSON\UTCDateTime;
+
 /**
  * Formats a record for use with the MongoDBHandler.
  *
@@ -55,9 +57,9 @@ class MongoDBFormatter implements FormatterInterface
     {
         if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
             foreach ($record as $name => $value) {
-                if ($value instanceof \DateTime) {
+                if ($value instanceof \DateTimeInterface) {
                     $record[$name] = $this->formatDate($value, $nestingLevel + 1);
-                } elseif ($value instanceof \Exception) {
+                } elseif ($value instanceof \Throwable) {
                     $record[$name] = $this->formatException($value, $nestingLevel + 1);
                 } elseif (is_array($value)) {
                     $record[$name] = $this->formatArray($value, $nestingLevel + 1);
@@ -80,7 +82,7 @@ class MongoDBFormatter implements FormatterInterface
         return $this->formatArray($objectVars, $nestingLevel);
     }
 
-    protected function formatException(\Exception $exception, $nestingLevel)
+    protected function formatException(\Throwable $exception, $nestingLevel)
     {
         $formattedException = array(
             'class' => get_class($exception),
@@ -98,8 +100,15 @@ class MongoDBFormatter implements FormatterInterface
         return $this->formatArray($formattedException, $nestingLevel);
     }
 
-    protected function formatDate(\DateTime $value, $nestingLevel)
+    protected function formatDate(\DateTimeInterface $value, $nestingLevel)
     {
-        return new \MongoDate($value->getTimestamp());
+        $seconds = (int) $value->format('U');
+        $milliseconds = (int) $value->format('u') / 1000;
+
+        if ($seconds < 0) {
+            return new UTCDateTime($seconds * 1000 - $milliseconds);
+        } else {
+            return new UTCDateTime($seconds * 1000 + $milliseconds);
+        }
     }
 }

+ 39 - 14
src/Monolog/Handler/MongoDBHandler.php

@@ -11,41 +11,66 @@
 
 namespace Monolog\Handler;
 
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Manager;
+use MongoDB\Client;
 use Monolog\Logger;
 use Monolog\Formatter\NormalizerFormatter;
 
 /**
  * Logs to a MongoDB database.
  *
- * usage example:
+ * Usage example:
  *
- *   $log = new Logger('application');
- *   $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod");
+ *   $log = new \Monolog\Logger('application');
+ *   $client = new \MongoDB\Client('mongodb://localhost:27017');
+ *   $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod');
  *   $log->pushHandler($mongodb);
  *
- * @author Thomas Tourlourat <thomas@tourlourat.com>
+ * The above examples uses the MongoDB PHP library's client class; however, the
+ * MongoDB\Driver\Manager class from ext-mongodb is also supported.
  */
 class MongoDBHandler extends AbstractProcessingHandler
 {
-    protected $mongoCollection;
+    private $collection;
+    private $manager;
+    private $namespace;
 
-    public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
+    /**
+     * Constructor.
+     *
+     * @param Client|Manager $mongodb    MongoDB library or driver client
+     * @param string         $database   Database name
+     * @param string         $collection Collection name
+     * @param int            $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
+     */
+    public function __construct($mongodb, $database, $collection, $level = Logger::DEBUG, $bubble = true)
     {
-        if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) {
-            throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required');
+        if (!($mongodb instanceof Client || $mongodb instanceof Manager)) {
+            throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required');
         }
 
-        $this->mongoCollection = $mongo->selectCollection($database, $collection);
+        if ($mongodb instanceof Client) {
+            $this->collection = $mongodb->selectCollection($database, $collection);
+        } else {
+            $this->manager = $mongodb;
+            $this->namespace = $database . '.' . $collection;
+        }
 
         parent::__construct($level, $bubble);
     }
 
     protected function write(array $record)
     {
-        if ($this->mongoCollection instanceof \MongoDB\Collection) {
-            $this->mongoCollection->insertOne($record["formatted"]);
-        } else {
-            $this->mongoCollection->save($record["formatted"]);
+        if (isset($this->collection)) {
+            $this->collection->insertOne($record['formatted']);
+        }
+
+        if (isset($this->manager, $this->namespace)) {
+            $bulk = new BulkWrite;
+            $bulk->insert($record["formatted"]);
+            $this->manager->executeBulkWrite($this->namespace, $bulk);
         }
     }
 
@@ -54,6 +79,6 @@ class MongoDBHandler extends AbstractProcessingHandler
      */
     protected function getDefaultFormatter()
     {
-        return new NormalizerFormatter();
+        return new NormalizerFormatter;
     }
 }

+ 14 - 13
tests/Monolog/Formatter/MongoDBFormatterTest.php

@@ -20,8 +20,8 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
 {
     public function setUp()
     {
-        if (!class_exists('MongoDate')) {
-            $this->markTestSkipped('mongo extension not installed');
+        if (!class_exists('MongoDB\BSON\UTCDateTime')) {
+            $this->markTestSkipped('ext-mongodb not installed');
         }
     }
 
@@ -62,7 +62,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
             'level' => Logger::WARNING,
             'level_name' => Logger::getLevelName(Logger::WARNING),
             'channel' => 'test',
-            'datetime' => new \DateTime('2014-02-01 00:00:00'),
+            'datetime' => new \DateTime('2016-01-21T21:11:30.123456+00:00'),
             'extra' => array(),
         );
 
@@ -75,8 +75,8 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals(Logger::WARNING, $formattedRecord['level']);
         $this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']);
         $this->assertEquals('test', $formattedRecord['channel']);
-        $this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']);
-        $this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString());
+        $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $formattedRecord['datetime']);
+        $this->assertEquals('1453410690123', $formattedRecord['datetime']->__toString());
         $this->assertEquals(array(), $formattedRecord['extra']);
     }
 
@@ -89,7 +89,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
         $record = array(
             'message' => 'some log message',
             'context' => array(
-                'stuff' => new \DateTime('2014-02-01 02:31:33'),
+                'stuff' => new \DateTime('1969-01-21T21:11:30.123456+00:00'),
                 'some_object' => $someObject,
                 'context_string' => 'some string',
                 'context_int' => 123456,
@@ -98,7 +98,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
             'level' => Logger::WARNING,
             'level_name' => Logger::getLevelName(Logger::WARNING),
             'channel' => 'test',
-            'datetime' => new \DateTime('2014-02-01 00:00:00'),
+            'datetime' => new \DateTime('2016-01-21T21:11:30.123456+00:00'),
             'extra' => array(),
         );
 
@@ -106,8 +106,9 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
         $formattedRecord = $formatter->format($record);
 
         $this->assertCount(5, $formattedRecord['context']);
-        $this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']);
-        $this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString());
+        $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $formattedRecord['context']['stuff']);
+        $this->assertEquals('-29731710123', $formattedRecord['context']['stuff']->__toString());
+
         $this->assertEquals(
             array(
                 'foo' => 'something',
@@ -144,7 +145,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
             'level' => Logger::WARNING,
             'level_name' => Logger::getLevelName(Logger::WARNING),
             'channel' => 'test',
-            'datetime' => new \DateTime('2014-02-01 00:00:00'),
+            'datetime' => new \DateTime('2016-01-21T21:11:30.123456+00:00'),
             'extra' => array(),
         );
 
@@ -180,7 +181,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
             'level' => Logger::WARNING,
             'level_name' => Logger::getLevelName(Logger::WARNING),
             'channel' => 'test',
-            'datetime' => new \DateTime('2014-02-01 00:00:00'),
+            'datetime' => new \DateTime('2016-01-21T21:11:30.123456+00:00'),
             'extra' => array(),
         );
 
@@ -219,7 +220,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
             'level' => Logger::WARNING,
             'level_name' => Logger::getLevelName(Logger::WARNING),
             'channel' => 'test',
-            'datetime' => new \DateTime('2014-02-01 00:00:00'),
+            'datetime' => new \DateTime('2016-01-21T21:11:30.123456+00:00'),
             'extra' => array(),
         );
 
@@ -248,7 +249,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
             'level' => Logger::WARNING,
             'level_name' => Logger::getLevelName(Logger::WARNING),
             'channel' => 'test',
-            'datetime' => new \DateTime('2014-02-01 00:00:00'),
+            'datetime' => new \DateTime('2016-01-21T21:11:30.123456+00:00'),
             'extra' => array(),
         );
 

+ 38 - 25
tests/Monolog/Handler/MongoDBHandlerTest.php

@@ -11,8 +11,9 @@
 
 namespace Monolog\Handler;
 
+use MongoDB\Driver\Manager;
 use Monolog\TestCase;
-use Monolog\Logger;
+use Monolog\Formatter\NormalizerFormatter;
 
 class MongoDBHandlerTest extends TestCase
 {
@@ -21,45 +22,57 @@ class MongoDBHandlerTest extends TestCase
      */
     public function testConstructorShouldThrowExceptionForInvalidMongo()
     {
-        new MongoDBHandler(new \stdClass(), 'DB', 'Collection');
+        new MongoDBHandler(new \stdClass, 'db', 'collection');
     }
 
-    public function testHandle()
+    public function testHandleWithLibraryClient()
     {
-        $mongo = $this->getMock('Mongo', array('selectCollection'), array(), '', false);
-        $collection = $this->getMock('stdClass', array('save'));
+        if (!(class_exists('MongoDB\Client'))) {
+            $this->markTestSkipped('mongodb/mongodb not installed');
+        }
+
+        $mongodb = $this->getMockBuilder('MongoDB\Client')
+            ->disableOriginalConstructor()
+            ->getMock();
 
-        $mongo->expects($this->once())
+        $collection = $this->getMockBuilder('MongoDB\Collection')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $mongodb->expects($this->once())
             ->method('selectCollection')
-            ->with('DB', 'Collection')
+            ->with('db', 'collection')
             ->will($this->returnValue($collection));
 
-        $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
-
-        $expected = array(
-            'message' => 'test',
-            'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34),
-            'level' => Logger::WARNING,
-            'level_name' => 'WARNING',
-            'channel' => 'test',
-            'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
-            'extra' => array(),
-        );
+        $record = $this->getRecord();
+        $expected = $record;
+        $expected['datetime'] = $record['datetime']->format(NormalizerFormatter::SIMPLE_DATE);
 
         $collection->expects($this->once())
-            ->method('save')
+            ->method('insertOne')
             ->with($expected);
 
-        $handler = new MongoDBHandler($mongo, 'DB', 'Collection');
+        $handler = new MongoDBHandler($mongodb, 'db', 'collection');
         $handler->handle($record);
     }
-}
 
-if (!class_exists('Mongo')) {
-    class Mongo
+    public function testHandleWithDriverManager()
     {
-        public function selectCollection()
-        {
+        if (!(class_exists('MongoDB\Driver\Manager'))) {
+            $this->markTestSkipped('ext-mongodb not installed');
+        }
+
+        /* This can become a unit test once ManagerInterface can be mocked.
+         * See: https://jira.mongodb.org/browse/PHPC-378
+         */
+        $mongodb = new Manager('mongodb://localhost:27017');
+        $handler = new MongoDBHandler($mongodb, 'test', 'monolog');
+        $record = $this->getRecord();
+
+        try {
+            $handler->handle($record);
+        } catch (\RuntimeException $e) {
+            $this->markTestSkipped('Could not connect to MongoDB server on mongodb://localhost:27017');
         }
     }
 }