1 # -*- test-case-name: twisted.test.test_logfile -*-
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
7 A rotating, browsable log file.
11 import os, glob, time, stat
13 from twisted.python import threadable
19 The base class for a log file that can be rotated.
22 synchronized = ["write", "rotate"]
24 def __init__(self, name, directory, defaultMode=None):
28 @param name: name of the file
29 @param directory: directory holding the file
30 @param defaultMode: permissions used to create the file. Default to
31 current permissions of the file if the file exists.
33 self.directory = directory
35 self.path = os.path.join(directory, name)
36 if defaultMode is None and os.path.exists(self.path):
37 self.defaultMode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
39 self.defaultMode = defaultMode
42 def fromFullPath(cls, filename, *args, **kwargs):
44 Construct a log file from a full file path.
46 logPath = os.path.abspath(filename)
47 return cls(os.path.basename(logPath),
48 os.path.dirname(logPath), *args, **kwargs)
49 fromFullPath = classmethod(fromFullPath)
51 def shouldRotate(self):
53 Override with a method to that returns true if the log
56 raise NotImplementedError
63 if os.path.exists(self.path):
64 self._file = file(self.path, "r+", 1)
67 if self.defaultMode is not None:
68 # Set the lowest permissions
69 oldUmask = os.umask(0777)
71 self._file = file(self.path, "w+", 1)
75 self._file = file(self.path, "w+", 1)
76 if self.defaultMode is not None:
78 os.chmod(self.path, self.defaultMode)
80 # Probably /dev/null or something?
83 def __getstate__(self):
84 state = self.__dict__.copy()
88 def __setstate__(self, state):
92 def write(self, data):
94 Write some data to the file.
96 if self.shouldRotate():
99 self._file.write(data)
111 The file cannot be used once it has been closed.
120 Reopen the log file. This is mainly useful if you use an external log
121 rotation tool, which moves under your feet.
123 Note that on Windows you probably need a specific API to rename the
124 file, as it's not supported to simply use os.rename, for example.
130 def getCurrentLog(self):
132 Return a LogReader for the current log file.
134 return LogReader(self.path)
137 class LogFile(BaseLogFile):
139 A log file that can be rotated.
141 A rotateLength of None disables automatic log rotation.
143 def __init__(self, name, directory, rotateLength=1000000, defaultMode=None,
144 maxRotatedFiles=None):
146 Create a log file rotating on length.
148 @param name: file name.
150 @param directory: path of the log file.
151 @type directory: C{str}
152 @param rotateLength: size of the log file where it rotates. Default to
154 @type rotateLength: C{int}
155 @param defaultMode: mode used to create the file.
156 @type defaultMode: C{int}
157 @param maxRotatedFiles: if not None, max number of log files the class
158 creates. Warning: it removes all log files above this number.
159 @type maxRotatedFiles: C{int}
161 BaseLogFile.__init__(self, name, directory, defaultMode)
162 self.rotateLength = rotateLength
163 self.maxRotatedFiles = maxRotatedFiles
166 BaseLogFile._openFile(self)
167 self.size = self._file.tell()
169 def shouldRotate(self):
171 Rotate when the log file size is larger than rotateLength.
173 return self.rotateLength and self.size >= self.rotateLength
175 def getLog(self, identifier):
177 Given an integer, return a LogReader for an old log file.
179 filename = "%s.%d" % (self.path, identifier)
180 if not os.path.exists(filename):
181 raise ValueError, "no such logfile exists"
182 return LogReader(filename)
184 def write(self, data):
186 Write some data to the file.
188 BaseLogFile.write(self, data)
189 self.size += len(data)
193 Rotate the file and create a new one.
195 If it's not possible to open new logfile, this will fail silently,
196 and continue logging to old logfile.
198 if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
200 logs = self.listLogs()
203 if self.maxRotatedFiles is not None and i >= self.maxRotatedFiles:
204 os.remove("%s.%d" % (self.path, i))
206 os.rename("%s.%d" % (self.path, i), "%s.%d" % (self.path, i + 1))
208 os.rename(self.path, "%s.1" % self.path)
213 Return sorted list of integers - the old logs' identifiers.
216 for name in glob.glob("%s.*" % self.path):
218 counter = int(name.split('.')[-1])
220 result.append(counter)
226 def __getstate__(self):
227 state = BaseLogFile.__getstate__(self)
231 threadable.synchronize(LogFile)
234 class DailyLogFile(BaseLogFile):
235 """A log file that is rotated daily (at or after midnight localtime)
238 BaseLogFile._openFile(self)
239 self.lastDate = self.toDate(os.stat(self.path)[8])
241 def shouldRotate(self):
242 """Rotate when the date has changed since last write"""
243 return self.toDate() > self.lastDate
245 def toDate(self, *args):
246 """Convert a unixtime to (year, month, day) localtime tuple,
247 or return the current (year, month, day) localtime tuple.
249 This function primarily exists so you may overload it with
250 gmtime, or some cruft to make unit testing possible.
252 # primarily so this can be unit tested easily
253 return time.localtime(*args)[:3]
255 def suffix(self, tupledate):
256 """Return the suffix given a (year, month, day) tuple or unixtime"""
258 return '_'.join(map(str, tupledate))
260 # try taking a float unixtime
261 return '_'.join(map(str, self.toDate(tupledate)))
263 def getLog(self, identifier):
264 """Given a unix time, return a LogReader for an old log file."""
265 if self.toDate(identifier) == self.lastDate:
266 return self.getCurrentLog()
267 filename = "%s.%s" % (self.path, self.suffix(identifier))
268 if not os.path.exists(filename):
269 raise ValueError, "no such logfile exists"
270 return LogReader(filename)
272 def write(self, data):
273 """Write some data to the log file"""
274 BaseLogFile.write(self, data)
275 # Guard against a corner case where time.time()
276 # could potentially run backwards to yesterday.
277 # Primarily due to network time.
278 self.lastDate = max(self.lastDate, self.toDate())
281 """Rotate the file and create a new one.
283 If it's not possible to open new logfile, this will fail silently,
284 and continue logging to old logfile.
286 if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
288 newpath = "%s.%s" % (self.path, self.suffix(self.lastDate))
289 if os.path.exists(newpath):
292 os.rename(self.path, newpath)
295 def __getstate__(self):
296 state = BaseLogFile.__getstate__(self)
297 del state["lastDate"]
300 threadable.synchronize(DailyLogFile)
304 """Read from a log file."""
306 def __init__(self, name):
307 self._file = file(name, "r")
309 def readLines(self, lines=10):
310 """Read a list of lines from the log file.
312 This doesn't returns all of the files lines - call it multiple times.
315 for i in range(lines):
316 line = self._file.readline()