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