<?php
/**
 * @category Zend
 * @package Zend_Log
 * @subpackage Writer
 */
/**
 * @see Zend_Log_Writer_Abstract
 */
require_once ('Zend/Log/Writer/Abstract.php');
/**
 * <b>Example 1:</b>
 *
 * <code>
 * $logger = Zend_Log::factory(
 *     array('timestampFormat' => 'Y-m-d',
 *     array('writerName' => 'MongoDb',
 *     'writerParams' => array(
 *         'server' => 'mongodb://somehost.mongolab.com:27017',
 *         'collection' => 'logging', 'database' => 'zend_log',
 *         'options' => array('username' => 'zircote-dev',
 *             'password' => 'somepassword', 'connect' => true, 'timeout' => 200,
 *             'replicaSet' => 'repset1', 'db' => 'zend_log'
 *         )
 *     ),
 *     'formatterName' => 'Simple',
 *     'formatterParams' => array(
 *         'format' => '%timestamp%: %message% -- %info%'),
 *         'filterName' => 'Priority',
 *         'filterParams' => array('priority' => Zend_Log::WARN)),
 *         array('writerName' => 'Firebug', 'filterName' => 'Priority',
 *             'filterParams' => array('priority' => Zend_Log::INFO))
 *     )
 * );
 * $logger->crit(__METHOD__);
 * </code>
 *
 * <b>Example 2:</b>
 *
 * <code>
 * $config = array('server' => 'mongodb://somehost.mongolab.com:27017',
 *     'collection' => 'logging', 'database' => 'zend_log',
 *     'options' => array('username' => 'zircote-dev',
 *     'password' => 'somepassword', 'connect' => true, 'timeout' => 200,
 *     'replicaSet' => 'repset1', 'db' => 'zend_log')
 * );
 * $log = new Zend_log();
 * $log->addWriter(Zend_Log_Writer_MongoDb::factory($config));
 * $log->info('this is a test ' . __METHOD__);
 * </code>
 *
 * <b>Example 3:</b>
 *
 * <code>
 * $config = array('collection' => 'log','database' => 'pincrowd');
 * $writer = Zend_Log_Writer_MongoDb::factory($config);
 * $log = new Zend_log();
 * $log->addWriter($writer);
 * $log->info('this is a test');
 * </code>
 *
 * <b>Example 4:</b>
 *
 * <code>
 * $mongo = new MongoDb();
 * $collection = $mongo->selectDB('logging')
 *     ->selectCollection('logCollection');
 * $log = new Zend_log();
 * $writer = new Zend_Log_Writer_MongoDb($collection);
 * $log->addWriter($writer);
 * $log->err(__METHOD__);
 * </code>
 *
 * <b>Reading Logs from a Tailable Cursor:</b>
 * $mongo = new Mongo();
 * $db = $mongo->selectDB('logging');
 * $collection = $db->selectCollection('logCollection');
 * $cursor = $collection->find()->tailable(true);
 * while (true) {
 *     if ($cursor->hasNext()) {
 *         $doc = $cursor->getNext();
 *         echo date(DATE_ISO8601, $doc['timestamp']->sec), ' ',$doc['priorityName'],' ', $doc['message'], PHP_EOL;
 *     } else {
 *         usleep(100);
 *     }
 * }
 * </code>
 *
 * <bZend_Application_Resource_Log</b>
 *
 * <code>
 * ;;; application.ini ;;;
 * resources.log.mongo.writerName = "MongoDb"
 * resources.log.mongo.writerParams.database = "pincrowd"
 * resources.log.mongo.writerParams.collection = "logging"
 * resources.log.mongo.writerParams.documentMap.timestamp = 'timestamp'
 * resources.log.mongo.writerParams.documentMap.message = 'message'
 * resources.log.mongo.writerParams.documentMap.priority = 'priority'
 * resources.log.mongo.writerParams.documentMap.priorityName = 'priorityName'
 * resources.log.mongo.writerParams.documentMap.hostname = 'hostname'
 * resources.log.mongo.filterName = "Priority"
 * resources.log.mongo.filterParams.priority = 5
 *
 * <?php
 * if($bootstrap->hasResource('log')){
 *     $log = $bootstrap->getResource('log');
 *     $log->info('log me');
 * }
 * </code>
 * @category Zend
 * @package Zend_Log
 * @subpackage Writer
 *
 */
class Core_Log_Writer_MongoDb extends Zend_Log_Writer_Abstract
{
    /**
     *
     *
     * @var MongoCollection
     */
    protected $_collection;
    /**
     * Defines the mapping of data to the collection members.
     *
     * <b>Zend_Config_Ini Example:</b>
     *
     * <code>
     * resources.log.mongo.writerParams.documentMap.timestamp = 'timestamp'
     * resources.log.mongo.writerParams.documentMap.message = 'message'
     * resources.log.mongo.writerParams.documentMap.priority = 'priority'
     * resources.log.mongo.writerParams.documentMap.priorityName = 'priorityName'
     * resources.log.mongo.writerParams.documentMap.hostname = 'hostname'
     * </code>
     * @var array
     */
    protected $_documentMap = array(
        'timestamp' => 'timestamp',
        'message' => 'message',
        'priority' => 'priority',
        'priorityName' => 'priorityName',
        'trace' => 'trace',
        'hostname' => 'hostname'
    );
    /**
     * Originating hostname of the log entry.
     *
     * @var string
     */
    protected $_hostname;
    /**
     *
     *
     * @param $collection
     * @param array $documentMap
     */
    public function __construct($collection, $documentMap = null)
    {
        if (!extension_loaded('Mongo')) {
            Zend_Cache::throwException("Cannot use Mongo storage because the ".
            "'Mongo' extension is not loaded in the current PHP environment");
        }
        if(!is_array($documentMap)){
            $documentMap = array();
        }

        if ($collection instanceof MongoCollection) {
            $this->_collection = $collection;
        } if ($collection instanceof Mongo_Abstract) {
            $this->_collection = $collection->getCollection();
        } else {
            $class = get_class($collection);
            throw new Zend_Log_Exception("Argument 1 passed to Zend_Log_Writer_MongoDb::__construct() must be an instance of MongoCollection or Mongo_Abstract, instance of $class given");
        }

        $this->_documentMap = array_merge($this->_documentMap, $documentMap);
        $this->_setHostname();
    }
    /**
     * (non-PHPdoc)
     * @see Zend_Log_Writer_Abstract::_write()
     */
    protected function _write ($event)
    {
        $event['hostname'] = $this->_hostname;
        if ($this->_collection === null) {
            require_once 'Zend/Log/Exception.php';
            throw new Zend_Log_Exception('MongoDb object is null');
        }
        $event['created'] = new MongoDate(strtotime($event['timestamp']));

        // Remap Exception object to array and remap params
        if ($event['message'] instanceof Exception) {
            $eventObject        = $event['message'];
            $event['message']   = $eventObject->getMessage();
            $event['trace']     = $eventObject->getTrace();
            $event['file']      = $eventObject->getFile();
            $event['line']      = $eventObject->getLine();
    
            if ($eventObject instanceof Core_Exception_Interface) {
                $event['type'] = $eventObject->getType();
                foreach ($eventObject->getParams() as $key => $param) {
                    $event[$key] = $param;
                }
            }
        } else {
            // Make sure message is writeable to Mongo
            $event['message'] = json_decode(json_encode($event['message']), true);
        }

        // Make trace array writeable to mongo
        if (isset($event['trace'])) {
            foreach ($event['trace'] as &$traceElement) {
                $traceElement['args'] = json_decode(json_encode($traceElement['args']), true);
            }
        }

        try {
            $this->_collection->insert($event);
        } catch (Exception $e) {
            throw new ErrorException('Cannot save exception data to Mongo: ' . $e->getMessage());
        }

        // Instead of document mapping, save everything we get into mongo collection
        /*
        if ($this->_documentMap === null) {
            $dataToInsert = $event;
        } else {
            $dataToInsert = array();
            foreach ($this->_documentMap as $columnName => $fieldKey) {
                if (isset($event[$fieldKey])) {
                    $dataToInsert[$columnName] = $event[$fieldKey];
                }
            }
        }
        $this->_collection->insert($event);
        */
    }
    /**
     *
     * @param array $config
     * @return Zend_Log_Writer_MongoDb
     */
    static public function factory ($config)
    {
        $config = self::_parseConfig($config);

        if (isset($config['columnmap'])) {
            $config['columnMap'] = $config['columnmap'];
        }
        $config = array_merge(
            array('collection'  => null,
            'documentMap' => null),
            $config
        );
        if (isset($config['documentmap'])) {
            $config['documentMap'] = $config['documentmap'];
        }
        if(!$config['collection'] instanceof MongoCollection){
            $config['collection'] = self::_createMongoCollection($config);
        }
        return new self(
            $config['collection'],
            $config['documentMap']
        );
    }
    /**
     * Create the MongoCollection Object.
     *
     * @param array $config
     * @return MongoCollection
     */
    static protected function _createMongoCollection($config)
    {
        if(!isset($config['server'])){
            $server  = "mongodb://localhost:27017";
        } else {
            $server = $config['server'];
        }
        if(!isset($config['options']) || !is_array($config['options'])){
            $options = array();
        } else {
            $options = $config['options'];
        }
        $mongo = new MongoDb($server, $options);
        return $mongo->selectDB($config['database'])
            ->selectCollection($config['collection']);
    }
    /**
     * Determine the hostname of the server executing the code. This allows for
     * demarcation of entries in a cluster.
     */
    protected function _setHostname()
    {
        if(!$this->_hostname){
            $this->_hostname = php_uname('n');
        }
    }
}