1
# -*- test-case-name: twisted.test.test_logfile -*-
3
# Copyright (c) 2001-2007 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
34
assert os.path.isdir(self.directory)
36
self.path = os.path.join(directory, name)
37
if defaultMode is None and os.path.exists(self.path):
38
self.defaultMode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
40
self.defaultMode = defaultMode
43
def fromFullPath(cls, filename, *args, **kwargs):
45
Construct a log file from a full file path.
47
logPath = os.path.abspath(filename)
48
return cls(os.path.basename(logPath),
49
os.path.dirname(logPath), *args, **kwargs)
50
fromFullPath = classmethod(fromFullPath)
52
def shouldRotate(self):
54
Override with a method to that returns true if the log
57
raise NotImplementedError
64
if os.path.exists(self.path):
65
self._file = file(self.path, "r+", 1)
68
if self.defaultMode is not None:
69
# Set the lowest permissions
70
oldUmask = os.umask(0777)
72
self._file = file(self.path, "w+", 1)
76
self._file = file(self.path, "w+", 1)
77
if self.defaultMode is not None:
79
os.chmod(self.path, self.defaultMode)
81
# Probably /dev/null or something?
84
def __getstate__(self):
85
state = self.__dict__.copy()
89
def __setstate__(self, state):
93
def write(self, data):
95
Write some data to the file.
97
if self.shouldRotate():
100
self._file.write(data)
112
The file cannot be used once it has been closed.
121
Reopen the log file. This is mainly useful if you use an external log
122
rotation tool, which moves under your feet.
124
Note that on Windows you probably need a specific API to rename the
125
file, as it's not supported to simply use os.rename, for example.
131
def getCurrentLog(self):
133
Return a LogReader for the current log file.
135
return LogReader(self.path)
138
class LogFile(BaseLogFile):
140
A log file that can be rotated.
142
A rotateLength of None disables automatic log rotation.
144
def __init__(self, name, directory, rotateLength=1000000, defaultMode=None,
145
maxRotatedFiles=None):
147
Create a log file rotating on length.
149
@param name: file name.
151
@param directory: path of the log file.
152
@type directory: C{str}
153
@param rotateLength: size of the log file where it rotates. Default to
155
@type rotateLength: C{int}
156
@param defaultMode: mode used to create the file.
157
@type defaultMode: C{int}
158
@param maxRotatedFiles: if not None, max number of log files the class
159
creates. Warning: it removes all log files above this number.
160
@type maxRotatedFiles: C{int}
162
BaseLogFile.__init__(self, name, directory, defaultMode)
163
self.rotateLength = rotateLength
164
self.maxRotatedFiles = maxRotatedFiles
167
BaseLogFile._openFile(self)
168
self.size = self._file.tell()
170
def shouldRotate(self):
172
Rotate when the log file size is larger than rotateLength.
174
return self.rotateLength and self.size >= self.rotateLength
176
def getLog(self, identifier):
178
Given an integer, return a LogReader for an old log file.
180
filename = "%s.%d" % (self.path, identifier)
181
if not os.path.exists(filename):
182
raise ValueError, "no such logfile exists"
183
return LogReader(filename)
185
def write(self, data):
187
Write some data to the file.
189
BaseLogFile.write(self, data)
190
self.size += len(data)
194
Rotate the file and create a new one.
196
If it's not possible to open new logfile, this will fail silently,
197
and continue logging to old logfile.
199
if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
201
logs = self.listLogs()
204
if self.maxRotatedFiles is not None and i >= self.maxRotatedFiles:
205
os.remove("%s.%d" % (self.path, i))
207
os.rename("%s.%d" % (self.path, i), "%s.%d" % (self.path, i + 1))
209
os.rename(self.path, "%s.1" % self.path)
214
Return sorted list of integers - the old logs' identifiers.
217
for name in glob.glob("%s.*" % self.path):
219
counter = int(name.split('.')[-1])
221
result.append(counter)
227
def __getstate__(self):
228
state = BaseLogFile.__getstate__(self)
232
threadable.synchronize(LogFile)
235
class DailyLogFile(BaseLogFile):
236
"""A log file that is rotated daily (at or after midnight localtime)
239
BaseLogFile._openFile(self)
240
self.lastDate = self.toDate(os.stat(self.path)[8])
242
def shouldRotate(self):
243
"""Rotate when the date has changed since last write"""
244
return self.toDate() > self.lastDate
246
def toDate(self, *args):
247
"""Convert a unixtime to (year, month, day) localtime tuple,
248
or return the current (year, month, day) localtime tuple.
250
This function primarily exists so you may overload it with
251
gmtime, or some cruft to make unit testing possible.
253
# primarily so this can be unit tested easily
254
return time.localtime(*args)[:3]
256
def suffix(self, tupledate):
257
"""Return the suffix given a (year, month, day) tuple or unixtime"""
259
return '_'.join(map(str, tupledate))
261
# try taking a float unixtime
262
return '_'.join(map(str, self.toDate(tupledate)))
264
def getLog(self, identifier):
265
"""Given a unix time, return a LogReader for an old log file."""
266
if self.toDate(identifier) == self.lastDate:
267
return self.getCurrentLog()
268
filename = "%s.%s" % (self.path, self.suffix(identifier))
269
if not os.path.exists(filename):
270
raise ValueError, "no such logfile exists"
271
return LogReader(filename)
273
def write(self, data):
274
"""Write some data to the log file"""
275
BaseLogFile.write(self, data)
276
# Guard against a corner case where time.time()
277
# could potentially run backwards to yesterday.
278
# Primarily due to network time.
279
self.lastDate = max(self.lastDate, self.toDate())
282
"""Rotate the file and create a new one.
284
If it's not possible to open new logfile, this will fail silently,
285
and continue logging to old logfile.
287
if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
289
newpath = "%s.%s" % (self.path, self.suffix(self.lastDate))
290
if os.path.exists(newpath):
293
os.rename(self.path, newpath)
296
def __getstate__(self):
297
state = BaseLogFile.__getstate__(self)
298
del state["lastDate"]
301
threadable.synchronize(DailyLogFile)
305
"""Read from a log file."""
307
def __init__(self, name):
308
self._file = file(name, "r")
310
def readLines(self, lines=10):
311
"""Read a list of lines from the log file.
313
This doesn't returns all of the files lines - call it multiple times.
316
for i in range(lines):
317
line = self._file.readline()