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.
