|
@@ -12,72 +12,212 @@
|
|
|
namespace Symfony\Bundle\DoctrineMongoDBBundle\Logger;
|
|
|
|
|
|
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
|
|
-use Symfony\Component\Yaml\Yaml;
|
|
|
|
|
|
/**
|
|
|
* Logger for the Doctrine MongoDB ODM.
|
|
|
*
|
|
|
+ * The {@link logQuery()} method is configured as the logger callable in the
|
|
|
+ * service container.
|
|
|
+ *
|
|
|
* @author Kris Wallsmith <kris.wallsmith@symfony.com>
|
|
|
*/
|
|
|
class DoctrineMongoDBLogger
|
|
|
{
|
|
|
- const LOG_PREFIX = 'MongoDB query: ';
|
|
|
-
|
|
|
protected $logger;
|
|
|
- protected $nbQueries;
|
|
|
|
|
|
- public function __construct(LoggerInterface $logger = null)
|
|
|
+ protected $prefix;
|
|
|
+ protected $queries;
|
|
|
+
|
|
|
+ protected $processed;
|
|
|
+ protected $formattedQueries;
|
|
|
+ protected $nbRealQueries;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Constructor.
|
|
|
+ *
|
|
|
+ * @param LoggerInterface $logger The Symfony logger
|
|
|
+ * @param string $prefix A prefix for messages sent to the Symfony logger
|
|
|
+ */
|
|
|
+ public function __construct(LoggerInterface $logger = null, $prefix = 'MongoDB query: ')
|
|
|
{
|
|
|
$this->logger = $logger;
|
|
|
- $this->nbQueries = 0;
|
|
|
+ $this->prefix = $prefix;
|
|
|
+ $this->queries = array();
|
|
|
+ $this->processed = false;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Logs a query.
|
|
|
+ *
|
|
|
+ * This method is configured as the logger callable in the service
|
|
|
+ * container.
|
|
|
+ *
|
|
|
+ * @param array $query A query log array from Doctrine
|
|
|
+ */
|
|
|
public function logQuery($query)
|
|
|
{
|
|
|
- ++$this->nbQueries;
|
|
|
+ $this->queries[] = $query;
|
|
|
+ $this->processed = false;
|
|
|
|
|
|
if (null !== $this->logger) {
|
|
|
- $this->logger->info(static::LOG_PREFIX.static::formatQuery($query));
|
|
|
+ $this->logger->info($this->prefix.static::bsonEncode($query));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns the number of queries that have been logged.
|
|
|
+ *
|
|
|
+ * @return integer The number of queries logged
|
|
|
+ */
|
|
|
public function getNbQueries()
|
|
|
{
|
|
|
- return $this->nbQueries;
|
|
|
+ if (!$this->processed) {
|
|
|
+ $this->processQueries();
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->nbRealQueries;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns a human-readable array of queries logged.
|
|
|
+ *
|
|
|
+ * @return array An array of queries
|
|
|
+ */
|
|
|
public function getQueries()
|
|
|
{
|
|
|
- if (null === $this->logger) {
|
|
|
- return false;
|
|
|
+ if (!$this->processed) {
|
|
|
+ $this->processQueries();
|
|
|
}
|
|
|
|
|
|
- $logger = $this->logger->getDebugLogger();
|
|
|
-
|
|
|
- if (!$logger) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- $offset = strlen(static::LOG_PREFIX);
|
|
|
- $mapper = function($log) use($offset)
|
|
|
- {
|
|
|
- if (0 === strpos($log['message'], DoctrineMongoDBLogger::LOG_PREFIX)) {
|
|
|
- return substr($log['message'], $offset);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // map queries from logs, remove empty entries and re-index the array
|
|
|
- return array_values(array_filter(array_map($mapper, $logger->getLogs())));
|
|
|
+ return $this->formattedQueries;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Formats the supplied query array recursively.
|
|
|
+ * Groups and formats query arrays.
|
|
|
*
|
|
|
- * @param array $query All or part of a query array
|
|
|
+ * @param array $queries An array of query arrays
|
|
|
*
|
|
|
- * @return string A serialized object for the log
|
|
|
+ * @return array An array of human-readable queries
|
|
|
*/
|
|
|
- static protected function formatQuery(array $query, $array = true)
|
|
|
+ protected function processQueries()
|
|
|
+ {
|
|
|
+ $this->formattedQueries = array();
|
|
|
+ $this->nbRealQueries = 0;
|
|
|
+
|
|
|
+ $grouped = array();
|
|
|
+ $ordered = array();
|
|
|
+ foreach ($this->queries as $query) {
|
|
|
+ $cursor = serialize($query['query']).serialize($query['fields']);
|
|
|
+
|
|
|
+ // append if issued from cursor (currently just "sort")
|
|
|
+ if (isset($query['sort'])) {
|
|
|
+ unset($query['query'], $query['fields']);
|
|
|
+ $grouped[$cursor][count($grouped[$cursor]) - 1][] = $query;
|
|
|
+ } else {
|
|
|
+ $grouped[$cursor][] = array($query);
|
|
|
+ $ordered[] =& $grouped[$cursor][count($grouped[$cursor]) - 1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $i = 0;
|
|
|
+ $db = '';
|
|
|
+ $query = '';
|
|
|
+ foreach ($ordered as $logs) {
|
|
|
+ foreach ($logs as $log) {
|
|
|
+ if (isset($log['db']) && $db != $log['db']) {
|
|
|
+ // for readability
|
|
|
+ $this->formattedQueries[$i++] = 'use '.$log['db'].';';
|
|
|
+ $db = $log['db'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($log['collection'])) {
|
|
|
+ // flush the previous and start a new query
|
|
|
+ if (!empty($query)) {
|
|
|
+ if ('.' == $query[0]) {
|
|
|
+ $query = 'db'.$query;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->formattedQueries[$i++] = $query.';';
|
|
|
+ ++$this->nbRealQueries;
|
|
|
+ }
|
|
|
+
|
|
|
+ $query = 'db.'.$log['collection'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // format the method call
|
|
|
+ if (isset($log['authenticate'])) {
|
|
|
+ $query .= '.authenticate()';
|
|
|
+ } elseif (isset($log['batchInsert'])) {
|
|
|
+ $query .= '.batchInsert(**'.$log['num'].' item(s)**)';
|
|
|
+ } elseif (isset($log['command'])) {
|
|
|
+ $query .= '.command()';
|
|
|
+ } elseif (isset($log['count'])) {
|
|
|
+ $query .= '.count(';
|
|
|
+ if ($log['query'] || $log['limit'] || $log['skip']) {
|
|
|
+ $query .= static::bsonEncode($log['query']);
|
|
|
+ if ($log['limit'] || $log['skip']) {
|
|
|
+ $query .= ', '.static::bsonEncode($log['limit']);
|
|
|
+ if ($log['skip']) {
|
|
|
+ $query .= ', '.static::bsonEncode($log['skip']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $query .= ')';
|
|
|
+ } elseif (isset($log['createCollection'])) {
|
|
|
+ $query .= '.createCollection()';
|
|
|
+ } elseif (isset($log['createDBRef'])) {
|
|
|
+ $query .= '.createDBRef()';
|
|
|
+ } elseif (isset($log['deleteIndex'])) {
|
|
|
+ $query .= '.dropIndex('.static::bsonEncode($log['keys']).')';
|
|
|
+ } elseif (isset($log['deleteIndexes'])) {
|
|
|
+ $query .= '.dropIndexes()';
|
|
|
+ } elseif (isset($log['drop'])) {
|
|
|
+ $query .= '.drop()';
|
|
|
+ } elseif (isset($log['dropDatabase'])) {
|
|
|
+ $query .= '.dropDatabase()';
|
|
|
+ } elseif (isset($log['ensureIndex'])) {
|
|
|
+ $query .= '.ensureIndex('.static::bsonEncode($log['keys']).', '.static::bsonEncode($log['options']).')';
|
|
|
+ } elseif (isset($log['execute'])) {
|
|
|
+ $query .= '.execute()';
|
|
|
+ } elseif (isset($log['find'])) {
|
|
|
+ $query .= '.find('.static::bsonEncode($log['query']);
|
|
|
+ if (!empty($log['fields'])) {
|
|
|
+ $query .= ', '.static::bsonEncode($log['fields']);
|
|
|
+ }
|
|
|
+ $query .= ')';
|
|
|
+ } elseif (isset($log['findOne'])) {
|
|
|
+ $query .= '.findOne('.static::bsonEncode($log['query']);
|
|
|
+ if (!empty($log['fields'])) {
|
|
|
+ $query .= ', '.static::bsonEncode($log['fields']);
|
|
|
+ }
|
|
|
+ $query .= ')';
|
|
|
+ } elseif (isset($log['getDBRef'])) {
|
|
|
+ $query .= '.getDBRef()';
|
|
|
+ } elseif (isset($log['group'])) {
|
|
|
+ $query .= '.group('.static::bsonEncode(array(
|
|
|
+ 'keys' => $log['keys'],
|
|
|
+ 'initial' => $log['initial'],
|
|
|
+ 'reduce' => $log['reduce'],
|
|
|
+ )).')';
|
|
|
+ } elseif (isset($log['insert'])) {
|
|
|
+ $query .= '.insert('.static::bsonEncode($log['document']).')';
|
|
|
+ } elseif (isset($log['remove'])) {
|
|
|
+ $query .= '.remove('.static::bsonEncode($log['query']).')';
|
|
|
+ } elseif (isset($log['save'])) {
|
|
|
+ $query .= '.save('.static::bsonEncode($log['document']).')';
|
|
|
+ } elseif (isset($log['sort'])) {
|
|
|
+ $query .= '.sort('.static::bsonEncode($log['sortFields']).')';
|
|
|
+ } elseif (isset($log['update'])) {
|
|
|
+ // todo: include $log['options']
|
|
|
+ $query .= '.update('.static::bsonEncode($log['query']).', '.static::bsonEncode($log['newObj']).')';
|
|
|
+ } elseif (isset($log['validate'])) {
|
|
|
+ $query .= '.validate()';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static protected function bsonEncode($query, $array = true)
|
|
|
{
|
|
|
$parts = array();
|
|
|
|
|
@@ -91,7 +231,7 @@ class DoctrineMongoDBLogger
|
|
|
} elseif (is_scalar($value)) {
|
|
|
$formatted = '"'.$value.'"';
|
|
|
} elseif (is_array($value)) {
|
|
|
- $formatted = static::formatQuery($value);
|
|
|
+ $formatted = static::bsonEncode($value);
|
|
|
} elseif ($value instanceof \MongoId) {
|
|
|
$formatted = 'ObjectId("'.$value.'")';
|
|
|
} elseif ($value instanceof \MongoDate) {
|
|
@@ -107,7 +247,7 @@ class DoctrineMongoDBLogger
|
|
|
} elseif ($value instanceof \MongoBinData) {
|
|
|
$formatted = 'new BinData("'.$value->bin.'", "'.$value->type.'")';
|
|
|
} elseif ($value instanceof \stdClass) {
|
|
|
- $formatted = static::formatQuery((array) $value, false);
|
|
|
+ $formatted = static::bsonEncode((array) $value);
|
|
|
} else {
|
|
|
$formatted = (string) $value;
|
|
|
}
|