Logování skoro zadarmo

Školení, která pořádám

U hodně navštěvovaných aplikací je k nezaplacení mít čítače na jednotlivé akce, např. zobrazení článku, vložení komentáře, provedené vyhledávání, ale třeba i ty nízkoúrovňovější jako počet zavolání nějaké funkce, počet konfliktů unikátního klíče v databázi nebo třeba celkový čas sestavení stránky. Vlastně se to hodí u všech aplikací, nejen u těch hodně navštěvovaných.

Monitorováním těchto čítačů (a např. automatickým posláním upozornění při jejich náhlé změně) můžeme rychle zjistit, že je něco v nepořádku. Navíc můžeme sledovat vliv změn, které v aplikaci provádíme, např.: „Mělo přesunutí tlačítka pro vložení komentáře vliv na počet nových komentářů?“

Zápis hodnot

Logování těchto akcí ale samozřejmě jako každý zápis něco stojí, takže se jím často šetří. Jak logování nastavit tak, aby bylo co nejrychlejší? Potřebujeme lokální paměťové úložiště, které přežije ukončení skriptu, např. APC (ale ne třeba Memcache, které posílá data po síti). S ním už to je jednoduché:

<?php
/** Zvýšení hodnoty logovacího čítače
* @param string v úložišti už musí existovat klíč "counter.$name"
* @param int
* @return bool
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function log_counter($name, $value = 1) {
    apc_inc("counter.$name", $value, $return);
    return $return;
}

// příklady použití:
log_counter('searches');
log_counter('page_build_time_ms', (int) $microtime * 1000);
?>

Klíč je vhodné zaregistrovat samostatně, abychom se ve funkci nemuseli zdržovat jeho vytvářením.

Takto nastavené logování není o moc dražší než $counter[$name] += $value – data se zapisují v lokální paměti webového serveru, jen do paměti sdílené všemi procesy. Báli byste se v kódu použít $i++, aby se aplikace příliš nezpomalila? Já tedy ne.

Čtení hodnot

Teď už jen potřebujeme skript, který jednou za čas obejde všechny hodnoty na všech serverech a pošle je do společného úložiště. Může běžet třeba jednou za 15 minut nebo v závislosti na tom, jak moc podrobná data potřebujeme. Tento skript už nemusí být nijak zvlášť rychlý, protože není závislý na tom, jak často čítače zvyšujeme.

<?php
/** Přesun hodnot všech čítačů do databáze
* @return int počet zapsaných čítačů
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function log_grab() {
    $return = 0;
    foreach (new APCIterator('user', '~^counter\\.~') as $counter) {
        $name = preg_replace('~^counter\\.~', '', $counter['key']);
        $result = mysql_query("
            INSERT INTO counter (name, value, logged) VALUES
            ('" . mysql_real_escape_string($name) . "', '" . mysql_real_escape_string($counter['value']) . "', NOW())
        ");
        if ($result) {
            apc_dec($counter['key'], $counter['value']);
            $return++;
        }
    }
    return $return;
}
?>

Závěr

S takto navrženým logováním si můžeme dovolit nastavit prakticky libovolné čítače bez obavy o výrazné zpomalení aplikace. Logováním součtu a počtu (např. u času sestavení stránky) můžeme sledovat i průměrné hodnoty.

Jakub Vrána, Řešení problému, 29.2.2012, diskuse: 8 (nové: 0)

Diskuse

ikona Petr Soukup:

Restart Apache ale paměť APC vymaže. Čítač se tudíž nemůže spolehnout na existenci klíče. Stejně pak musím někde na začátku skriptu klíč zkontrolovat a případně vytvořit. Pokud tedy budu čítat vždy jen jednu věc, můžu klíč tvořit už rovnou při tom čítání.
Nebo je lepší řešení?

Jan Seidl:

Rekl bych, ze pokud zacne pouzivat toto na svem webu vypalti se ti zahookovat start/stop apache a tuto (tyto) hodnoty ukladat/nakladat.
napr. http://php.net/manual/en/function.apc-bin-dumpfile.php (komentar) ackoliv bych se nedivil kdyby byl primo nejaky modul do apache, protoze apache umi hooky..

MW:

Ale to stále neřeší výpadek proudu nebo fatální selhání HW ;-)

Filip Procházka (@HosipLan):

V případě fatálního selhání HW, nebo výpadku proudu (copak nemáte UPSky?). Budeš mít daleko větší problémy, než jsou chybějící statistiky klikání na tlačitko formuláře za posledních 10 minut.

Radim Daniel Pánek (@RDPanek):

A proč by se to nemohlo jednou za 10min. uložit do klasické k-v databáze?

Filip Procházka (@HosipLan):

To přesně dělá log_grab(), ne? Jaká je tady použita databáze, už je detail, ne?

Enlil:

Narazil jsem ovšem na nepříjemný bug pod windows.
Pokud je APC spuštěné (nemusí se vůbec v celém skriptu ani použít, jen je povolena tato extenze), tak zastaví skripty, které jdou po sobě.

Typicky - mám generovanou stránku, která má 20 obrázků generovaných php skripty (těchto 20 skriptů APC nepoužívá). Načte se cca prvních 10 skriptů a další se zaseknou...

Enlil:

Po několika hodinách jsem to vyřešil.
APC obsahuje opcode cache, kterou stačí vypnout pomocí direktivy

apc.cache_by_default = 0

a je po problému.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.