====== Jak pracovat s logovací knihovnou ====== Tento zápisek se krátce věnuje logovacím knihovnám v Javě a práci s nimi -- konkrétně commons-logging a log4j. ===== Logovací knihovna ===== Ve svých projektech používám dvojici knihoven **commons-logging** a **log4j**. První jmenovaná je velmi jednoduchá (což vidím jako výhodu), sama o sobě loguje snad jen jen na standardní a chybový výstup a obvykle se použije jen jako fasáda pro jinou knihovnu. Pro tyto účely pak často používám log4j -- de facto standard pro logování (už nejen pro Javu). Výhoda tohoto řešení je zřejmá -- log4j je možno kdykoliv nahradit jinou knihovnou dle požadavků zákazníka a podobně. Nastavení je jednoduché -- po přidání knihoven do projektu stačí mít v CLASSPATH soubory //commons-logging.properties// a //log4j.properties//. V prvním souboru pouze definujeme, že chceme logovat přes knihovnu log4j. # Commons Logging configuration org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger Knihovna Commons Logging se ale chová chytře a vlastně tento soubor vůbec není potřeba -- pokud najde v CLASSPATH log4j, loguje tam. Pakliže zjistí, že jede na JRE 1.4 nebo vyšší, použije logger z JRE. A ve chvíli, kdy není ani jedna podmínka splněna, loguje na standardní výstup vlastními prostředky. Já soubor uvádím ale explicitně. Hodnotu //org.apache.commons.logging.Log// lze nastavit také proměnnou -- následující ukázka je přímo z kódu (můžete použít i java proměnnou -D). static { System.getProperties().setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger"); } Nastavení log4j je podobné, bližší informace podá dokumentace. # log4j properties log4j.rootLogger=WARN, A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.Target=System.out log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-5p %C{1}: %m [%t] %x%n Nyní lze použít knihovnu commons-logging pro logování, vše bude dostávat knihovna log4j, která zprávy zpracuje dle daného nastavení. ===== Něco málo o výjimkách ===== O výjimkách v jazyce Java toho bylo napsáno mnoho, takže nebudu opisovat již napsané. Zachycují **vyjímečný stav** programu, takže by neměly být zneužívány jako nosiče dat, logiky či jako berličky -- to je myslím jasné. Někteří programátoři navyklí na jazyk C (nebo jiné imperativní jazyky) občas provádějí toto: MyObject = myMethod(param1, param2); if (myObject == null) { // something is wrong } Toto je jasný případ zbytečného testování návratových hodnot. //Kdykoli je to možné, použijte pro chybný stav výjimku.// Ještě než budu pokračovat, tak musím uvést, že testovat na null není ve své podstatě nic špatného -- pakliže nemáte jinou možnost (nejste autorem knihovny, kterou používáte -- například), tak holt testovat musíte. Jinak bychom mohli dostat NPE. Zpátky k příkladu -- v tomto případě by tedy měla metoda myMethod vyhazovat výjimku a v kódu místo ifu by měl být použit try blok. try { myObject = myMethod(param1, param2); } catch (MyException e) { // something is wrong } Velmi často se programátoři //zdráhají tvořit vlastní výjimky//. V nejhorším případě se přikloní k výše uvedenému testování návratových hodnot, v lepším použijí nějakou standardní výjimku Javy -- a bohužel v některých případech vyberou nevhodného kandidáta. Ačkoli je to věc vkusu, už před časem jsem se věnoval [[blog:hazet_ci_nehazet_nullpointerexception|problému NPE]]. Je výhodné tvořit vlastní sadu výjimek a v místech, kde je to potřeba, výjimky vyhazovat (do cause ukládáme příčinu -- vnořenou výjimku). Dnešní programovací prostředí nabízejí komfort, vytvoření potomka z třídy je otázkou pár sekund. Je vhodné zapřemýšlet, jakou hiearchii vlastních výjimek vytvořit -- u jednoduššího projektu nebude vůbec na škodu mít jen jednu uživatelskou výjimku. Jakmile máme jednu nebo více uživatelských výjimek (vždy je dobré mít jednu kořenovou výjimku -- jednoho rodiče), je nutné přemýšlet o tom, //kde uživatelské výjimky odchytnout// a zpracovat. U konzolové aplikace to může být až třeba metoda main, která provede případné logování pomocí commons-logging nebo log4j. Je výhodné odchytávat uživatelské výjimky na jednom (nebo "konečném počtu") místě. Příkad: try { worker.work(); } catch (ConvertorException e) { System.err.println("Error while converting: " + e.getMessage()); // report exception only if debug (or trace) enabled if (log.isDebugEnabled()) { log.fatal("Error while converting, conversion has not finished properly", e); } else { log.fatal("Error while converting, conversion has not finished properly"); } } Toto je typická ukázka zpracování uživatelské výjimky //ConvertorException//, která je zároveň rodičem všech uživatelských výjimek v programu. Na konzoli do chybového výstupu vypiše patřičnou hlášku plus detailnější popis výjimky (pomocí //getMessage//). Poté zaloguje pomocí logovacího subsystému detailnější popis a v případě, že je aktivován DEBUG, tak vkládá i stacktrace. Výpisy zásobníku nemusejí být vhodné, pakliže se jedná o něco nezávažného (jako například FileNotFoundExceptioin) -- mohou být kdykoli aktivovány (v tomto případě konzolové aplikace pomocí parametrů -debug nebo -trace). Podobně se řeší programy s uživatelským rozhraním. Obvykle je vhodné připravit nějaký speciální chybový dialog, který po klepnutí na tlačítko "Detaily" zobrazí kromě hlášky také stacktrace. ===== Úrovně výjimek ===== |Úroveň pro CL|Úroveň pro log4j|Popis| |TRACE|TRACE|Nejnižší úroveň, obvykle vypisujeme pouze do souboru. Vhodné pro různé check-pointy (metoda zavolána, různé počítadla pro ladící účely). Tato úroveň může obsahovat mnoho zpráv, často je programátory nevyužívána a je psáno zbytečně mnoho informací do úrovně DEBUG.| |DEBUG|DEBUG|Ladící hlášky, které pomáhají zákazníkovi identifikovat prvotní problém, případně vytvořit report pro dodavatele, aby mohl chybu opravit. V DEBUG by nemělo být příliš hlášek, ale zároveň by tato úroveň měla poskytovat dostatek informací k tomu, aby mohlo být zpětně zjištěno, v čem je problém.| |INFO|INFO|Hlášky INFO obvykle nejsou vypisovány ve standardní instalaci, ale zákazník si může zvýšit vypisování z WARN na INFO a měl by vidět //o něco více// informací. Hlášky INFO jsou vhodné zejména pro různé informační zprávy typu něco se startuje, server znovu načetl konfiguraci nebo že aplikace načítá soubor z disku.| |WARN|WARN|Tato úroveň by měla být zapnuta v implicitní instalaci softwaru, program informuje o události, která není obvyklá. Příklad -- potřebná konfigurační hodnota nebyla nalezena v konfiguraci, program použije defaultní hodnotu. Je vždy dobré se zamyslet, jestli daná je daná hláška chybou (ERROR), varováním (WARN) nebo dokonce pouze informativního charakteru (INFO).| |ERROR|ERROR|V aplikaci došlo k chybovému stavu, který však nenaruší běh programu. Typicky například nemohl být zpracován požadavek na serveru kvůli špatné konfiguraci nebo třeba chyba vstupních dat -- program neprovede svoji činnost, ale čeká na další (validní) vstup.| |FATAL|FATAL|Došlo k vyjímečnému stavu a program ukončuje svoji činnost. Například soubor nebyl vůbec nalezen, chyba v programu nebo došla paměť.| Konfigurace úrovní je možné dělat pomocí properties souborů (v případě například serverových aplikací), nebo přímo z programu (nastavení, přepínače) v případě klientských aplikací. ===== Jak psát hlášky ===== Všechny metody pro logování (fatal, error, warn, info, debug, trace) přijímají řetězec a některé i výjimku, která způsobila vlastní hlášení. Vždy je nutné napsat jednou větou, v čem může být problém. Někteří programátoři popíšou chybu (např. "Soubor nenalezen"), to je ale špatně. Taková informace je uživateli k ničemu, je potřeba popsat příčinu chyby -- tedy //proč// k tomu došlo, resp. //co program dělal//, když k výjimce došlo. **Snažíme se popsat příčinu, nikoliv chybu samotnou**! Než hlášku napíšete, podívejte se v kódu do try bloku a na výjimku, kterou zachycujeme. Zamyslete se nad tím, když výjimka nastane, jakou informaci případný řešitel potřebuje. Na konci nepíšeme žádnou interpunkci (tečku, dvojtečku). Správné popisy jsou: log.info("Starting server"); log.error("Cannot read configuration file " + filename, exception); log.fatal("Too many open connections, increase the OCL variable"); Špatné popisky: log.error("File not found", exception); log.error("Error occured in program", exception); log.fatal("Program error: ", exception); Dobrou praxí je také všechny chybové hlášky načítat z property souboru a navíc v nich ukládat i chybové kódy. Pomůže to lépe identifikovat místa v kódu a lépe provázat s dokumentací. Příklad: # error messages XYERR01=XYERR01: The selected option is not available in this mode XYERR02=XYERR02: File %s not found in path Všimněte si, že chybové kódy jsou ještě jednou zopakovány také v textu výjimky a vypíší se zároveň s textem chyby. Je dobré vytvořit si na to vlastní implementace výjimek, které se o načítání a správné zobrazování postarají. Tento můj dokument neberte jako dogma, je to jen sběr mých dosavadních zkušeností. Budu rád, když se o názory podělíte v diskusi. ===== Odkazy ===== * http://commons.apache.org/logging/guide.html * http://logging.apache.org/log4j/1.2/manual.html {{tag>java}} ~~DISCUSSION~~