Posts by author:

tygl

Logování ve Flexu II.

by Petr Cihelka on December 6, 2008

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.

Logování ve Flexu

by Petr Cihelka on December 1, 2008

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.

Who is bee and who is tygl

by Petr Cihelka on November 28, 2008

Jako vždy je největším problémem začít, tj. vymyslet ten předzápasový výkop. Dlouhou dobu jsem přemýšlel, jak vlastně začít. A pokud možno začít tak, aby to zase celé rychle neskončilo.

Toto je v podstatě historický okamžik, neb má osoba nepublikovala za celou svou “internetovou” existenci jediný článek. Pravdou je, že nebýt Toma Krchy z Adobe a založení Flash Platform Czech Republic tak by se to asi vůbec nestalo. Ale co čert nechtěl, na prvním srazu se stalo to, že se zrodil Flash Platform UG. Tyto dvě hlavní události zapříčinily to, že jsem nad nějakým blogem začal vůbec přemýšlet. Došlo mi, že zkušenosti, které získávám při řešení problémů s Flexem a LCDS by se mohly někomu hodit. Prostě vrátit do komunity nějaký ten drobeček zkušeností a zároveň se něčemu přiučit. Tak se tedy stalo to, že si převážně introvertní osoba založila blog.

Tygl, včelka a jiná zvířátka

Došlo na nejhorší, představení. Mé jméno je Petr Cihelka, také přezdívaný jako “tygl” (”zkomolený” tygr). Internetovým technologiím se věnuji poměrně dlouhou dobu. S Flashem jsem začal převážně nevážně před pár roky, ale spíše to byla jen hračka. Vážnější zájem přišel s příchodem Flexu verze 3, tj. tak před rokem. Aktivně pracuji s Flexem tak 3/4 roku. Ve flexu se věnuji se především vývoji RIA aplikací.

Jak to souvisí s včelkou, proč “tyglobee” ? Popravdě, je zde více možností. Může to být naprostá “divná nepěkný věc”. Nebo jde o narážku na Adobeee. A nebo jde o takové přirovnání, kdy si občas pří vývoji aplikace připadám jako ta včelka, která dráždí tygra. Přičemž riskuje, že ho naštve a tygr ji “bácne” a včelka se rozplácne jak přezrálý meloun. Co je správně vím, ale nepovím.

Obsah

O čem se zde budu psát ? Půjde převážně o informace z řady produktů, se kterými pracuji. Pokud to mám vyjmenovat, tak to odhaduji takto:

Závěrem

Závěrem musím podotknout pár věcí. Varuji poťouchlé jazykozpytce, protože zde může hrozit nebezpečí úrazu, budu se snažit psát hezky česky, ale nevylučuji, že mi sem tam neulétne nejaké to pravopisné fuj, popř. diakritika (jsem postižen “cestinou”). Co se týče obsahu článků, tak předem říkám, že se nepovažuji za “gurua” dané technologie, ale budu se snažit, abych nepsal totální blbosti.