Logy (záznamníky, journaly, žurnály,...) využíváme ve dvou rovinách:
- při ladění programu
- při sledování činnosti hotového programu
Jak to obvykle chodí při ladění
Znáte, to. Ladíte program a v kritických místech vkládáte nějaký ten print, který vypisuje obsah těžce zkoušené proměnné, nebo jen něco inteligentního jako "tady", abychom viděli, kudy to běželo.
Když je kritické místo v pohodě váháme, zda onen print vyhodit, nebo jen zakomentářovat. Co kdyby se ještě někdy hodil, že? A taky že při revizi kódu se nám podobná hláška od minula skutečně někdy hodí.
Ale proč to všechno dělat složitě a chaoticky, když máme v Pythonu vestavěný modul logging.
Příklad na úvod
import logging logging.warning("Varovná hláška")
Po spuštění tohoto příkladu se vypíše:
WARNING:root:Varovná hláška
A je to. Jak vidíme, logování může fungovat bez jakéhokoliv dalšího nastavování. Výpisy vystupují na stderr, což je obvykle konzole z níž program spustíme.
Základní nastavení
Log, který vypisuje hlášky na konzoli ovšem není zpravidla to, co by nám stačilo. Navíc formát řádky se nám taky nemusí líbit. Takže využijme možnosti konfigurace:
import logging logging.basicConfig( filename="program.log", format="%(asctime)s %(levelname)-8s %(message)s", level=logging.INFO) logging.info("Nejaka informace") logging.warning("A nejake varovani")
Nyní se bude logovat do souboru s názvem program.log. Jeho obsah bude:
2007-05-07 16:04:44,972 INFO Nejaka informace 2007-05-07 16:04:51,802 WARNING A nejake varovani
To už vypadá lépe, že?
Trocha teorie
Nechtěl jsem hned z počátku motat hlavu. To co jsme si ukázali jako návnada stačilo. Není nutné tu opisovat dokumentaci ke standardní knihovně - tu každý ví, kde má; pod polštářem.
Základním objektem modulu logging je objekt Logger. Jeho základní instance se jmenuje root a je vytvořena automaticky hned po načtení modulu. Proto jsme taky v prvním příkladu mohli hned logovat, a taky už víme, proč se ve výpisu jako druhá položka objevilo to slovo "root".
Každý Logger se projevuje prostřednictvím objektů Handler. Množné číslo je na místě - handlerů může mít jeden logger více. Pak se hlášky generované tímto loggerem objevují na více místech současně (třeba na konzoli a v souboru).
Aby hlášky nějak vypadaly, o to se stará objekt Formater. Každý handler může mít vlastní formát, takže když bychom si skutečně nechali vypisovat hlášky do souboru a ještě na konzoli, budeme v souboru chtít mít datum a čas, zatímco na konzoli by to asi byl tak nějak zbytečný údaj.
Protože ne vše musí být textový soubor, je tu ještě objekt LogRecord, který zajišťuje správně zformátování pro příslušný handler. Ten ale pracuje automaticky na pozadí a my se o něj starat nemusíme.
Modul logging má pět vestavěných úrovní - konstant s odpovídající číselnou hodnotou:
jméno levelu | číselná hodnota |
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
Každý logger má svoji nastavenou úroveň logování. Hlášky s nižší úrovní se do logu nedostanou. Logger může svou úroveň změnit pomocí metody setLevel. Např:
mylogger.setLevel(logging.INFO)
Nastaví úroveň logování pro logger "mylogger" na INFO.
Pro logování hlášek pak používáme metody loggeru s názvy, odpovídajícími příslušným levelům: .debug('zpráva'), .info(... Krom toho máme k dispozici obecnou metodu log(level, 'zprava'), díky níž můžeme zprávu zalogovat s úrovní jakou chceme (třeba 36).
Dokumentace odhalí i další možnosti těchto metod.
Takže znovu prakticky
Po přečtení předchozí kapitoly a se spuštěným interpreterem Pythonu ve druhé ruce bude následující příklad převzatý z originální dokumentace lahůdka. V tomto příkladu vidíme, jak nastavit dva handlery s různými formáty výstupu. Jeden (základní) jde do souboru, druhý s názvem console se vypisuje v konzoli. Taky vidíme vytvoření dvou loggerů, které pracují s nastavenou konfigurací.
import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filename='myapp.log', filemode='w') console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logging.getLogger('').addHandler(console) logging.info('Jackdaws love my big sphinx of quartz.') logger1 = logging.getLogger('myapp.area1') logger2 = logging.getLogger('myapp.area2') logger1.debug('Quick zephyrs blow, vexing daft Jim.') logger1.info('How quickly daft jumping zebras vex.') logger2.warning('Jail zesty vixen who grabbed pay from quack.') logger2.error('The five boxing wizards jump quickly.')
Na výstupu z konzole obdržíte:
root : INFO Jackdaws love my big sphinx of quartz. myapp.area1 : INFO How quickly daft jumping zebras vex. myapp.area2 : WARNING Jail zesty vixen who grabbed pay from quack. myapp.area2 : ERROR The five boxing wizards jump quickly.
A v logovacím souboru myapp.log bude tohle:
2007-05-07 16:33:33 root INFO Jackdaws love my big sphinx of quartz. 2007-05-07 16:33:33 myapp.area1 DEBUG Quick zephyrs blow, vexing daft Jim. 2007-05-07 16:33:33 myapp.area1 INFO How quickly daft jumping zebras vex. 2007-05-07 16:33:33 myapp.area2 WARNING Jail zesty vixen who grabbed pay from quack. 2007-05-07 16:33:33 myapp.area2 ERROR The five boxing wizards jump quickly.
Příklad s více moduly
V praxi sestávají naše aplikace z více modulů. Tady je hlavní úlevou dovědět se, že konfigurace loggeru se do importovaných modulů přenáší automaticky a my se o to starat nemusíme.
Ukažme si to na příkladu. Základní konfiguraci loggeru jsem dal do samostatného modulu mylog.py. Ten si každá aplikace může načíst hned na začátku (možnost logovat chceme asi pořád, že?).
A pak tu máme ještě vymyšlený modul mydbmod.py, který předstírá otevírání databáze. My ho tu máme ale hlavně proto, abychom ukázali, že konfigurace loggeru se opravdu přenáší automaticky všude - do všech modulů importovaných po inicializaci loggeru.
mylog.py:
import logging # jmeno souboru pro logovani: logfile = "prog.log" # nejnizsi logovana uroven: loglevel = logging.INFO def logstart(): logging.basicConfig( filename=logfile, format="%(asctime)s %(levelname)-8s %(name)-10s %(message)s", level=loglevel) logging.info("Logovani zapnuto") def logstop(): logging.info("Logovani ukonceno") logstart()
prog.py:
import logging, mylog mylogprg = logging.getLogger('prog') mylogprg.info("Hlavni modul aplikace startuje...") import mydbmod class App: def __init__(self): mydbmod.opendb() if __name__ == '__main__': app = App() mylog.logstop()
mydbmod.py:
import logging mylogdb = logging.getLogger('prog.dbmod') mylogdb.info('Zavadeni modulu mydbmod - start') mylogdb.setLevel(logging.DEBUG) # Tady je obsah modulu... def opendb(): # neco co otevre databazi # ... mylogdb.debug('Databaze otevrena.') mylogdb.info('Zavadeni modulu mydbmod - hotovo.')
Závěrem
Logování v Pythonu je významná pomůcka jak pro vývojáře, tak pro uživatele a administrátory. Nemusíme nic vymýšlet. Logovací nástroje máme ve standardní knihovně. Další podrobnosti hledejte v originální dokumentaci.
Zdraví Strýček Grumpa :o)