Windows NT Eventlog and Threading
Python's threading to manage access access to many Eventlogs
If you need to access the eventlog of many servers, it can take
quite some time if you go through a list sequentially -- since you
need to wait for each operation to complete. There are a few ways you
can do this in python. You can spawn many processes, use python's
win32 libraries for win32 style-threading, or use python's native
libraries.
In NT spawning a process is slow (though in this case that may not
matter) and it can be a little cumbersome to get them to share
data. Since python's win32 libraries are thread-safe, you can use
python's win32 libraries to do win32-style threading or use python's
native threading library to spawn threads. Win32 style threading is
available via python's win32process library. It gives you excellent
control and ability to do some sophisticated stuff, however raw win32
threaded programming can be tricky. The final option is Python's
native high-level threading library which is built on top of a lower
level thread library. It turns out that it is easy to use and
cross-platform (well at least to platforms that support
threading). It's only disadvantage is that your thread
control(synchronization, giving them priorities, suspending them,
etc.) is limited. In many cases, however, that is not important, you
just need the ability to _easily_ do a few things at once. Thus,
we'll focus on Python's native threading.
One final note, the python interpreter has a global lock for
threads forcing them to run serially. Which means only one thread can
intrepret python code at any given time. After a certain amount of
python code has been run, then control is switched to another
thread. Thus, this means you can't have things like many threads
running on many processors. Fortunately, in cases with I/O and
extension modules (in which you can manipulate the interpreter lock),
this isn't an issue. In our case (accessing the eventlog of many
servers) the global lock shouldn't be a bottleneck(i/o will be).
There are a couple of ways to use the threading library. We'll
look at the case in which you override the run method of the
threading.Thread class. The run method will contain the actual
code you want to run many times.
The basic procedure to follow is this.
For every server in the list:
1)create a thread class
2)call start method in thread class(which invokes your run method)
3)call join method to force main thread to wait for threads to complete
4)compile data together from all thread classes created.
Since you don't use the return values of the thread, you need to store data with the thread object that you create. Furthermore, you'll notice we use the join method to force the calling thread to wait for the other threads to finish.
Example
Here is the skeleton of that:
#We are overiding run() method of the threading.Thread class. class thread_it ( threading.Thread ) : def __init__ ( self, server) : threading.Thread.__init__(self) self.data=[] #store data here to get later self.server=server # the start() method invokes run def run ( self ): #overridden from threading library try: pass #get event information here and store in data except: #append any errors to self.data, to get later self.data.append('Error for '+self.server+':'+str(traceback.print_exc(sys.exc_info()))) ######main here try: l_servers=('fred','barney','wilma','betty') for server in l_servers: #make a thread for each server thread = thread_it (server) threads.append ( thread ) #append to the a threads list for thread in threads: #now go thru list and start threads running thread.start() for thread in threads: #make main thread wait for all in list to complete thread.join() for thread in threads: #print thread results for event in thread.data: print event except: print traceback.print_exc(sys.exc_info())Looking at the skeleton, all one really needs to do is put the eventlog code in the run function and have it store results in the data variable. The main part is almost boilerplate code. So you can see multi-threading at work, I've added a 'now' column to the eventlog printout which tells you the current time, and easily shows that the data was gathered simultaneously on the servers.
Filling out the skeleton we get code like this:
import win32evtlog import win32evtlogutil import win32security import win32con import winerror import time import re import string import sys import threading import traceback #We are overiding run() method of the threading.Thread class. class thread_it ( threading.Thread ) : def __init__ ( self, server) : threading.Thread.__init__(self) self.data=[] #store data here to get later self.server=server # the start() method invokes run def run ( self): #overridden from threading library flags = win32evtlog.EVENTLOG_BACKWARDS_READ|\\ win32evtlog.EVENTLOG_SEQUENTIAL_READ #This dict converts the event type into a human readable form evt_dict={win32con.EVENTLOG_AUDIT_FAILURE:'EVENTLOG_AUDIT_FAILURE',\\ win32con.EVENTLOG_AUDIT_SUCCESS:'EVENTLOG_AUDIT_SUCCESS',\\ win32con.EVENTLOG_INFORMATION_TYPE:'EVENTLOG_INFORMATION_TYPE',\\ win32con.EVENTLOG_WARNING_TYPE:'EVENTLOG_WARNING_TYPE',\\ win32con.EVENTLOG_ERROR_TYPE:'EVENTLOG_ERROR_TYPE'} logtype='System' begin_sec=time.time() begin_time=time.strftime('%H:%M:%S ',time.localtime(begin_sec)) try: hand=win32evtlog.OpenEventLog(self.server,logtype) #open event log here self.data.append('events found in the last 8 hours since:'+begin_time+'for '+self.server) events=1 while events: events=win32evtlog.ReadEventLog(hand,flags,0) for ev_obj in events: now_sec=time.time() now_time=time.strftime('now=%H:%M:%S ',time.localtime(now_sec)) #check if the event is recent enough #only want data from last 8hrs the_time=ev_obj.TimeGenerated.Format() seconds=self.date2sec(the_time) if seconds < begin_sec-28800: break #data is recent enough, so print it out computer=str(ev_obj.ComputerName) cat=str(ev_obj.EventCategory) src=str(ev_obj.SourceName) record=str(ev_obj.RecordNumber) evt_id=str(winerror.HRESULT_CODE(ev_obj.EventID)) evt_type=str(evt_dict[ev_obj.EventType]) msg = str(win32evtlogutil.SafeFormatMessage(ev_obj, logtype)) results=string.join((now_time,the_time,computer,src,cat,record,evt_id,evt_type,msg[0:15]),':') self.data.append(results) if seconds < begin_sec-28800: break win32evtlog.CloseEventLog(hand) except: self.data.append('Error for '+self.server+':'+str(traceback.print_exc(sys.exc_info()))) def date2sec(self,evt_date): ''' This function converts dates with format '12/23/99 15:54:09' to seconds since 1970. ''' regexp=re.compile('(.*)\\s(.*)') #store result in site reg_result=regexp.search(evt_date) date=reg_result.group(1) the_time=reg_result.group(2) (mon,day,yr)=map(lambda x: string.atoi(x),string.split(date,'/')) (hr,min,sec)=map(lambda x: string.atoi(x),string.split(the_time,':')) tup=[yr,mon,day,hr,min,sec,0,0,0] sec=time.mktime(tup) return sec ######main here try: threads=[] data=[] l_servers=['barney','betty','fred','wilma'] for server in l_servers: #make a thread for each server thread = thread_it (server) threads.append ( thread ) #append to the a threads list for thread in threads: #now go thru list and start threads running thread.start() for thread in threads: #make main thread wait for all in list to complete thread.join() for thread in threads: #compile all of the threads' data together. print '###############' for event in thread.data: print event except: print traceback.print_exc(sys.exc_info())A very nice addition to this would be to convert it to a web application. HTMLgen is a useful tool in this context.
Have a great time with programming with python!
John Nielsen nielsenjf@my-deja.com