Loggolás másként - lf4php
Java fejlesztőként találkoztam az slf4j-vel, ami nem más, mint egy logging facade. Önmagában nem sok mindenre jó: mint ahogy a neve is sejteti, elrejti előlünk a konkrét megvalósítást, absztrakt felületet ad. Használatával bármikor válthatunk egyik logging keretrendszerről a másikra. Innen vettem az ötletet, és nem titkoltan az slf4j forráskódját bújva készítettem el az lf4php-t. Egy korábbi írásomban már megemlítettem, most bemutatom, hogyan kell használni, illetve milyen irányelvek vezettek a tervezése és fejlesztése során.
Használat
Az osztályainkban mindössze el kell kérni egy loggert a LoggerFactory-tól és már mehet is a móka. Például:
$logger = LoggerFactory::getLogger(__CLASS__);
$logger->debug("Hello");
Jaj, mi ez a statikusság, miért nem DI?!
Jómagam nagy híve vagyok a DI-nak, illetve a DIC használatának. Miért nem adom át akkor (injektálom) a logger objektumokat az osztálynak? Ennek több oka is van:
- A DIC konfigurációs fájlomat nem kell megtömni logger függőségekkel.
- Az osztályaimat nem kell setLogger() metódushívással díszítenem, illetve valamiféle LoggerAware interfészt implementálnom.
- Ha a loggolási szintet finomhangolni akarom, elég csak a megfelelő namespace nevével regisztrálnom egy loggert és onnantól az lesz érvényben minden az alatt található osztályra nézve.
- Véleményem szerint a loggolásnak mindenhonnan könnyen elérhetőnek kell lennie, mivel ez egy elemi funkció.
Namespace szerinti konfigurálás
Tegyük fel, van egy lib, ami lf4php-t használ. Tele van szórva különböző szintű logokkal. Mi is lf4php-t használunk a saját kódunkban. Fejlesztés közben viszont nem szeretnénk minden debug üzenetet látni, elég csak az, ami a mi kódunkból jön. Az is elképzelhető, hogy még finomabb beállításokat szeretnénk: hibajavítás során csak abból az osztályból jövő trace logokat szeretnénk látni (és nem akarunk elveszni a log tengerben), amin épp dolgozunk.
Mindez elérhető úgy, hogy a finomhangolni kívánt namespace, vagy osztály nevével beregisztrálunk egy megfelelően konfigurált loggert. A log4php natívan támogatja ezt, nézzük meg, Monolog esetén hogy járhatunk el.
$monologFactory = new MonologLoggerFactory();
$monolog = new \Monolog\Logger('foo\bar');
// itt konfiguráljuk a Monolog loggert a Monolog dokumentációja alapján
$monologFactory->registerMonologLogger($monolog);
LoggerFactory::setILoggerFactory($monologFactory);
Ha a foo\bar
egy namespace és van pl. egy foo\bar\Service
osztályunk, akkor az abból jövő összes log esemény a fenti $monolog loggernek kerül átadásra. Egészen addig, amíg be nem regisztrálunk egy másik loggert mondjuk a foo\bar\Service
névvel. Tehát az lf4php megkeresi a hierarchia szerinti legközelebbi regisztrált loggert. Ezért érdemes mindig a __CLASS__
névvel elkérni a példányt.
Paraméteres log üzenetek
Az slf4j-hez hasonló módon az lf4php is támogatja a paramétereket. Az üzenetbe tetszőleges számú {}
párt tehetünk, amelyek lecserélődnek a paraméterben átadott tömb megfelelő elemeivel.
$logger->debug('Hello {}, derűs {}!', array('Jani', 'estét'));
Kivételeket harmadik, opcionális paraméterként adhatunk át.
$logger->error('Jaj, mi történt!', null, $exception);
Oké, de hogy használom mondjuk a log4php-vel?
Az lf4php egy interfészt ad, valamint néhány alapvető segéd metódust, amit aztán a binderek implementálnak, illetve felhasználhatnak. Ahhoz, hogy a log4php-t használjuk, szükségünk lesz az lf4hp-logf4php csomagra. Ez köti össze a két libet. A konfigurációhoz mindösszesen a következőre van szükség.
Logger::configure('config.xml');
LoggerFactory::setILoggerFactory(new Log4phpLoggerFactory());
A binderek nyilvánvalóan függenek az lf4php-től. Ha log4php helyett mondjuk monolog-ot szeretnénk használni, dobjuk ki a meglévő bindert és használjuk a monolog bindert. Az alkalmazásunkban kizárólag a konfigurációt kell megváltoztatni.
És a PSR-3?
Igen, időközben megalkották a PSR-3-mat, ami alapvetően pozitívum, csak épp a megvalósítással (mármint az interfész kapcsán) van néhány problémám. Mindazonáltal úgy gondolom, hogy az lf4php-nek van létjogosultsága. Egyrészt az interfész véleményem szerint tisztább (gondolok itt a paraméterekre, valamint az ‘exception’ kulcsra). Ráadásul a két megoldás megfér egymás mellett is. Ha egy framework PSR-3 loggerre dependál és mi mondjuk lf4php-t használnánk, akkor felkonfiguráljuk a monologot és hagyjuk, hogy a libek a preferált interfészen keresztül használják azt. Egyébként tervezem, hogy készítek egy PSR-3 bindert, amivel aztán bármilyen “standard” logging keretrendszert felhasználhatunk lf4php-n keresztül.