Logování ve Flexu II.

Minule (Logovani ve flexu) byla popsána tvorba a princip logovacího API Flexu. Dnes navážu pokračováním v tomto tématu, konkrétně půjde o vytvoření vlastního Log Targetu. Vlastní Log Target umožňuje přizpůsobení výstupu z Logerru k obrazu svému, což je dnešním cílem.

Než bude ukázána praktická část vytvoření a práce s vlastním Log Targetem, bylo by dobré popsat princip spoluprace mezi Loggerem a Log Targetem.

Zprávy, které odesíláme pomocí metod poskytovaných Loggerem (debug(), info(), error(), …), jsou “vysílány” jako Event událost, konkrétně LogEvent. Aby Log Target tyto zprávy mohl přijímat, musí nastavit na daném Loggeru listener na událost LogEvent.LOG. Nastavení listeneru na tuto událost probíhá automaticky a odchycená událost je předána do metody logEvent( event:LogEvent ), která přijatou událost zpracuje, zformátuje a odešle na výstup.

Máme tento kód:

var logTarget:ILoggingTarget = new TraceTarget();
logTarget.includeLevel = true;
logTarget.includeDate = true;
// .....
logTarget.filters = ["my.class.foo.*"];

// samotna registrace Log Targetu do spravce loggeru
Log.addTarget( logTarget );

Při zavolání Log.addTarget() dojde k registraci zadaného Log Targetu. Metoda addTarget() zjistí, jaké kategorie zpráv bude registrovaný Log Target přijímat. Správce Loggerů projde filtry (logTarget.filters = ["mu.class.foo.*"] ) právě registrovaného Log Targetu a porovnává je s registrem kategorií Loggerů. Pokud najde Logger, který poskytuje zprávy jež Log Target chce, zavolá Log Target a předá mu informaci o nalezeném Loggeru. Log Target na předaném loggeru nastaví výše zmíněný listener na událost LogEvent.LOG. Od této chvíle Log Target přijímá zprávy odesílané daným Loggerem.

Vlastní Log Target

Flex poskytuje tyto vestavěné Log Targety:

  • TraceTarget – vhodný pro základní logování, jako výstup je použita trace() konzole.
  • MiniDebugTarget – umožnuje přesměrovat výstup do jiného SWF souboru, pomocí LocalConnection.
  • LineFormattedTarget – základní Log Target, který nikam neposílá výstup a je často používán jako “taťka” pro vlastní loggery.

LineFormattedTarget je základním stavebním kamenem při tvorbě vlastního Log Targetu. Poskytuje základní metody, potřebné pro logování událostí. Sám o sobě vychází z třídy AbstractTarget, která definuje základní metody pro správu a příjem událostí z Loggerů. Rozdíl mezi nimi je ten, že LineFormattedTarget definuje metodu, pro základní formtováný výstup, čímž nám práci ulehčí. Kdežto AbstractTarget žádný výstup nedefinuje a je nutné si výstup formátovat osobně.

Na samotném začátku, při vytváření vlastního Log Targetu, stojíme před rozhodnutím z jaké třídy vycházet. Chceme-li pouze využít možnosti ukládání zpráv do souboru, popř. odesílání pomocí LCDS tak doporučuji vycházet z třídy LineFormattedTarget, protože pak stačí implementovat metodu internalLog(), která provádí samotný formátovaný výstup a přesměrovat na libovolný výstup.

Pokud však máme nároky vyšší, např. chci mít vlastní vlastní výstupní formát (např. datum, čas, aj.) tak již doporučuji sáhnout po metodě AbstractTarget. Bylo by možné použít i LineFormattedTarget, ale bylo by nutné implementovat nejméně dvě metody, jednu navíc z mx_internal namespace. LineFormattedTarget přijme událost do metody logEvent, zformátuje a zavolá metodu internalLog(), která provede samotné odeslání na výstup.

Ukázka použití LineFormattedTarget

Zadání problému:

Mějme AIR aplikaci a zprávy o jejím běhu chceme zaznamenávat do zadaného souboru.

Řešení:

Pro tento případ se hodí použití třídy LineFormattedTarget, protože nemáme žádné požadavky na formát zprávy a je tedy možné použít výchozí formátování.

Naše třída bude vypadat např. takto:

package
{
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;

    import mx.core.mx_internal;
    import mx.logging.targets.LineFormattedTarget;

    use namespace mx_internal;

    public class CustomLogger extends LineFormattedTarget
    {
       private var _file:File;
       private var _fileStream:FileStream;

    	public function CustomLogger()
    	{
            super();

            // chceme logovat dotohoto souboru, musime jej tedy otevrit pro zapis
	    _file = new File( "C:\\flexLog\\log.txt" );
            _fileStream = new FileStream();
            _fileStream.open( _file, FileMode.APPEND);
    	}

    	override mx_internal function internalLog(message:String):void
    	{
            // prijatou zpravu zapisujeme do souboru
            _fileStream.writeMultiByte( message + "\n", 'iso-8859-1' );
    	}

    }
}

Výstup v log souboru bude asi takovýto:

12/6/2008 13:58:37.293 [DEBUG] my.class.logger.Main Button click
12/6/2008 13:58:37.421 [DEBUG] my.class.logger.Main Button click
12/6/2008 13:58:47.852 [DEBUG] my.class.logger.Main Button click

Tímto jsme vytvořili vlastní Log Target, který si ponechává všechny možnosti nastavení a zároveň se nemusíme starat o formátování zprávy.

Ukázka použití AbstractTarget

Problém:

Mějme stejné zadání z předchozí ukázky, avšak tentokrát je požadavek na formát zprávy. Zprávu je nutné zapisovat ve formátu “DATE|TIME|LEVEL|CATEGORY@MESSAGE“, protože log zprávy budou následně zpracovány jiným softwarem. Zároveň je nutné, aby byl vytvořen jedinečný soubor pro každé spuštění aplikace.

Řešení:

V tomto případě bude lepší jako základ použít třídu AbstractTarget, protože máme daný přesný formát zprávy a zároveň je nutné implementovat i vlastní metodu prozápis.

Třída bude vypadat např. takto:

package
{
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;

    import mx.logging.AbstractTarget;
    import mx.logging.ILogger;
    import mx.logging.LogEvent;

    public class CustomLogger2 extends AbstractTarget
    {
       private var _file:File;
       private var _fileStream:FileStream;

    	public function CustomLogger2()
    	{
            super();

	    _file = new File( "C:\\flexLog\\log.txt" );
            _fileStream = new FileStream();
            _fileStream.open( _file, FileMode.APPEND);
    	}

        /**
         * metoda, ktera zpracovava prijate LogEvent udalosti
         */
    	override public function logEvent(event:LogEvent):void
    	{
    	    var date:Date = new Date();
    	    var message:String = "";

    	    // vlozime datum
    	    message += Number(date.getMonth() + 1).toString() + "." +
                       date.getDate().toString() + "." +
                       date.getFullYear().toString();

            // separator
            message += "|";

            // vlozime cas
            message += padTime(date.getHours()) + ":" +
                        padTime(date.getMinutes()) + ":" +
                        padTime(date.getSeconds()) + "." +
                        padTime(date.getMilliseconds(), true);
            //separator
            message += "|";   

            // vlozime uroven
            message += LogEvent.getLevelString( event.level );

            // separator
            message += "|";

            // vlozime kategorii loggeru, ktery poslal zpravu
            message += ILogger( event.target ).category;

            // separator
            message += "@";

            // vlozime obsah prijate zpravy
            message += event.message;

            // zapiseme zformatovanou zpravu do souboru
            log( message );
    	}

        /**
         * metoda prevzata z LineFormattedTarget, upravuje zobrazeni vterin
         */
        private function padTime(num:Number, millis:Boolean = false):String
        {
            if (millis)
            {
                if (num < 10)
                    return "00" + num.toString();
                else if (num < 100)
                    return "0" + num.toString();
                else
                    return num.toString();
            }
            else
            {
                return num > 9 ? num.toString() : "0" + num.toString();
            }
        }

        /**
         * metoda zajisti vytvoreni jedinecneho jmena souboru
         * (pro jednoduchost neresi jakekoli konflikty, ktere by mohly
         * vzniknout, pokud by se aplikace spustila 2x v ten samy cas)
         */
        private static function _createLogName():String
        {
            var date:Date = new Date();

            return 'log_' + date.getTime() + '.log';
        }

        /**
         * metoda zapisuje zpravu do log souboru
         */
        protected function log( message:String ):void
        {
            _fileStream.writeMultiByte( message + "\n", "iso-8859-1" );
        }
    }
}

Výstup v log souboru bude vypadat asi takto:

12.6.2008|14:50:51.069|DEBUG|my.class.logger.Main@Button click
12.6.2008|14:50:51.359|DEBUG|my.class.logger.Main@Button click

Vytvořili jsme tedy Log Target, ktery zaznamenavá zprávy dle zadání.

Závěr

Možností jak logovat je spousta, snažil jsem se (snad úspěšně) popsat jak využít kvalitního logovacího API, které poskutuje Flex. Log Targety nejsou omezené pouze na zápis do souboru, lze taktéž data rozesílat pomocí LCDS Messaging protokolů, popř. pomocí DataServices zapisovat do databáze. Možností je spousta a je těžké je všechny obsáhnout, ale i tak doufám, že jsem poskytl základní návod, jak správně logovat.


Leave a Reply