Publikoval Michal Kočí dňa 12.4.2005 o 00:29 v kategórii .Net
Minulý týždeň som začal tvoriť malú utilitku, ktorá z Event Logu načíta najnovšie chyby a varovania, vytvorí jednoduchú zostavu a pošle ju e-mailom. Fungovať mala tak, že si bude pamätať dátum a čas poslednej reportovanej položky Event Logu a pri ďaľšom spustení bude informovať iba o novších položkách.
V .Net Frameworku je Vám k dispozícii trieda EventLog. Použiť sa dá aj keď chcete pristúpiť k Event Logu iného počítača. Čo mi prekážalo najviac bolo, že som si nemohol vyfiltrovať iba tie udalosti, ktoré ma zaujímajú. Keďže som mal záujem iba o "najnovšie" zápisy, teda tie ktoré boli vytvorené od posledného spustenia mojej utility (cca deň), bolo pre mňa neúnosné načítať všetky položky z Event Logu na danom počítači a filtráciu robiť ako druhý krok. Totiž, po pripojení sa na jeden zo serverov (ale obdobná situácia bola aj na iných serveroch) som musel spracovať vyše 160 tisíc položiek. To trvalo niečo okolo 5 minút. No a 5 minút pre približne desať serverov je niečo okolo hodiny práce. To je dosť veľa.
Ešte pred prvým príkladom upozorňujem, že tak ako vždy, v ukážkovom kóde neošetrujem vynímky, hlavne preto, lebo tento kód píšem v tomto momente, teda nie je to Copy&Paste z produkčného kódu.
Ukážka kódu, ktorým sa dá prebehnúť každá položka a vykonať nad ňou nejaká kontrola:
private void EventLogs() { // zistime si pristupne event logy na aktualnom pocitaci EventLog[] logs = EventLog.GetEventLogs(); // pre kazdy event log (security, application, ...) for(int i=0; i<logs.Length; i++) { EventLog log = logs[i]; EventLogEntryCollection entries = log.Entries; // informacia o nazve a pocte poloziek // na niektorych serveroch hrozivo vysoke cislo Console.WriteLine("{0}:{1}", log.Log, entries.Count); // pre kazdu polozku v logu for(int j=0; j<entries.Count; j++) { EventLogEntry entry = entries[j]; if(ValidateEntry(entry)) { /* nieco vykoname s polozkou, napriklad si ju pridame do kolekcie, na zaklade ktorej budeme v buducnosti generovat zostavu */ } } } } private bool ValidateEntry(EventLogEntry entry) { /* kontrolny kod, napriklad na datum */ return false; }
Utilita mala bežať v noci, takže by hodinová práca až tak nevadila, ale nebolo to riešenie, ktoré by mi zrovna sedelo. Lenže, čo by sa s tým dalo robiť? Mohlo by pomôcť WMI? WMI na mňa odjakživa pôsobilo nejak až moc obrovsky a trpel som pocitom, že sa jedná o docela pomalú technológiu. Ale, veď za pokus nič nedám.
Čo je skvelé na WMI je, že sa dá dotazovať syntaxou podobnou syntaxy jazyka SQL. Priznám sa, neskúšal som prebehnúť všetky položky v Event Logu pomocou WMI, takže neviem zodpovedne povedať, či pri prechádzaní všetkých položiek by bolo WMI rýchlejšie alebo pomalšie. Ale práve vďaka tomu, že sa dá dotazom požiadať iba o tie dáta, ktoré ozaj chceme spracovávať (mojom prípade položky za posledných cca 24 hodín), skrátilo sa načítavanie požadovaných položiek z vyše 5 minút na niekoľko sekúnd. A to je teda ozaj poriadny rozdiel.
WMI má samozrejme zopár nevýhod, ale nie nejakých závažných:
V mojom prípade potrebujem pracovať s triedou Win32_NTLogEvent (to som si zistil cez spomenuté CIM Studio). Táto trieda sa nachádza v namespace root\cimv2. Microsoft v .Net Framework dodáva aplikáciu Management Strongly Typed Class Generator (MgmtClassGen.exe), ktorá dokáže vygenerovať silne typovú triedu pre ľubovoľnú WMI triedu. Tú som si vygeneroval nasledovne:
MgmtClassGen.exe Win32_NTLogEvent /O EventLog2Mail /L CS /P NTLogEvent.cs
No a keď máme aj silne typovú triedu, potrebujem ešte vygenerovať ten správny dotaz, pustiť ho a výsledné objekty nejak spracovať:
private void EventLogsWMI(DateTime from) { ManagementObjectSearcher mos = null; ManagementObjectCollection moc = null; ManagementObject mo = null; NTLogEvent le = null; // pripravime si namespace a query // pri query pouzijeme na formatovanie datumu // metodu ToDmtfDateTime, ktore sme pred tym // zmenili jej viditelnost z private na public string scope = string.Format(@"\ROOT\CIMV2"); string query = string.Format( "SELECT * FROM Win32_NTLogEvent " + "WHERE TimeWritten > '{0}' " + "AND (Type = 'error' OR Type = 'warning')", NTLogEvent.ToDmtfDateTime(from)); // vyhladame podla nasho dotazu mos = new ManagementObjectSearcher(scope, query); moc = mos.Get(); // prebehneme vsetky polozky IEnumerator ie = moc.GetEnumerator(); while(ie.MoveNext()) { mo = (ManagementObject) ie.Current; // pretypujeme z ManagementObject na silne // typovy objekt NtLogEvent, ktory sme si // nechali vygenerovat programom MgmtClassGen.exe le = new NTLogEvent(mo); if(ValidateEvent(le)) { /* nieco vykoname s polozkou, napriklad si ju pridame do kolekcie, na zaklade ktorej budeme v buducnosti generovat zostavu */ } } if(moc != null) moc.Dispose(); if(mos != null) mos.Dispose(); } private bool ValidateEvent(NTLogEvent ev) { /* kontrolny kod, napriklad na datum */ return false; }
Kód možno na prvý pohľad vyzerá trochu zložitejšie ale za ten ušetrný čas rozhodne stojí. Takže v tomto mojom prípade rozhodne vyhralo WMI. Predpokladám, že najmä vďaka tomu, že sa dajú cez dotaz vyselektovať iba tie položky, ktoré nás ozaj zaujímajú a že selekciu nemusíme robiť programovo.
A ešte malá upútavka na záver. Keď som si chcel testovať dotazy, teda v momente keď som ešte dotaz nemal hotový, ale testovanie cez CIM mi neprišlo príliš užívateľsky príjemné, spomenul som si, že keď som bol na konferencii Advanced.Net, tak tam Michael Juřek v rámci svojej prednášky o WMI používal malú utilitku.
Táto spustí Vami požadovaný dotaz (dokonca disponuje aj pár preddefinovanými dotazmi) a výsledok zobrazí v DataGride. Túto utilitku a zopár ďaľších príkladov, ako aj prezentáciu z jeho prednášky si môžete stiahnuť zo stránky s materiálmi k tejto konferencii - Advanced.Net.
Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.