ugrás a tartalomhoz

Hibanapló Zend Framework alkalmazásokhoz

tiku I tikaszvince · 2011. Jan. 31. (H), 16.46
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

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.

  1. Zend_Loader::loadClass('Zend_Controller_Plugin_ErrorHandler');  
  2.   
  3. /** 
  4. * ErrorHandler plugin 
  5. * 
  6. * @author 4image#dev, Vince Tikász 
  7. */  
  8. class FI_Controller_Plugin_ErrorHandler extends Zend_Controller_Plugin_ErrorHandler {  
  9. /** 
  10. * @var Zend_Db_Adapter_Pdo_Sqlite 
  11. */  
  12. protected $db;  
  13.   
  14. /** 
  15. * @var String 
  16. */  
  17. protected $dbName = 'error-log.sqlite3.db';  
  18.   
  19. /** 
  20. * @var String 
  21. */  
  22. protected $tblName = 'error_log';  
  23.   
  24. protected function initLogDb() {  
  25. $logPath  = APPLICATION_PATH . '/log/' . $this->dbName;  
  26. $this->db = new Zend_Db_Adapter_Pdo_Sqlite(array('dbname' => $logPath));  
  27. $desc     = $this->db->describeTable($this->tblName);  
  28.   
  29. if (count($desc) == 0) {  
  30.     // Ha nem létezik, létrehozzok a táblát  
  31.     $this->db->query('CREATE TABLE IF NOT EXISTS ' . $this->tblName . ' (  
  32.         id INTEGER PRIMARY KEY AUTOINCREMENT,  
  33.         session_id char(32),  
  34.         user_id INTEGER default NULL,  
  35.         user_datas text,  
  36.         ip_address varchar(45) default NULL,  
  37.         request_uri text,  
  38.         path text,  
  39.         url text,  
  40.         hostname varchar(100) default NULL,  
  41.         timestamp timestamp NOT NULL default CURRENT_TIMESTAMP,  
  42.         method varchar(45) default NULL,  
  43.         request blob,  
  44.         exceptions blob  
  45.     )');  
  46. }  
  47. }  
  48.   
  49. /** 
  50. * Filter sensitive params 
  51. * @param Array $array 
  52. * @retunr Array 
  53. */  
  54. protected function filterParams($array = array()) {  
  55. $filtered = array();  
  56.   
  57. foreach((array$array as $k => $v) {  
  58.     if (is_array($v)) {  
  59.         $filtered[$k] = $this->filterParams($v);  
  60.     } else {  
  61.         switch($k) {  
  62.             // soroljuk fel ide a jelszómezőknél használt neveket  
  63.             case 'password':  
  64.             case 'passwrd':  
  65.                 $filtered[$k] = '[keep-in-secret]';  
  66.                 break;  
  67.             // itt pedig a jelszó megerősítő mezők neveit  
  68.             case 'password-confirm':  
  69.                 $same = $v === $array['password'];  
  70.                 $filtered[$k] = '[keep-in-secret(' . ($same ? '' : 'not-') . 'match)]';  
  71.                 break;  
  72.         }  
  73.     }  
  74. }  
  75.   
  76. return $filtered;  
  77. }  
  78.   
  79. protected function writeLog() {  
  80. $this->intiLogDb();  
  81. $user_id = Zend_Auth::getInstance()->hasIdentity() ? Zend_Auth::getInstance()->getIdentity()->id : 0;  
  82.   
  83. $this->db->insert($this->tblName, array(  
  84.     'session_id'  => Zend_Session::getId(),  
  85.     'user_id'     => $user_id,  
  86.     'user_datas'  => serialize(Zend_Auth::getInstance()->getIdentity()),  
  87.     'ip_address'  => $this->_request->getServer('REMOTE_ADDR'),  
  88.     'request_uri' => $this->_request->getServer('REQUEST_URI'),  
  89.       
  90.     'path' =>  
  91.         $this->_request->getModuleName() . '@' .  
  92.         $this->_request->getControllerName() . '/' .  
  93.         $this->_request->getActionName(),  
  94.           
  95.     'url' =>  
  96.         $this->_request->getScheme() . '://' .  
  97.         $this->_request->getHttpHost() .  
  98.         $this->_request->getRequestUri(),  
  99.           
  100.     'hostname'  => $this->_request->getServer('HTTP_HOST'),  
  101.     'timestamp' => date('Y-m-d H:i:s'),  
  102.     'method'    => $this->_request->getMethod(),  
  103.       
  104.     'request' => serialize( array(  
  105.         'SERVER'  => $this->_request->getServer(),  
  106.         'POST'    => $this->filterParams($this->_request->getPost()),  
  107.         'COOKIE'  => $this->filterParams($this->_request->getCookie()),  
  108.         'params'  => $this->filterParams($this->_request->getParams()),  
  109.         'request' => $this->filterParams($this->getRequest())  
  110.     )),  
  111.       
  112.     'exceptions' => serialize($this->_response->getException()),  
  113. ));  
  114.   
  115. $this->db->closeConnection();  
  116. }  
  117.   
  118. /** 
  119. * Post dispatch 
  120. * @param Zend_Controller_Request_Abstract $request 
  121. */  
  122. public function postDispatch(Zend_Controller_Request_Abstract $request) {  
  123.    parent::postDispatch();  
  124.    // csak abban az esetben kell naplót írni, ha hiba van  
  125.    if ( $this->getResponse()->isException() ) {  
  126.        $this->writeLog();  
  127.    }  
  128. }  

A plugin betöltése most is a szokásos módon, a Bootstrap osztályban történik.

  1. class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {  
  2.    protected function _initPlugins() {  
  3.       Zend_Controller_Front::getInstance()  
  4.        ->registerPlugin(new FI_Controller_Plugin_ErrorHandler());  
  5.    }  
  6. }  

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 arcképe
tiku I tikaszvince
Tikász Vince végzettsége szerint könyvtáros, a szemantikus és
hozzá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.
1

Na most vagy az van, hogy

Protezis · 2011. Feb. 1. (K), 00.00
Na most vagy az van, hogy késő van már és nem látom a fától az erdőt, vagy az, hogy a pluginod minden postDispatch után nyom egy logot, függetlenül attól, hogy van hiba, vagy nincs.
2

mea culpa

tiku I tikaszvince · 2011. Feb. 1. (K), 09.18
rossz kódot írtam a :( A "túl takarítás" esete áll fenn; az egyébként használt kódomból nem csak az azokat a részeket sikerült kiszedni, amik itt feleslegesek

A kódban szereplő postDispatch függvény helyesen a következően néz ki
  1. /** 
  2.  * Post dispatch 
  3.  * @param Zend_Controller_Request_Abstract $request 
  4.  */  
  5. public function postDispatch(Zend_Controller_Request_Abstract $request) {  
  6.    parent::postDispatch();  
  7.    // csak abban az esetben kell naplót írni, ha hiba van  
  8.    if ( $this->getResponse()->isException() ) {  
  9.        $this->writeLog();  
  10.    }  
  11. }  
3

Javítottam a cikkben.

Török Gábor · 2011. Feb. 1. (K), 10.26
Javítottam a cikkben.
4

Kérdés: A lazy init miatt nem

inf · 2011. Júl. 12. (K), 00.25
Kérdés: A lazy init miatt nem lett volna jobb külön osztályba tenni a kód nagyrészét, a plugint pedig a lehető legkisebbre venni?