1 """ |
1 """ |
2 `tail -f` style continuous file reads. |
2 Iterate over input lines in files. |
3 |
3 |
4 Checks if the file was renamed on EOF, and reopens if so. |
4 Can be read up to eof, on blocking inputs, or polled with a timeout (see: pvl.syslog.syslog.SyslogSource) |
5 """ |
5 """ |
6 |
6 |
7 import os |
7 import os |
8 |
8 |
9 import logging; log = logging.getLogger('pvl.collectd.tail') |
9 import logging; log = logging.getLogger('pvl.syslog.tail') |
10 |
10 |
11 class TailFile (object) : |
11 class Tail (object) : |
12 """ |
12 """ |
13 A file on the filesystem, that is appended to. |
13 Follow a file-like object, reading lines until EOF. |
|
14 |
|
15 Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`. |
14 """ |
16 """ |
15 |
17 |
16 def __init__ (self, path, skip=None) : |
18 def __init__ (self, file) : |
17 self.path = path |
19 self.file = file |
18 self._file = None |
|
19 self._id = None |
|
20 |
20 |
21 self._open() |
21 def readline (self) : |
|
22 """ |
|
23 Reads a line from the file, without trailing \n |
22 |
24 |
23 if skip : |
25 Raises EOF on end-of-file. |
24 for line in self : |
|
25 pass |
|
26 |
|
27 @property |
|
28 def file (self) : |
|
29 """ |
|
30 The underlying file objct, if opened. |
|
31 """ |
26 """ |
32 |
27 |
33 if self._file is None : |
28 line = self.file.readline() |
34 raise ValueError("I/O operation on closed file: {0}".format(self.path)) |
|
35 |
29 |
36 return self._file |
30 if not line : |
|
31 raise EOFError() |
|
32 else : |
|
33 return line.rstrip('\n') |
37 |
34 |
38 @property |
35 def readlines (self) : |
39 def fd (self) : |
|
40 return self.file.fileno() |
|
41 |
|
42 def fileno (self) : |
|
43 """ |
36 """ |
44 The underlying OS fd. |
37 Reads any available lines from the file. |
45 """ |
38 """ |
46 |
39 |
47 return self.fd |
40 while True : |
|
41 try : |
|
42 line = self.readline() |
48 |
43 |
49 def _stat (self) : |
44 except EOFError : |
|
45 log.debug("EOF") |
|
46 break |
|
47 |
|
48 else : |
|
49 yield line |
|
50 |
|
51 def skip (self) : |
|
52 """ |
|
53 Skip any available lines. |
|
54 """ |
|
55 |
|
56 for line in self.readlines() : |
|
57 pass |
|
58 |
|
59 __iter__ = readlines |
|
60 |
|
61 class TailFile (Tail) : |
|
62 """ |
|
63 Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced. |
|
64 """ |
|
65 |
|
66 def __init__ (self, path) : |
|
67 self.path = path |
|
68 self.reopen() |
|
69 |
|
70 def stat (cls) : |
50 """ |
71 """ |
51 Return a key identifying the file at our path. |
72 Return a key identifying the file at our path. |
52 """ |
73 """ |
53 |
74 |
54 st = os.stat(self.path) |
75 st = os.stat(self.path) |
55 |
76 |
56 return st.st_dev, st.st_ino |
77 return st.st_dev, st.st_ino |
|
78 |
|
79 def open (cls) : |
|
80 return open(self.path, 'r') |
57 |
81 |
58 def _open (self) : |
82 def reopen (cls) : |
59 assert self._file is None |
83 self.file = self.open() |
|
84 self._stat = self.stat() |
60 |
85 |
61 self._file = open(self.path, 'r') |
86 def changed (self) : |
62 self._id = self._stat() |
|
63 |
|
64 def _close (self) : |
|
65 assert self._file is not None |
|
66 |
|
67 self._file.close() |
|
68 self._file = None |
|
69 |
|
70 def _reopen (self) : |
|
71 """ |
|
72 Re-open, in case the file changed.. |
|
73 """ |
|
74 |
|
75 self._close() |
|
76 self._open() |
|
77 |
|
78 def _changed (self) : |
|
79 """ |
87 """ |
80 Has the underlying file changed? |
88 Has the underlying file changed? |
81 """ |
89 """ |
82 |
90 |
83 return self._stat() != self._id |
91 return self.stat() != self._stat |
84 |
|
85 def poll (self, timeout) : |
|
86 """ |
|
87 XXX: not really implemented... |
|
88 """ |
|
89 |
|
90 import time |
|
91 |
|
92 time.sleep(timeout) |
|
93 |
|
94 def readline (self) : |
|
95 """ |
|
96 Reads a line from the file. |
|
97 |
|
98 Raises EOF on end-of-file. |
|
99 """ |
|
100 |
|
101 line = self.file.readline() |
|
102 |
|
103 # eof? |
|
104 if not line : |
|
105 raise EOFError() |
|
106 |
|
107 return line |
|
108 |
92 |
109 def readlines (self, eof_mark=False) : |
93 def readlines (self, eof_mark=False) : |
110 """ |
94 """ |
111 Reads any available lines from the file. |
95 Reads any available lines from the file. |
112 |
96 |