Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / python / logfile.py
1 # -*- test-case-name: twisted.test.test_logfile -*-
2
3 # Copyright (c) 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
16
17 class BaseLogFile:
18     """
19     The base class for a log file that can be rotated.
20     """
21
22     synchronized = ["write", "rotate"]
23
24     def __init__(self, name, directory, defaultMode=None):
25         """
26         Create a log file.
27
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.
32         """
33         self.directory = directory
34         self.name = name
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])
38         else:
39             self.defaultMode = defaultMode
40         self._openFile()
41
42     def fromFullPath(cls, filename, *args, **kwargs):
43         """
44         Construct a log file from a full file path.
45         """
46         logPath = os.path.abspath(filename)
47         return cls(os.path.basename(logPath),
48                    os.path.dirname(logPath), *args, **kwargs)
49     fromFullPath = classmethod(fromFullPath)
50
51     def shouldRotate(self):
52         """
53         Override with a method to that returns true if the log
54         should be rotated.
55         """
56         raise NotImplementedError
57
58     def _openFile(self):
59         """
60         Open the log file.
61         """
62         self.closed = False
63         if os.path.exists(self.path):
64             self._file = file(self.path, "r+", 1)
65             self._file.seek(0, 2)
66         else:
67             if self.defaultMode is not None:
68                 # Set the lowest permissions
69                 oldUmask = os.umask(0777)
70                 try:
71                     self._file = file(self.path, "w+", 1)
72                 finally:
73                     os.umask(oldUmask)
74             else:
75                 self._file = file(self.path, "w+", 1)
76         if self.defaultMode is not None:
77             try:
78                 os.chmod(self.path, self.defaultMode)
79             except OSError:
80                 # Probably /dev/null or something?
81                 pass
82
83     def __getstate__(self):
84         state = self.__dict__.copy()
85         del state["_file"]
86         return state
87
88     def __setstate__(self, state):
89         self.__dict__ = state
90         self._openFile()
91
92     def write(self, data):
93         """
94         Write some data to the file.
95         """
96         if self.shouldRotate():
97             self.flush()
98             self.rotate()
99         self._file.write(data)
100
101     def flush(self):
102         """
103         Flush the file.
104         """
105         self._file.flush()
106
107     def close(self):
108         """
109         Close the file.
110
111         The file cannot be used once it has been closed.
112         """
113         self.closed = True
114         self._file.close()
115         self._file = None
116
117
118     def reopen(self):
119         """
120         Reopen the log file. This is mainly useful if you use an external log
121         rotation tool, which moves under your feet.
122
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.
125         """
126         self.close()
127         self._openFile()
128
129
130     def getCurrentLog(self):
131         """
132         Return a LogReader for the current log file.
133         """
134         return LogReader(self.path)
135
136
137 class LogFile(BaseLogFile):
138     """
139     A log file that can be rotated.
140
141     A rotateLength of None disables automatic log rotation.
142     """
143     def __init__(self, name, directory, rotateLength=1000000, defaultMode=None,
144                  maxRotatedFiles=None):
145         """
146         Create a log file rotating on length.
147
148         @param name: file name.
149         @type name: C{str}
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
153             1M.
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}
160         """
161         BaseLogFile.__init__(self, name, directory, defaultMode)
162         self.rotateLength = rotateLength
163         self.maxRotatedFiles = maxRotatedFiles
164
165     def _openFile(self):
166         BaseLogFile._openFile(self)
167         self.size = self._file.tell()
168
169     def shouldRotate(self):
170         """
171         Rotate when the log file size is larger than rotateLength.
172         """
173         return self.rotateLength and self.size >= self.rotateLength
174
175     def getLog(self, identifier):
176         """
177         Given an integer, return a LogReader for an old log file.
178         """
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)
183
184     def write(self, data):
185         """
186         Write some data to the file.
187         """
188         BaseLogFile.write(self, data)
189         self.size += len(data)
190
191     def rotate(self):
192         """
193         Rotate the file and create a new one.
194
195         If it's not possible to open new logfile, this will fail silently,
196         and continue logging to old logfile.
197         """
198         if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
199             return
200         logs = self.listLogs()
201         logs.reverse()
202         for i in logs:
203             if self.maxRotatedFiles is not None and i >= self.maxRotatedFiles:
204                 os.remove("%s.%d" % (self.path, i))
205             else:
206                 os.rename("%s.%d" % (self.path, i), "%s.%d" % (self.path, i + 1))
207         self._file.close()
208         os.rename(self.path, "%s.1" % self.path)
209         self._openFile()
210
211     def listLogs(self):
212         """
213         Return sorted list of integers - the old logs' identifiers.
214         """
215         result = []
216         for name in glob.glob("%s.*" % self.path):
217             try:
218                 counter = int(name.split('.')[-1])
219                 if counter:
220                     result.append(counter)
221             except ValueError:
222                 pass
223         result.sort()
224         return result
225
226     def __getstate__(self):
227         state = BaseLogFile.__getstate__(self)
228         del state["size"]
229         return state
230
231 threadable.synchronize(LogFile)
232
233
234 class DailyLogFile(BaseLogFile):
235     """A log file that is rotated daily (at or after midnight localtime)
236     """
237     def _openFile(self):
238         BaseLogFile._openFile(self)
239         self.lastDate = self.toDate(os.stat(self.path)[8])
240
241     def shouldRotate(self):
242         """Rotate when the date has changed since last write"""
243         return self.toDate() > self.lastDate
244
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.
248
249         This function primarily exists so you may overload it with
250         gmtime, or some cruft to make unit testing possible.
251         """
252         # primarily so this can be unit tested easily
253         return time.localtime(*args)[:3]
254
255     def suffix(self, tupledate):
256         """Return the suffix given a (year, month, day) tuple or unixtime"""
257         try:
258             return '_'.join(map(str, tupledate))
259         except:
260             # try taking a float unixtime
261             return '_'.join(map(str, self.toDate(tupledate)))
262
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)
271
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())
279
280     def rotate(self):
281         """Rotate the file and create a new one.
282
283         If it's not possible to open new logfile, this will fail silently,
284         and continue logging to old logfile.
285         """
286         if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
287             return
288         newpath = "%s.%s" % (self.path, self.suffix(self.lastDate))
289         if os.path.exists(newpath):
290             return
291         self._file.close()
292         os.rename(self.path, newpath)
293         self._openFile()
294
295     def __getstate__(self):
296         state = BaseLogFile.__getstate__(self)
297         del state["lastDate"]
298         return state
299
300 threadable.synchronize(DailyLogFile)
301
302
303 class LogReader:
304     """Read from a log file."""
305
306     def __init__(self, name):
307         self._file = file(name, "r")
308
309     def readLines(self, lines=10):
310         """Read a list of lines from the log file.
311
312         This doesn't returns all of the files lines - call it multiple times.
313         """
314         result = []
315         for i in range(lines):
316             line = self._file.readline()
317             if not line:
318                 break
319             result.append(line)
320         return result
321
322     def close(self):
323         self._file.close()