Skip to content

Commit e1bc320

Browse files
committed
Properly handle unreadable files by logging a warning instead of crashing. Closes #130
1 parent 4c4277f commit e1bc320

3 files changed

Lines changed: 101 additions & 42 deletions

File tree

beaver/worker/tail.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,29 @@ def __init__(self, filename, callback, position="end", logger=None, beaver_confi
4747
self._type = beaver_config.get_field('type', filename)
4848

4949
self._update_file()
50-
self._log_info("watching logfile")
50+
if self.active:
51+
self._log_info("watching logfile")
5152

5253
def __del__(self):
5354
"""Closes all files"""
5455
self.close()
5556

5657
def open(self, encoding=None):
5758
"""Opens the file with the appropriate call"""
58-
if IS_GZIPPED_FILE.search(self._filename):
59-
_file = gzip.open(self._filename, 'rb')
60-
else:
61-
if encoding:
62-
_file = io.open(self._filename, 'r', encoding=encoding)
63-
elif self._encoding:
64-
_file = io.open(self._filename, 'r', encoding=self._encoding)
59+
try:
60+
if IS_GZIPPED_FILE.search(self._filename):
61+
_file = gzip.open(self._filename, 'rb')
6562
else:
66-
_file = io.open(self._filename, 'r')
63+
if encoding:
64+
_file = io.open(self._filename, 'r', encoding=encoding)
65+
elif self._encoding:
66+
_file = io.open(self._filename, 'r', encoding=self._encoding)
67+
else:
68+
_file = io.open(self._filename, 'r')
69+
except IOError, e:
70+
self._log_warning(str(e))
71+
_file = None
72+
self.close()
6773

6874
return _file
6975

@@ -122,7 +128,8 @@ def _ensure_file_is_good(self, current_time):
122128
self._log_debug('file reloaded (non-linux)')
123129
position = self._file.tell()
124130
self._update_file(seek_to_end=False)
125-
self._file.seek(position, os.SEEK_SET)
131+
if self.active:
132+
self._file.seek(position, os.SEEK_SET)
126133

127134
def _run_pass(self):
128135
"""Read lines from a file and performs a callback against them"""
@@ -179,6 +186,9 @@ def _seek_to_end(self):
179186
self._start_position = int(self._start_position)
180187
for encoding in ENCODINGS:
181188
line_count, encoded = self._seek_to_position(encoding=encoding, position=True)
189+
if line_count is None and encoded is None:
190+
return
191+
182192
if encoded:
183193
break
184194

@@ -190,6 +200,9 @@ def _seek_to_end(self):
190200
self._log_debug('getting end position')
191201
for encoding in ENCODINGS:
192202
line_count, encoded = self._seek_to_position(encoding=encoding)
203+
if line_count is None and encoded is None:
204+
return
205+
193206
if encoded:
194207
break
195208

@@ -204,6 +217,8 @@ def _seek_to_end(self):
204217
if lines:
205218
self._callback_wrapper(lines)
206219

220+
return
221+
207222
def _seek_to_position(self, encoding=None, position=None):
208223
line_count = 0
209224
encoded = False
@@ -221,6 +236,9 @@ def _seek_to_position(self, encoding=None, position=None):
221236
self._file = self.open(encoding=encoding)
222237
self._encoding = encoding
223238

239+
if not self._file:
240+
return None, None
241+
224242
if position and line_count != self._start_position:
225243
self._log_debug('file at different position than {0}, assuming manual truncate'.format(self._start_position))
226244
self._file.seek(0, os.SEEK_SET)
@@ -317,6 +335,9 @@ def _update_file(self, seek_to_end=True):
317335
except IOError:
318336
pass
319337
else:
338+
if not self._file:
339+
return
340+
320341
self.active = True
321342
try:
322343
st = os.stat(self._filename)
@@ -347,7 +368,10 @@ def tail(self, fname, encoding, window, position=None):
347368
for enc in encodings:
348369
try:
349370
f = self.open(encoding=enc)
350-
return self.tail_read(f, window, position=position)
371+
if f:
372+
return self.tail_read(f, window, position=position)
373+
374+
return False
351375
except IOError, err:
352376
if err.errno == errno.ENOENT:
353377
return []

beaver/worker/tail_manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def watch(self, paths=[]):
4545
logger=self._logger
4646
)
4747

48-
self._tails[tail.fid()] = tail
48+
if tail.active:
49+
self._tails[tail.fid()] = tail
4950

5051
def run(self, interval=0.1,):
5152
while self._active:

beaver/worker/worker.py

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def seek_to_end(self):
127127
for fid, data in self._file_map.iteritems():
128128
self._logger.debug("[{0}] - getting start position {1}".format(fid, data['file'].name))
129129
start_position = self._beaver_config.get_field('start_position', data['file'].name)
130+
is_active = data['active']
130131

131132
if self._sincedb_path:
132133
sincedb_start_position = self._sincedb_start_position(data['file'], fid=fid)
@@ -151,13 +152,21 @@ def seek_to_end(self):
151152
except UnicodeDecodeError:
152153
self._logger.debug("[{0}] - UnicodeDecodeError raised for {1} with encoding {2}".format(fid, data['file'].name, data['encoding']))
153154
data['file'] = self.open(data['file'].name, encoding=encoding)
155+
if not data['file']:
156+
self.unwatch(data['file'], fid)
157+
is_active = False
158+
break
159+
154160
data['encoding'] = encoding
155161

156162
if line_count != start_position:
157163
self._logger.debug("[{0}] - file at different position than {1}, assuming manual truncate for {2}".format(fid, start_position, data['file'].name))
158164
data['file'].seek(0, os.SEEK_SET)
159165
start_position == "beginning"
160166

167+
if not is_active:
168+
continue
169+
161170
if start_position == "beginning":
162171
continue
163172

@@ -172,8 +181,16 @@ def seek_to_end(self):
172181
except UnicodeDecodeError:
173182
self._logger.debug("[{0}] - UnicodeDecodeError raised for {1} with encoding {2}".format(fid, data['file'].name, data['encoding']))
174183
data['file'] = self.open(data['file'].name, encoding=encoding)
184+
if not data['file']:
185+
self.unwatch(data['file'], fid)
186+
is_active = False
187+
break
188+
175189
data['encoding'] = encoding
176190

191+
if not is_active:
192+
continue
193+
177194
current_position = data['file'].tell()
178195
self._logger.debug("[{0}] - line count {1} for {2}".format(fid, line_count, data['file'].name))
179196
self._sincedb_update_position(data['file'], fid=fid, lines=line_count, force_update=True)
@@ -365,58 +382,69 @@ def _ensure_files_are_good(self, current_time):
365382
fname = data['file'].name
366383
data['file'].close()
367384
file = self.open(fname, encoding=data['encoding'])
368-
file.seek(position)
369-
self._file_map[fid]['file'] = file
385+
if file:
386+
file.seek(position)
387+
self._file_map[fid]['file'] = file
370388

371389
def unwatch(self, file, fid):
372390
"""file no longer exists; if it has been renamed
373391
try to read it for the last time in case the
374392
log rotator has written something in it.
375393
"""
376394
try:
377-
self.readfile(fid, file)
395+
if file:
396+
self.readfile(fid, file)
378397
except IOError:
379398
# Silently ignore any IOErrors -- file is gone
380399
pass
381-
self._logger.info("[{0}] - un-watching logfile {1}".format(fid, file.name))
400+
401+
if file:
402+
self._logger.info("[{0}] - un-watching logfile {1}".format(fid, file.name))
403+
else:
404+
self._logger.info("[{0}] - un-watching logfile".format(fid))
405+
382406
del self._file_map[fid]
383407

384408
def watch(self, fname):
385409
"""Opens a file for log tailing"""
386410
try:
387411
file = self.open(fname, encoding=self._beaver_config.get_field('encoding', fname))
388-
fid = self.get_file_id(os.stat(fname))
412+
if file:
413+
fid = self.get_file_id(os.stat(fname))
389414
except EnvironmentError, err:
390415
if err.errno != errno.ENOENT:
391416
raise
392417
else:
393-
self._logger.info("[{0}] - watching logfile {1}".format(fid, fname))
394-
self._file_map[fid] = {
395-
'encoding': self._beaver_config.get_field('encoding', fname),
396-
'file': file,
397-
'line': 0,
398-
'update_time': None,
399-
}
400-
401-
@classmethod
402-
def open(cls, fname, encoding=None):
418+
if file:
419+
self._logger.info("[{0}] - watching logfile {1}".format(fid, fname))
420+
self._file_map[fid] = {
421+
'encoding': self._beaver_config.get_field('encoding', fname),
422+
'file': file,
423+
'line': 0,
424+
'update_time': None,
425+
'active': True,
426+
}
427+
428+
def open(self, filename, encoding=None):
403429
"""Opens a file with the appropriate call"""
404-
if IS_GZIPPED_FILE.search(fname):
405-
file = gzip.open(fname, "rb")
406-
else:
407-
if encoding:
408-
file = io.open(fname, "r", encoding=encoding)
430+
try:
431+
if IS_GZIPPED_FILE.search(filename):
432+
_file = gzip.open(filename, "rb")
409433
else:
410-
file = io.open(fname, "r")
411-
412-
return file
434+
file_encoding = self._beaver_config.get_field('encoding', filename)
435+
if encoding:
436+
_file = io.open(filename, "r", encoding=encoding)
437+
elif file_encoding:
438+
_file = io.open(filename, "r", encoding=file_encoding)
439+
else:
440+
_file = io.open(filename, "r")
441+
except IOError, e:
442+
self._logger.warning(str(e))
443+
_file = None
413444

414-
@staticmethod
415-
def get_file_id(st):
416-
return "%xg%x" % (st.st_dev, st.st_ino)
445+
return _file
417446

418-
@classmethod
419-
def tail(cls, fname, encoding, window, position=None):
447+
def tail(self, fname, encoding, window, position=None):
420448
"""Read last N lines from file fname."""
421449
if window <= 0:
422450
raise ValueError('invalid window %r' % window)
@@ -427,15 +455,21 @@ def tail(cls, fname, encoding, window, position=None):
427455

428456
for enc in encodings:
429457
try:
430-
f = cls.open(fname, encoding=enc)
431-
return cls.tail_read(f, window, position=position)
458+
f = self.open(fname, encoding=enc)
459+
if not f:
460+
return []
461+
return self.tail_read(f, window, position=position)
432462
except IOError, err:
433463
if err.errno == errno.ENOENT:
434464
return []
435465
raise
436466
except UnicodeDecodeError:
437467
pass
438468

469+
@staticmethod
470+
def get_file_id(st):
471+
return "%xg%x" % (st.st_dev, st.st_ino)
472+
439473
@classmethod
440474
def tail_read(cls, f, window, position=None):
441475
BUFSIZ = 1024

0 commit comments

Comments
 (0)