Hozzáférési napló írása Zend Framework alkalmazásokhoz
Próbálok nagy szavakat találni, miért érdemes egy-egy alkalmazásba naplózást építeni, de mindig a hiba felderítéshez lyukadok ki. Biztosan az olvasó is találkozott olyan felhasználóval, aki rendszeresen futott bele egy hibába, amit az elmondása alapján nem lehetett reprodukálni. Vagy olyanba, amibe csak egy-két felhasználó szaladt bele, olyan úton, amire fejlesztőként egyáltalán nem gondoltunk. Ilyenkor hatalmas segítség lehet, hogy lépésről lépésre végig tudjuk követni felhasználót.
A sorozatban megjelent
- Hozzáférési napló írása Zend Framework alkalmazásokhoz
- Hibanapló Zend Framework alkalmazásokhoz
Jöhet a felvetés, hogy ott vannak nekünk a szerverlogok, használjuk azt. Igaz, vannak logok. De mi van, ha a tárhelyszolgáltató nem őrizgeti, csak korlátozott ideig? Vagy prompt megoldást kell találni egy problémára, és a szolgáltató nem elérhető, nem ér rá, nem tud vagy – egyszerűen – nem akar segíteni? Egyik opció lehet egy másik, készségesebb tárhelyszolgáltató, másik a naplózás beépítése az alkalmazásunkba.
Mindennapi munkámban Zend Framework alapokon építek webalkalmazásokat, ezért e rendszeren fogom bemutatni, hogyan naplózom a kéréseket. Egy következő cikk témája lesz a felület elkészítése, ahol végig lehet majd nézni a naplókat.
A napló írását egy egyszerű bővítmény végzi, SQLite adatbázisba, heti bontásban. Minden bejegyzés tartalmaz minden adatot, amit a felhasználóról ismer a rendszer: az aktuális munkamenet azonosítót, az IP-címet, ahonnan a kérés érkezett, a tartománynevet, a lekért URL-t, a kérés típusát, az időpontot és egy kicsit kibővített Request
objektumot.
/**
* AccessLog plugin
*
* @author 4image#dev, Tikász Vince
*/
class FI_Controller_Plugin_AccessLog extends Zend_Controller_Plugin_Abstract
{
/**
* @var Zend_Db_Adapter_Abstract
*/
protected $db;
protected $tableName = 'access_log';
protected function initDb()
{
$dbName = 'access-log-' . date('Y') . '-w' . date('W') . '.sqlite3.db';
$logPath = APPLICATION_PATH . '/log/' . $dbName;
$dbExists = is_file($logPath);
$this->db = new Zend_Db_Adapter_Pdo_Sqlite(array('dbname' => $logPath));
if (!$dbExists) {
// Még nem létezik az eheti adatbázis, tehát létre kell hoznunk a napló táblát.
$this->db->query(
'CREATE TABLE IF NOT EXISTS ' . $this->tableName . ' (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id CHAR(32),
user_id INTEGER DEFAULT NULL,
user_data 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
)');
}
}
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$this->initDb();
$userId = 0;
if (Zend_Auth::getInstance()->hasIdentity()) {
$userId = Zend_Auth::getInstance()->getIdentity()->id;
}
$this->db->insert($this->tableName, array(
'session_id' => Zend_Session::getId(),
'user_id' => $userId,
'user_data' => serialize(Zend_Auth::getInstance()->getIdentity()),
'ip_address' => $request->getServer('REMOTE_ADDR'),
'request_uri' => $request->getServer('REQUEST_URI'),
'path' => $request->getModuleName() . '@' . $request->getControllerName() . '/' . $request->getActionName(),
'url' => $request->getScheme() . '://' . $request->getHttpHost() . $request->getRequestUri(),
'hostname' => $request->getServer('HTTP_HOST'),
'timestamp' => date('Y-m-d H:i:s'),
'method' => $request->getMethod(),
'request' => serialize(array(
'SERVER' => $request->getServer(),
'POST' => $request->getPost(),
'COOKIE' => $request->getCookie(),
'params' => $request->getParms(),
'request' => $this->getRequest(),
)),
));
// Lezárjuk az adatbázis kapcsolatot, megtörténik a tényleges írás
$this->db->closeConnection();
}
}
A plugin betöltése 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_AccessLog(), -10);
}
}
A napló írása a dispatchLoopStartup()
ponton történik. Ennél korábban csak a routeStartup()
és a routeShutdown()
fut le. Ezeken a pontokon még nem lehet pontosan tudni, hogy melyik modul, vezérlő és művelet kerül majd meghívásra, ezért sanszos, hogy nem a tényleges esemény kerül a naplóba. Ezen pontot követően pedig már a preDispatch()
fut le, ahol a jogosultságok ellenőrzése és egyéb dolgokkal összefüggő átirányítások is történhetnek.
Az adatbázis inicializálásakor az első lépés a napló nevének meghatározása. Mivel heti napló készítés a cél, ezért év-hét formátumú SQLite adatbázis állományaink lesznek. Ha nem létezett korábban az adatbázis, akkor a rendszer azt automatikusan létrehozza majd. Viszont a tábla létrehozásáról nekünk kell gondoskodni. Az írás után pedig ne felejtsük el lezárni az adatbázis kapcsolatot.
A napló bejegyzésbe nem csak az egész Request
objektumot mentjük le, de ki is emelünk belőle néhány részt, így pl a SERVER
, POST
, COOKIE
tartalmát, és az útválasztás után kapott paramétereket. A külön oszlopba való kiemelés a majdani keresést könnyíti meg: így egyszerűbb lesz megírni ez egy bizonyos felhasználó követését segítő lekérdezéseket.
Már a cikk írása közben jött néhány új ötlet, hogyan is lehetne további funkciókat beépíteni: az új adatbázis állomány létrehozásakor az előző naplót archiválni: tömöríteni, másik szerverre feltölteni, e-mail-ben elküldeni egy vagy több címre, esetleg ezek közül többet is elvégezni. Vagy akár ezeknek a konfigurálására külön adminisztrációs felületet írni.
■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.
Nagyon jó!
statisztika
Tapasztaltam
Cikk?
Attól tartok
Hú, ez jó!
nem orulok neki, hogy
ezert nem szeretem az ilyen altalanos logolo alkalmazasokat, mert konnyu megfelejtkezni rola, aztan hiaba tarolod a jelszavakat hashelve saltolva a user tablaban, ha az access_log-ban ott van minden plainben. :/
Tyrael
INSERT
nem megoldás
password
igazabol csak azert irtam le,
a hozzaszolasbol is latszott, hogy eleg sokan esz nelkul atvesznek mindenfele kodreszletet, ezert nem szeretem, hogyha demo kodban valamilyen sebezhetoseg van, es legalabb egy figyelmeztetes nincs kirakva melle, hogy ez es ez vele a problema.
Tyrael
audit log