diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Logger/DoctrineMongoDBLogger.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Logger/DoctrineMongoDBLogger.php index 41b7e49e39..bd81261fd7 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Logger/DoctrineMongoDBLogger.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Logger/DoctrineMongoDBLogger.php @@ -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 */ 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; - } - - public function logQuery($query) - { - ++$this->nbQueries; - - if (null !== $this->logger) { - $this->logger->info(static::LOG_PREFIX.static::formatQuery($query)); - } - } - - public function getNbQueries() - { - return $this->nbQueries; - } - - public function getQueries() - { - if (null === $this->logger) { - return false; - } - - $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()))); + $this->prefix = $prefix; + $this->queries = array(); + $this->processed = false; } /** - * Formats the supplied query array recursively. + * Logs a query. * - * @param array $query All or part of a query array + * This method is configured as the logger callable in the service + * container. * - * @return string A serialized object for the log + * @param array $query A query log array from Doctrine */ - static protected function formatQuery(array $query, $array = true) + public function logQuery($query) + { + $this->queries[] = $query; + $this->processed = false; + + if (null !== $this->logger) { + $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() + { + 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 (!$this->processed) { + $this->processQueries(); + } + + return $this->formattedQueries; + } + + /** + * Groups and formats query arrays. + * + * @param array $queries An array of query arrays + * + * @return array An array of human-readable queries + */ + 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; }