Hibanapló Zend Framework alkalmazásokhoz
A korábbi cikkben megnéztük, hogyan lehet Zend Framework alkalmazásunkat rávenni hozzáférési napló írására. Ezúttal vegyük rá a rendszert a hibák naplózására!
A sorozatban megjelent
- Hozzáférési napló írása Zend Framework alkalmazásokhoz
- Hibanapló Zend Framework alkalmazásokhoz
Mit is kellene a naplóba írni? Rögzítsük
- az aktuális felhasználónk adatait,
- milyen IP-ről jött a kérés, ami hibához vezetett,
- az URL-t ahol a hiba keletkezett
- – természetesen kell időbélyeg is –,
- valamint maga a hiba.
- És a biztonság kedvéért a kérés lehető legtöbb adata.
A naplót most is SQLite adatbázisba írjuk. A hozzáférési naplóval ellentétben ezúttal csak egy naplót fogunk írni. A Zend Framework szállít már egy hibakezelő bővítményt (Zend_Controller_Plugin_ErrorHandler
). Mivel ennek a működését szeretnénk megtartani, származtatjuk azt, majd ezt a leszármazott osztályt fogjuk használni az alkalmazásunkban.
Zend_Loader::loadClass('Zend_Controller_Plugin_ErrorHandler');
/**
* ErrorHandler plugin
*
* @author 4image#dev, Vince Tikász
*/
class FI_Controller_Plugin_ErrorHandler extends Zend_Controller_Plugin_ErrorHandler {
/**
* @var Zend_Db_Adapter_Pdo_Sqlite
*/
protected $db;
/**
* @var String
*/
protected $dbName = 'error-log.sqlite3.db';
/**
* @var String
*/
protected $tblName = 'error_log';
protected function initLogDb() {
$logPath = APPLICATION_PATH . '/log/' . $this->dbName;
$this->db = new Zend_Db_Adapter_Pdo_Sqlite(array('dbname' => $logPath));
$desc = $this->db->describeTable($this->tblName);
if (count($desc) == 0) {
// Ha nem létezik, létrehozzok a táblát
$this->db->query('CREATE TABLE IF NOT EXISTS ' . $this->tblName . ' (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id char(32),
user_id INTEGER default NULL,
user_datas text,
ip_address varchar(45) default NULL,
request_uri text,
path text,
url text,
hostname varchar(100) default NULL,
timestamp timestamp NOT NULL default CURRENT_TIMESTAMP,
method varchar(45) default NULL,
request blob,
exceptions blob
)');
}
}
/**
* Filter sensitive params
* @param Array $array
* @retunr Array
*/
protected function filterParams($array = array()) {
$filtered = array();
foreach((array) $array as $k => $v) {
if (is_array($v)) {
$filtered[$k] = $this->filterParams($v);
} else {
switch($k) {
// soroljuk fel ide a jelszómezőknél használt neveket
case 'password':
case 'passwrd':
$filtered[$k] = '[keep-in-secret]';
break;
// itt pedig a jelszó megerősítő mezők neveit
case 'password-confirm':
$same = $v === $array['password'];
$filtered[$k] = '[keep-in-secret(' . ($same ? '' : 'not-') . 'match)]';
break;
}
}
}
return $filtered;
}
protected function writeLog() {
$this->intiLogDb();
$user_id = Zend_Auth::getInstance()->hasIdentity() ? Zend_Auth::getInstance()->getIdentity()->id : 0;
$this->db->insert($this->tblName, array(
'session_id' => Zend_Session::getId(),
'user_id' => $user_id,
'user_datas' => serialize(Zend_Auth::getInstance()->getIdentity()),
'ip_address' => $this->_request->getServer('REMOTE_ADDR'),
'request_uri' => $this->_request->getServer('REQUEST_URI'),
'path' =>
$this->_request->getModuleName() . '@' .
$this->_request->getControllerName() . '/' .
$this->_request->getActionName(),
'url' =>
$this->_request->getScheme() . '://' .
$this->_request->getHttpHost() .
$this->_request->getRequestUri(),
'hostname' => $this->_request->getServer('HTTP_HOST'),
'timestamp' => date('Y-m-d H:i:s'),
'method' => $this->_request->getMethod(),
'request' => serialize( array(
'SERVER' => $this->_request->getServer(),
'POST' => $this->filterParams($this->_request->getPost()),
'COOKIE' => $this->filterParams($this->_request->getCookie()),
'params' => $this->filterParams($this->_request->getParams()),
'request' => $this->filterParams($this->getRequest())
)),
'exceptions' => serialize($this->_response->getException()),
));
$this->db->closeConnection();
}
/**
* Post dispatch
* @param Zend_Controller_Request_Abstract $request
*/
public function postDispatch(Zend_Controller_Request_Abstract $request) {
parent::postDispatch();
// csak abban az esetben kell naplót írni, ha hiba van
if ( $this->getResponse()->isException() ) {
$this->writeLog();
}
}
A plugin betöltése most is a szokásos módon, a Bootstrap
osztályban történik.
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
protected function _initPlugins() {
Zend_Controller_Front::getInstance()
->registerPlugin(new FI_Controller_Plugin_ErrorHandler());
}
}
A hozzáférési napló írását a lehető legkorábban futtatjuk, a hibanaplóét pedig a lehető legkésőbb, ezért ezúttal a postDispatch()
pontra kerül. Első lépésben hagyjuk, hogy az ősosztály kezelje a hibát. Akit érdekel, megvizsgálhatja, pontosan hogyan is szedi össze, és adja át a hiba adatait az alkalmazásunk ErrorController
-jének. A beépített mechanizmus futását követően létrehozzuk a naplóként használt SQLite adatbázist és benne a szükséges táblát. Záró lépésként pedig elkészítjük a naplóbejegyzést.
Nagyjából ide lehet érdemes beépíteni egy e-mail küldést is, amiben értesíthetjük magunkat az új hibákról. Az ErrorController
-hez tartozó sablonban az aktuális felhasználó jogaittól függően akár meg is jeleníthetjük a hiba részleteit.
Az előző cikk hozzászólásai közt Tyrael felhívta a figyelmet az érzékeny adatok védelmének fontosságára. A legegyszerűbb megoldás, hogy a kérés azon részeit, amelyek érzékenynek minősülnek, egyszerűen helyettesítjük valamilyen szöveggel. Azért nem töröljük a tömbből, mert lehetséges, hogy a paraméter megléte vagy hiánya ugyanolyan fontos tényező a hiba kiváltásában. Jelen példában csak a jelszó, valamint a jelszó megerősítő mezőt szűröm meg, de a filterParams()
metódus tovább bővíthető saját igényeknek megfelelően. Nyílván lehetne fokozni a védelmet, illetve a szűrendő mezők megadása történhetne például konfigurációs állománnyal, de itt a cél az alapvető logika prezentálása.
A következő cikkben megmutatom, hogyan tudunk egy olyan felületet készíteni, amin keresztül a naplókat lehet vizsgálni.
■tiku I tikaszvince
Tikász Vince végzettsége szerint könyvtáros, a szemantikus éshozzáférhető web elkötelezett híve. 2005 óta hivatásszerűen
foglalkozik webes fejlesztéssel. Jelenleg a Full Market Zrt frontend fejlesztője, valamint a 4image Kft „bedolgozó” fejlesztője. Korábban a WebShop-Experts Kft. vezető fejlesztője.
Na most vagy az van, hogy
mea culpa
A kódban szereplő postDispatch függvény helyesen a következően néz ki
Javítottam a cikkben.
Kérdés: A lazy init miatt nem