| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 # -*- test-case-name: twisted.test.test_logfile -*- |  | 
| 2 |  | 
| 3 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. |  | 
| 4 # See LICENSE for details. |  | 
| 5 |  | 
| 6 """ |  | 
| 7 A rotating, browsable log file. |  | 
| 8 """ |  | 
| 9 |  | 
| 10 # System Imports |  | 
| 11 import os, glob, time, stat |  | 
| 12 |  | 
| 13 from twisted.python import threadable |  | 
| 14 |  | 
| 15 class BaseLogFile: |  | 
| 16     """ |  | 
| 17     The base class for a log file that can be rotated. |  | 
| 18     """ |  | 
| 19 |  | 
| 20     synchronized = ["write", "rotate"] |  | 
| 21 |  | 
| 22     def __init__(self, name, directory, defaultMode=None): |  | 
| 23         """ |  | 
| 24         Create a log file. |  | 
| 25 |  | 
| 26         @param name: name of the file |  | 
| 27         @param directory: directory holding the file |  | 
| 28         @param defaultMode: permissions used to create the file. Default to |  | 
| 29         current permissions of the file if the file exists. |  | 
| 30         """ |  | 
| 31         self.directory = directory |  | 
| 32         assert os.path.isdir(self.directory) |  | 
| 33         self.name = name |  | 
| 34         self.path = os.path.join(directory, name) |  | 
| 35         if defaultMode is None and os.path.exists(self.path): |  | 
| 36             self.defaultMode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE]) |  | 
| 37         else: |  | 
| 38             self.defaultMode = defaultMode |  | 
| 39         self._openFile() |  | 
| 40 |  | 
| 41     def fromFullPath(cls, filename, *args, **kwargs): |  | 
| 42         """ |  | 
| 43         Construct a log file from a full file path. |  | 
| 44         """ |  | 
| 45         logPath = os.path.abspath(filename) |  | 
| 46         return cls(os.path.basename(logPath), |  | 
| 47                    os.path.dirname(logPath), *args, **kwargs) |  | 
| 48     fromFullPath = classmethod(fromFullPath) |  | 
| 49 |  | 
| 50     def shouldRotate(self): |  | 
| 51         """ |  | 
| 52         Override with a method to that returns true if the log |  | 
| 53         should be rotated. |  | 
| 54         """ |  | 
| 55         raise NotImplementedError |  | 
| 56 |  | 
| 57     def _openFile(self): |  | 
| 58         """ |  | 
| 59         Open the log file. |  | 
| 60         """ |  | 
| 61         self.closed = False |  | 
| 62         if os.path.exists(self.path): |  | 
| 63             self._file = file(self.path, "r+", 1) |  | 
| 64             self._file.seek(0, 2) |  | 
| 65         else: |  | 
| 66             if self.defaultMode is not None: |  | 
| 67                 # Set the lowest permissions |  | 
| 68                 oldUmask = os.umask(0777) |  | 
| 69                 try: |  | 
| 70                     self._file = file(self.path, "w+", 1) |  | 
| 71                 finally: |  | 
| 72                     os.umask(oldUmask) |  | 
| 73             else: |  | 
| 74                 self._file = file(self.path, "w+", 1) |  | 
| 75         if self.defaultMode is not None: |  | 
| 76             try: |  | 
| 77                 os.chmod(self.path, self.defaultMode) |  | 
| 78             except OSError: |  | 
| 79                 # Probably /dev/null or something? |  | 
| 80                 pass |  | 
| 81 |  | 
| 82     def __getstate__(self): |  | 
| 83         state = self.__dict__.copy() |  | 
| 84         del state["_file"] |  | 
| 85         return state |  | 
| 86 |  | 
| 87     def __setstate__(self, state): |  | 
| 88         self.__dict__ = state |  | 
| 89         self._openFile() |  | 
| 90 |  | 
| 91     def write(self, data): |  | 
| 92         """ |  | 
| 93         Write some data to the file. |  | 
| 94         """ |  | 
| 95         if self.shouldRotate(): |  | 
| 96             self.flush() |  | 
| 97             self.rotate() |  | 
| 98         self._file.write(data) |  | 
| 99 |  | 
| 100     def flush(self): |  | 
| 101         """ |  | 
| 102         Flush the file. |  | 
| 103         """ |  | 
| 104         self._file.flush() |  | 
| 105 |  | 
| 106     def close(self): |  | 
| 107         """ |  | 
| 108         Close the file. |  | 
| 109 |  | 
| 110         The file cannot be used once it has been closed. |  | 
| 111         """ |  | 
| 112         self.closed = True |  | 
| 113         self._file.close() |  | 
| 114         self._file = None |  | 
| 115 |  | 
| 116     def getCurrentLog(self): |  | 
| 117         """ |  | 
| 118         Return a LogReader for the current log file. |  | 
| 119         """ |  | 
| 120         return LogReader(self.path) |  | 
| 121 |  | 
| 122 |  | 
| 123 class LogFile(BaseLogFile): |  | 
| 124     """ |  | 
| 125     A log file that can be rotated. |  | 
| 126 |  | 
| 127     A rotateLength of None disables automatic log rotation. |  | 
| 128     """ |  | 
| 129     def __init__(self, name, directory, rotateLength=1000000, defaultMode=None, |  | 
| 130                  maxRotatedFiles=None): |  | 
| 131         """ |  | 
| 132         Create a log file rotating on length. |  | 
| 133 |  | 
| 134         @param name: file name. |  | 
| 135         @type name: C{str} |  | 
| 136         @param directory: path of the log file. |  | 
| 137         @type directory: C{str} |  | 
| 138         @param rotateLength: size of the log file where it rotates. Default to |  | 
| 139             1M. |  | 
| 140         @type rotateLength: C{int} |  | 
| 141         @param defaultMode: mode used to create the file. |  | 
| 142         @type defaultMode: C{int} |  | 
| 143         @param maxRotatedFiles: if not None, max number of log files the class |  | 
| 144             creates. Warning: it removes all log files above this number. |  | 
| 145         @type maxRotatedFiles: C{int} |  | 
| 146         """ |  | 
| 147         BaseLogFile.__init__(self, name, directory, defaultMode) |  | 
| 148         self.rotateLength = rotateLength |  | 
| 149         self.maxRotatedFiles = maxRotatedFiles |  | 
| 150 |  | 
| 151     def _openFile(self): |  | 
| 152         BaseLogFile._openFile(self) |  | 
| 153         self.size = self._file.tell() |  | 
| 154 |  | 
| 155     def shouldRotate(self): |  | 
| 156         """ |  | 
| 157         Rotate when the log file size is larger than rotateLength. |  | 
| 158         """ |  | 
| 159         return self.rotateLength and self.size >= self.rotateLength |  | 
| 160 |  | 
| 161     def getLog(self, identifier): |  | 
| 162         """ |  | 
| 163         Given an integer, return a LogReader for an old log file. |  | 
| 164         """ |  | 
| 165         filename = "%s.%d" % (self.path, identifier) |  | 
| 166         if not os.path.exists(filename): |  | 
| 167             raise ValueError, "no such logfile exists" |  | 
| 168         return LogReader(filename) |  | 
| 169 |  | 
| 170     def write(self, data): |  | 
| 171         """ |  | 
| 172         Write some data to the file. |  | 
| 173         """ |  | 
| 174         BaseLogFile.write(self, data) |  | 
| 175         self.size += len(data) |  | 
| 176 |  | 
| 177     def rotate(self): |  | 
| 178         """ |  | 
| 179         Rotate the file and create a new one. |  | 
| 180 |  | 
| 181         If it's not possible to open new logfile, this will fail silently, |  | 
| 182         and continue logging to old logfile. |  | 
| 183         """ |  | 
| 184         if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W
     _OK)): |  | 
| 185             return |  | 
| 186         logs = self.listLogs() |  | 
| 187         logs.reverse() |  | 
| 188         for i in logs: |  | 
| 189             if self.maxRotatedFiles is not None and i >= self.maxRotatedFiles: |  | 
| 190                 os.remove("%s.%d" % (self.path, i)) |  | 
| 191             else: |  | 
| 192                 os.rename("%s.%d" % (self.path, i), "%s.%d" % (self.path, i + 1)
     ) |  | 
| 193         self._file.close() |  | 
| 194         os.rename(self.path, "%s.1" % self.path) |  | 
| 195         self._openFile() |  | 
| 196 |  | 
| 197     def listLogs(self): |  | 
| 198         """ |  | 
| 199         Return sorted list of integers - the old logs' identifiers. |  | 
| 200         """ |  | 
| 201         result = [] |  | 
| 202         for name in glob.glob("%s.*" % self.path): |  | 
| 203             try: |  | 
| 204                 counter = int(name.split('.')[-1]) |  | 
| 205                 if counter: |  | 
| 206                     result.append(counter) |  | 
| 207             except ValueError: |  | 
| 208                 pass |  | 
| 209         result.sort() |  | 
| 210         return result |  | 
| 211 |  | 
| 212     def __getstate__(self): |  | 
| 213         state = BaseLogFile.__getstate__(self) |  | 
| 214         del state["size"] |  | 
| 215         return state |  | 
| 216 |  | 
| 217 threadable.synchronize(LogFile) |  | 
| 218 |  | 
| 219 |  | 
| 220 class DailyLogFile(BaseLogFile): |  | 
| 221     """A log file that is rotated daily (at or after midnight localtime) |  | 
| 222     """ |  | 
| 223     def _openFile(self): |  | 
| 224         BaseLogFile._openFile(self) |  | 
| 225         self.lastDate = self.toDate(os.stat(self.path)[8]) |  | 
| 226 |  | 
| 227     def shouldRotate(self): |  | 
| 228         """Rotate when the date has changed since last write""" |  | 
| 229         return self.toDate() > self.lastDate |  | 
| 230 |  | 
| 231     def toDate(self, *args): |  | 
| 232         """Convert a unixtime to (year, month, day) localtime tuple, |  | 
| 233         or return the current (year, month, day) localtime tuple. |  | 
| 234 |  | 
| 235         This function primarily exists so you may overload it with |  | 
| 236         gmtime, or some cruft to make unit testing possible. |  | 
| 237         """ |  | 
| 238         # primarily so this can be unit tested easily |  | 
| 239         return time.localtime(*args)[:3] |  | 
| 240 |  | 
| 241     def suffix(self, tupledate): |  | 
| 242         """Return the suffix given a (year, month, day) tuple or unixtime""" |  | 
| 243         try: |  | 
| 244             return '_'.join(map(str, tupledate)) |  | 
| 245         except: |  | 
| 246             # try taking a float unixtime |  | 
| 247             return '_'.join(map(str, self.toDate(tupledate))) |  | 
| 248 |  | 
| 249     def getLog(self, identifier): |  | 
| 250         """Given a unix time, return a LogReader for an old log file.""" |  | 
| 251         if self.toDate(identifier) == self.lastDate: |  | 
| 252             return self.getCurrentLog() |  | 
| 253         filename = "%s.%s" % (self.path, self.suffix(identifier)) |  | 
| 254         if not os.path.exists(filename): |  | 
| 255             raise ValueError, "no such logfile exists" |  | 
| 256         return LogReader(filename) |  | 
| 257 |  | 
| 258     def write(self, data): |  | 
| 259         """Write some data to the log file""" |  | 
| 260         BaseLogFile.write(self, data) |  | 
| 261         # Guard against a corner case where time.time() |  | 
| 262         # could potentially run backwards to yesterday. |  | 
| 263         # Primarily due to network time. |  | 
| 264         self.lastDate = max(self.lastDate, self.toDate()) |  | 
| 265 |  | 
| 266     def rotate(self): |  | 
| 267         """Rotate the file and create a new one. |  | 
| 268 |  | 
| 269         If it's not possible to open new logfile, this will fail silently, |  | 
| 270         and continue logging to old logfile. |  | 
| 271         """ |  | 
| 272         if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W
     _OK)): |  | 
| 273             return |  | 
| 274         newpath = "%s.%s" % (self.path, self.suffix(self.lastDate)) |  | 
| 275         if os.path.exists(newpath): |  | 
| 276             return |  | 
| 277         self._file.close() |  | 
| 278         os.rename(self.path, newpath) |  | 
| 279         self._openFile() |  | 
| 280 |  | 
| 281     def __getstate__(self): |  | 
| 282         state = BaseLogFile.__getstate__(self) |  | 
| 283         del state["lastDate"] |  | 
| 284         return state |  | 
| 285 |  | 
| 286 threadable.synchronize(DailyLogFile) |  | 
| 287 |  | 
| 288 |  | 
| 289 class LogReader: |  | 
| 290     """Read from a log file.""" |  | 
| 291 |  | 
| 292     def __init__(self, name): |  | 
| 293         self._file = file(name, "r") |  | 
| 294 |  | 
| 295     def readLines(self, lines=10): |  | 
| 296         """Read a list of lines from the log file. |  | 
| 297 |  | 
| 298         This doesn't returns all of the files lines - call it multiple times. |  | 
| 299         """ |  | 
| 300         result = [] |  | 
| 301         for i in range(lines): |  | 
| 302             line = self._file.readline() |  | 
| 303             if not line: |  | 
| 304                 break |  | 
| 305             result.append(line) |  | 
| 306         return result |  | 
| 307 |  | 
| 308     def close(self): |  | 
| 309         self._file.close() |  | 
| OLD | NEW | 
|---|