Zend Framework a NoSQL pro ukládání logů ve webových aplikací, díl prvnío CouchDb

Použití databáze pro ukládání logů se používá často pro analýzu logů. Technicky napojit databázi RBMS (MySQL) pomocí Zend_Log_Writer_Db není žádný problém. Ale vidím tu nevýhodu v tom, že musíte mít schema podle toho co ukládáte za logy. Pokud se rozhodnete použít NOSQL databázi (CouchDb, MongoDb) nemusíte se o schema starat.

V tomto článku si napsat vlastní Zend_Log_Writer pro CouchDb a jak si potom lehce zobrazíme příslušné logy. Napíšeme si také jednoduchou map funkci pro view v CouchDb, kterou v aplikaci použijeme.

Nejprve si projedeme jak se používá Zend_Log_Writer_Db, syntaxi najdete v manuálu. Tabulka v db musí mít pevně dané schéma, která namapujete v konfiguraci writeru.

Implementaci vlastního App_Log_Writer_CouchDb provedeme třeba takto:

class App_Log_Writer_CouchDb extends Zend_Log_Writer_Abstract
{
    /**
     * Db
     * @var Phly_Couch
     */
    private $_db;
    /**
     * CouchDb host localhost default
     * @var string
     */
    private $_host;
    /**
     * Couchdb port 3184 default
     * @var int
     */
    private $_port;
    /**
     *
     * @param array $params
     * @return void
     */
    public function __construct($dbname, $options = null)
    {
        if (is_null($options)) {
            $options['host'] = "localhost";
            $options['port'] = "5984";
            $options['db'] = $dbname;
        }
        $this->_db = new Phly_Couch($options);
    }

    static public function factory($config)
    {
        $config = self::_parseConfig($config);
        return $config;
    }
    /**
     * @param array $event
     * @return void
     */
    protected function _write($event)
    {
        // action body
        $doc = new Phly_Couch_Document($event);
        $result = $this->_db->docSave($doc);
        // return array ok, id, rev
        $info = $result->getInfo();
        if (!$info['ok']) {
            throw new Zend_Log_Exception("Error in save to CouchDb");
        }
    }

}

Není na tom jak vidíte nic složitého, implementuje funkce _constructor, factory a write funkci a je to.

Samozřejmě je to kus Zend Frameworku, který potrebuje konfiguraci.

autoloaderNamespaces.phly = "Phly_"
autoloaderNamespaces.app = "App_"
; Options for CouchDb
couchdb.host = prskavec.couchone.com
couchdb.port = 80
couchdb.db = "test-log"

Pro správu CouchDb jsem použil vestavěný Futon.

Vytvořil jsem testovací db “test-log” a v ní pohled, který v kódu potom používám. Vytvoříte temporary_view, které potom při ukládání ložíte do příslušného designu a view. U mě to bylo použité logger/log_by_prior.

  function(doc) {
    if (doc.priority) {
       emit(doc.priority, [doc.priorityName, doc.timestamp, doc.message, doc.module, doc.controller]);
    }
  }

Toto view potom volá controller, který načítá data pro zobrazení dat z databáze.

class IndexController extends Zend_Controller_Action
{
    protected $_config;

    public function preDispatch()
    {
            $this->_config = new Zend_Config_Ini('../application/configs/'.
                    'application.ini', APPLICATION_ENV);
    }

    public function indexAction()
    {
         $db = new Phly_Couch($this->_config->couchdb);

         $this->view->form = $form = new App_Form_Filter();
         if ($this->getRequest()->isPost()) {
             $formData = $this->getRequest()->getPost();
             if ($form->isValid($formData)) {
                 $filterValue = $form->getValue('filter');
                 Zend_Debug::dump($filterValue);
                 if (empty($filterValue) && $filterValue===0) $filterValue = null;
                 $result = $db->view('logger','log_by_prior', $filterValue, array("db"=>$this->_config->couchdb->db));
             } else {
             $form->populate($formData);
             }
         } else {
            $result = $db->view('logger','log_by_prior', null, array("db"=>$this->_config->couchdb->db));
         }
         $this->view->docs = $result->toArray();
         $this->view->messages = $this->_helper->flashMessenger->getMessages();

         $logger = new Zend_Log();
         $r = new ReflectionClass($logger);
         $this->view->priorities = array_flip($r->getConstants());

    }

    public function logAction()
    {
          $id = $this->_request->getParam('id', 0);  

          $logger = new Zend_Log();
          $format = '%timestamp% %priorityName% (%priority%): '.
            '[%module%] [%controller%] %message%';
          $formatter = new Zend_Log_Formatter_Simple($format);

          $writer = new App_Log_Writer_CouchDb($this->_config->couchdb->db, $this->_config->couchdb);
          $writer->setFormatter($formatter);

          $logger->addWriter($writer);
          $logger->setEventItem('module', $this->getRequest()->getModuleName());
          $logger->setEventItem('controller', $this->getRequest()->getControllerName());
          $logger->log("Testovani chyba", $id);
          $this->_helper->flashMessenger->addMessage('Log item saved');
          $this->_helper->redirector('index');
    }

}

Controller obsahuje dvě akce a to vlastní zobrazení v indexAction a metodu, která záznamy vytváří logAction. V logAction je vidět výhoda neexistence schématu, protože jsem si přidal další informace bez potřeby měnit schéma databáze.

PHP a CouchDb

Použil jsem knihovnu Phly_Couch pro práci s CouchDb, jen jsem musel funkci lehce aktualizovat pro současnou verzi CouchDb 1.0.1, kterou jsem použil.

Aktualizovanou verzi Phly_CouchDb najdete v mém forku na githubu, celou ukázkovou aplikaci php_couchdb_logger také.

Pro přístup ke CouchDb můžete použít i jiné knihovny, například PHPillow, která vypadá celkem aktualizovaně. Já jsem použil knihovnu, kterou napsal Matthew Weier O’Phinney hlavně proto, že je psaná přímo pro Zend Framework. Má přehledný a dobře napsaný kód, kde jsem si upravil jen to co jsem se potřeboval.

CouchDb

Na Mac OS X si můžete nainstalovat CouchDb jako na linuxu ze zdrojáků nebo použít balík, který mi přijde ideální. Podrobné informace o instalaci jsou ve wiki, případně se podívejte tam.

Závěr

Myslím, že to je jednoduchý příklad jak například použít CouchDb, který se pro začátek práce s CouchDb může hodit. Pokud chcete dělat náročnějši vyhledavání na logy, je potřeba použit CouchDb Lucene. Pokud se chcete dozvědět více o CouchDb tak doporučuji jít na přednášku Karla Minaříka na letošním Webexpu.

V dalším pokračování si ukážeme jak by se to dělalo v MongoDb.