Logovací vrstva chování uživatelů s využitím dibi
- Ot@s
- Backer | 476
Já to řeším tak, že mám modelový objekt. Ten funguje jako obálka nad funkcionalitou dibi knihovny (nebo jakékoli jiné). Tj. mám v ní vlastní obecné metody pro insert, update a delete (+ další). Kromě jiného si v těchto metodách zajišťuji i logování. Mám tam samo i metodu, která nastaví popisku celé transakci, takže více SQL zaloguje jako jednu operaci, viz. třeba vazby n:m, či hromadné operace. Logování provádím do vyhrazené tabulky, která nad sebou nemá žádné indexy (pro rychlý INSERT). Dále nad touto tabulkou cronem zajišťuji rotaci dat po 3 měsících (=data starší 3 měsíců archivuji v CSV pro případné pozdější dohledání).
- Marsme
- Člen | 75
Ot@s:
máš někde tu vrstvu dostupnou na gitu? Nad tímhle řešením jsem taky uvažoval akorát s tím že bych asi data čistil z db po měsících. Ve většině případů chce „majitel“ webu vědět kdo mu tam co udělal v rámci 2–3 dnů max týdne takže měsíční rezerva je víc než dostačující. Navíc zbytek záznamů pak s nějakým rozumným exportem např do toho csv nebo čehokoliv jiného bude opět dohledatelný.
- Ot@s
- Backer | 476
Marsme napsal(a):
máš někde tu vrstvu dostupnou na gitu?
Bohužel nemám, je to rok starý komerční projekt. Navíc, dnes bych tu třídu napsal malinko jinak (princip ale platí). Moje řešení bylo z důvodu mojí lenosti více zaměřené na automatické logování událostí (úroveň „base“ objektů modelu) činnosti v databázi, takže pokud jsi třeba chtěl zalogovat něco přímo z presenteru (třeba událost v handleru), tak to nešlo. V praxi to ale až tak moc nevadilo, protože to většinou šlo opět přes nějakou tu metodu modelu.
Navíc píšeš, že to chceš aplikovat nad existujícím projektem, takže
bude dost záležet na míře použité abstrakce v projektu. Jestli máš
přímo v presenteru kostrukce typu
dibi::select(...)->from(...)
, tak se zásadnějšímu přepisu
aplikace stejně nevyhneš.
- Tharos
- Člen | 1030
Jde to realizovat a svým způsobem velice snadno. Tohle používám v jednom projektu:
SQL
CREATE TABLE `sqllog` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`asked` datetime NOT NULL,
`username` varchar(50) COLLATE utf8_czech_ci NOT NULL,
`ip` varchar(15) COLLATE utf8_czech_ci NOT NULL,
`sql` text COLLATE utf8_czech_ci NOT NULL,
`type` enum('INSERT','UPDATE','DELETE') COLLATE utf8_czech_ci NOT NULL,
`table` varchar(50) COLLATE utf8_czech_ci DEFAULT NULL,
`entryId` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `type` (`type`),
KEY `table` (`table`),
KEY `entryId` (`entryId`),
KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
Logger.php
<?php
namespace Util;
use DibiEvent;
use DibiConnection;
use Nette\Utils\Strings;
/**
* @author Vojtěch Kohout
*/
class Logger
{
private $connection;
private $username;
private $ip;
private $queue = array();
function __construct(DibiConnection $connection)
{
$this->connection = $connection;
}
public function setActorIdentity($username, $ip)
{
$this->username = (string) $username;
$this->ip = (string) $ip;
}
public function logEvent(DibiEvent $event)
{
if ($this->username === null) {
throw new \Util\RuntimeException('Actor was not initialized in modifying queries logger.');
}
if (($event->type & 56) !== 0) { // is INSERT, UPDATE or DELETE
if (!$this->isLoggingEvent($event->sql)) {
switch ($event->type) {
case DibiEvent::INSERT: $type = 'INSERT'; break;
case DibiEvent::UPDATE: $type = 'UPDATE'; break;
case DibiEvent::DELETE: $type = 'DELETE'; break;
}
if (isset($type)) {
$table = $this->parsetTableName($event->sql) ?: '';
if ($type === 'INSERT') {
$entryId = $this->connection->insertId;
} else {
$entryId = $this->parseEntryId($event->sql) ?: '';
}
$this->queue[] = array($this->username, $this->ip, $event->sql, $type, $table, $entryId);
}
}
}
}
public function processQueue()
{
foreach ($this->queue as $job) {
$this->connection->query('
INSERT INTO [sqllog] ([asked], [username], [ip], [sql], [type], [table], [entryId])
VALUES (NOW(), %s, %s, %s, %s, %sN, %sN)
', $job[0], $job[1], $job[2], $job[3], $job[4], $job[5]);
}
}
////////////////////
////////////////////
private function isLoggingEvent($sql)
{
return Strings::startsWith($sql, 'INSERT INTO `sqllog`');
}
private function parsetTableName($sql)
{
$matches = array();
preg_match('#^(INSERT INTO|UPDATE|DELETE FROM) `([a-z_]*)`#', $sql, $matches);
if (array_key_exists(2, $matches)) {
return $matches[2];
} else {
return null;
}
}
private function parseEntryId($sql)
{
$matches = array();
preg_match('#WHERE.*`id` = (\S+)#', $sql, $matches);
if (array_key_exists(1, $matches)) {
return $matches[1];
} else {
return null;
}
}
}
Ke konci v bootstrap.php:
$application->onShutdown[] = function() use ($container) {
$container->logger->processQueue();
};
Uvnitř startup
metody nějakého abstraktního presenteru, ze
kterého dědí ty presentery, jejichž aktivitu chceš logovat:
// $connection instanceof DibiConnection
// $logger instanceof Util\Logger
$connection->onEvent[] = function (DibiEvent $event) use ($logger) {
$logger->logEvent($event);
};
Editoval Tharos (5. 10. 2012 13:30)
- Tharos
- Člen | 1030
Jinak, kdyby to nebylo zřejmé, loguje to všechny pozměňující dotazy na databázi (pochopitelně kromě jedné aktivity, a to vlastního vložení záznamu do logu). Snaží se to i vždy detekovat, kterého řádku v které tabulce se úprava týkala. To lze snadno u vkládání (podle ID posledního záznamu) a většinou i při úpravě a výmazu.
Celý log se sype do databáze až v $application->onShutdown() proto, protože průběžné vkládání do logu by znemožňovalo v aplikaci používat last insert ID (to by vždy ukazovalo na ID nějakého řádku v logu).