Logování ve Flexu
Osobně považuji logování v aplikaci za jednu z nejdůležitějších funkcí. Logování jednotlivých akcí zpřehlední jednoduché “debugování”, popř. dokáže odhalit špatnou posloupnost akcí. Ve flashi, kde je asynchronní volání na denním pořádku se to může stát hned.
Dříve jsem si na logování napsal vlastní třídu, jednalo se o klasický singleton, který měl pár metod, které byly převážně statické. Stěžejní metodou byla metoda “log( msg:String, level:LoggerLevelEvent )”. Z popisu je jasné, že se předávaly dva parametry. Prvním byl text zprávy, druhým úroveň, která říkala, zda jde o zprávu debug, info, error, aj. Výstup byl prováděn na klasický trace output nebo do souboru, popř. obojí. Taktéž bylo možné zprávy filtrovat, protože jsem si zavedl konvenci, že na začátku každé zprávy byl název třídy, která zprávu posílala.
Vše fungovalo krásně, až do chvíle kdy jsem přišel na to, že jsem vynalezl kolo. Po pozdějším zjištění, hodně šišaté kolo. Místo, abych se nejdříve koukl do manuálu jsem si za par minutách napsal něco, co je už napsáno bylo. Řeč je o balíčku “mx.logging.*”. Jo, objevil jsem ameriku.
mx.logging.*
Nebudu zde citovat manuál balíčku mx.logging.*. Zároveň také neřeknu nic nového, co by nebylo popsáno v manuálu, konkrétně kapitola: Using the logging API.
Stručně řečeno. Logování se dělí na tři části: “Logger -> Log Target -> Log Destination”. Flex jako takový logovací API nevyužívá, vyjímku tvoří pouze třídy: mx.rpc.*, mx.messaging.*, mx.data.* , které pomocí tohoto API zasílají informace o své činnosti.
Logger
Logger je v podstatě singleton, který poskytuje metody pro logování v danné kategorii. Veškeré Loggery jsou soustředěny a spravovány jednou třídou, konkrétně jde o třídu mx.logging.Log. Tato třída poskytuje rozhraní na jejich vytváření a taktéž poskytuje metody na registraci “Log targetů”.
Chci-li získat Logger, pro vlastní kategorii, tak to vypadá asi takto:
// definice balíčku a třídy my.class.foo.Bar
// ....
private var _log:ILogger = Log.getLogger('myCategory');
//....
Log.getLogger(‘myCategory’) provede vytvoření zadané kategorie myCategory. Jako název lze volit libovolný textový řetězec, ale je dobré držet nějaký standard. Je doporučeno kategorii zadávat jako plný název třídy, napr.: my.class.foo.Bar. Na základě názvu kategorie můžeme následně logované zprávy filtrovat.
Mám li vytvořený logger, mohu začít zasílat zprávy, např. takto:
// definice balíčku a třídy
// ....
private var _log:ILogger = Log.getLogger('my.class.foo.Bar');
public function Bar():void
{
_log.debug('In constructor.');
initClass();
}
private function initClass():void
{
_log.info('InitClass');
}
private function doLogEvent( eventName:String ):void
{
_log.log(LogEventLevel.DEBUG, "some info from event {0}", eventName );
}
private function doError( errorMsg:String, partName:String ):void
{
if( Log.isError() ) {
_log.error("Oou, we have a problem. Message: '{0}' from part: '{1}' ", errorMsg, partName );
}
}
//....
Jak je vidět, Logger poskytuje 5 základních metod a jednu obecnou metodu log(), která poskytuje možnost logovat zprávy i na vlastní úrovni.
- info( message:String, …rest ) – odpovídá zprávě na úrovni LogEventLevel.INFO
- debug( message:String, …rest ) – odpovídá zprávě na úrovni LogEventLevel.DEBUG
- warn( message:String, …rest ) – odpovídá zprávě na úrovni LogEventLevel.WARN
- error( message:String, …rest ) – odpovídá zprávě na úrovni LogEventLevel.ERROR
- fatal( message:String, …rest ) – odpovídá zprávě na úrovni LogEventLevel.FATAL
- log( level:Int, message:String, …rest )
Všechny poskytované metody umožnují ve zprávách nahrazování parametrů, tj. pomocí syntaxe {x}, kde x je pořadové číslo dodatečného parametru (…rest).
V metodě doError() je použita další vlastnost třídy Log. Použitá konstrukce zajistí to, že zpráva bude do loggeru odeslána pouze za předpokladu, že je úrověn přijímaných zpráv vyžží než úroveň danné zprávy. K dispozici jsou metody na zjištění všech stavů: isInfo(), isDebug(), isError(), isFatal(), isWarn().
Log Target
Zjednodušeně a ve zkratce řečeno, je to v příjemce zpráv z loggerů. Z vestavěných Log Targetů je nejpoužívanější TraceTarget, ktery jako výstup používá stejnou konzoli jako příkaz trace(). Dále pak máme k dispozici MiniDebugTarget a LineFormattedTarget (nejlepší taťka pro psaní vlastních log targetů).
Každý Log Target umožnuje filtrovat přijímané zprávy pomocí vlastnosti filters, která jako hodnotu přijímá pole textových řetězců, které specifikují kategorie, které daný Log Target chce. Složitě řečeno, ale příklad vysvětlí.
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="init()">
<mx:Script>
<![CDATA[
// .... imports ...
private function init():void
{
var logTarget:ILoggingTarget = new TraceTarget();
// chci přijímat pouze zprávy z těchto kategorií
logTarget.filters = ["my.class.foo.*", "mx.rpc.*" ];
// Vlastnost level určuje maximální úrověn přijímaných zpráv
// Posloupnost úrovní je takováto (číslo vedle názvu vyjadřuje hodnotu úrovně):
// LogEventLevel.FATAL (1000)
// LogEventLevel.ERROR (8)
// LogEventLevel.WARN (6)
// LogEventLevel.INFO (4)
// LogEventLevel.DEBUG (2)
// LogEventLevel.ALL (0)
//
// Zprávy jsou přijímány od zadané úrovně nahoru, tj. pokud zadám:
// logTarget.level = LogEventLevel.WARN
// bude target přijímat pouze zprávy typu WARN, ERROR, FATAL
//
// V tomto případě chci přijímat všechny druhy zpráv
logTarget.level = LogEventLevel.ALL;
// jak budou jednotlivé položky odděleny, napr: datum : errorLevel : errorMessage
logTarget.fieldSeparator = " : ";
// ve zprávě chci zahrnout nazev ktergorie, ze které zpráva přišla
logTarget.includeCategory = true;
// ve zprávě chci vidět datum a čas vzniku zprávy
logTarget.includeTime = true;
logTarget.includeDate = true;
// ve zprávě chci vidět úroveň zprávy
logTarget.includeLevel = true;
// zaregistrujeme logTarget do seznamu Loggerů
Log.addTarget( logTarget );
var myBar:Bar= new Bar();
myBar.doLogEvent( "myClass" );
myBar.doError( "myClass", "first" );
}
]]>
</mx:Script>
</mx:WindowedApplication>
Výstup do konzole bude např. takovýto:
12/1/2008 : [DEBUG] : my.class.foo.Bar : In constructor.
12/1/2008 : [INFO] : my.class.foo.Bar : InitClass
12/1/2008 : [DEBUG] : my.class.foo.Bar : Some info from event LogEvent
12/1/2008 : [ERROR] : my.class.foo.Bar : Oou, we have a problem. Message: 'LogEvent' from part: 'LogEventLevel'
Log Destination
Poslední část je nejjednodušší. Pouze určuje, kam má Log Target odeslat formátovaný výstup přijaté zprávy. Může to být soubor, SharedObject, databáze, prostě cokoli. Toto je již v režii daného Log Targetu.
Závěrem
Nepopsal jsem zde úplně všechny možnosti logovacího API, ale jako základ by to mohlo stačit. Největší výhodu tohoto přístupu jsem si však nechal na později, konkrétně jde o vytvoření vlastního Log Targetu. Doufám, že popis byl srozumitelný a snad se to bude někomu hodit.