import os import time class RotatingLog: """ A file() (or open()) replacement that will automatically open and write to a new file if the prior file is too large or after a time interval. """ def __init__(self, path="./log_file", hourInterval=24, megabyteLimit=1024): """ path is a full or partial path with file name. hourInterval is the number of hours at which to rotate the file. megabyteLimit is the number of megabytes of file size the log may grow to, after which the log is rotated. Note: The log file may get a bit larger than limit do to writing out whole lines (last line may exceed megabyteLimit or "megabyteGuidline"). """ self.path=path self.timeInterval=None self.timeLimit=None self.sizeLimit=None if hourInterval is not None: self.timeInterval=hourInterval*60*60 self.timeLimit=time.time()+self.timeInterval if megabyteLimit is not None: self.sizeLimit=megabyteLimit*1024*1024 def __del__(self): self.close() def close(self): if hasattr(self, "file"): self.file.flush() self.file.close() self.closed = self.file.closed del self.file else: self.closed = 1 def shouldRotate(self): """ Returns a bool about whether a new log file should be created and written to (while at the same time stopping output to the old log file and closing it). """ if not hasattr(self, "file"): return 1 if self.timeLimit is not None and time.time() > self.timeLimit: return 1 if self.sizeLimit is not None and self.file.tell() > self.sizeLimit: return 1 return 0 def filePath(self): dateString=time.strftime("%Y_%m_%d_%H", time.localtime()) for i in range(26): path="%s_%s_%s.log"%(self.path, dateString, chr(i+97)) if not os.path.exists(path) or os.stat(path)[6] < self.sizeLimit: return path # Hmm, 26 files are full? throw the rest in z: # Maybe we should clear the self.sizeLimit here... maybe. return path def rotate(self): """ Rotate the log now. You normally shouldn't need to call this. See write(). """ path=self.filePath() file=open(path, "a") if file: self.close() # This should be redundant with "a" open() mode, # but on some platforms tell() will return 0 # until the first write: file.seek(0, 2) self.file=file # Some of these data members may be expected by some of our clients: self.closed = self.file.closed self.mode = self.file.mode self.name = self.file.name self.softspace = self.file.softspace #self.encoding = self.file.encoding # Python 2.3 #self.newlines = self.file.newlines # Python 2.3, maybe if self.timeLimit is not None and time.time() > self.timeLimit: self.timeLimit=time.time()+self.timeInterval else: # We'll keep writing to the old file, if available. print "RotatingLog error: Unable to open new log file \"%s\"."%(path,) def write(self, data): """ Write the data to either the current log or a new one, depending on the return of shouldRotate() and whether the new file can be opened. """ if self.shouldRotate(): self.rotate() if hasattr(self, "file"): r = self.file.write(data) self.file.flush() return r def flush(self): return self.file.flush() def fileno(self): return self.file.fileno() def isatty(self): return self.file.isatty() def next(self): return self.file.next() def read(self, size): return self.file.read(size) def readline(self, size): return self.file.readline(size) def readlines(self, sizehint): return self.file.readlines(sizehint) def xreadlines(self): return self.file.xreadlines() def seek(self, offset, whence=0): return self.file.seek(offset, whence) def tell(self): return self.file.tell() def truncate(self, size): return self.file.truncate(size) def writelines(self, sequence): return self.file.writelines(sequence)