Windows NT Event Log: WMI vs. EventLog class

Publikoval Michal Kočí dňa 11.04.2005 o 22: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:

  • musíte vedieť, akú triedu a z akého namespace chcete použiť - tu to chce trochu hravosti, alebo surfovania. Prezerať si dostupné namespaces a ich triedy môžete napríklad cez CIM (WMI Administrative Tools)
  • keď si necháte vyhľadať triedy na základe dotazu, vráti sa Vám kolekcia objektov typu ManagementObject - ovšem, nižšie spomenutý program z .Net Framework SDK Vám prácu uľahčí a vytvorí Vám silne typovú triedu presne podľa triedy, s ktorou chcete pracovať
  • WMI pracuje s dátumom vo zvláštnom formáte - našťastie, utilita, ktorú spomeniem nižšie vygeneruje aj metódy (síce private, ale veď zmeniť ich na public je ľahké), ktoré vedia konvertovať medzi týmto dátumom a dátumom reprezentovaným objektom typu DateTime

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.

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

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.

Komentáre

K tomuto článku nie su pridané žiadne komentáre.

Pridať komentár

Máš niečo zaujímavé povedať k článku? Pridaj to k článku ako komentár. Spam, reklamu alebo inak nerelevantné komentáre okamžite mažem.

Pridanie komentára sa nepodarilo. Oprav si prosím chyby.