1# Licensed under the Apache License, Version 2.0 (the "License"); you may 2# not use this file except in compliance with the License. You may obtain 3# a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10# License for the specific language governing permissions and limitations 11# under the License. 12 13import errno 14import logging 15import logging.config 16import logging.handlers 17import os 18import pyinotify 19import stat 20import time 21try: 22 import syslog 23except ImportError: 24 syslog = None 25 26"""Linux specific pyinotify based logging handlers""" 27 28 29class _FileKeeper(pyinotify.ProcessEvent): 30 def my_init(self, watched_handler, watched_file): 31 self._watched_handler = watched_handler 32 self._watched_file = watched_file 33 34 def process_default(self, event): 35 if event.name == self._watched_file: 36 self._watched_handler.reopen_file() 37 38 39class _EventletThreadedNotifier(pyinotify.ThreadedNotifier): 40 41 def loop(self): 42 """Eventlet friendly ThreadedNotifier 43 44 EventletFriendlyThreadedNotifier contains additional time.sleep() 45 call insude loop to allow switching to other thread when eventlet 46 is used. 47 It can be used with eventlet and native threads as well. 48 """ 49 50 while not self._stop_event.is_set(): 51 self.process_events() 52 time.sleep(0) 53 ref_time = time.time() 54 if self.check_events(): 55 self._sleep(ref_time) 56 self.read_events() 57 58 59class FastWatchedFileHandler(logging.handlers.WatchedFileHandler, object): 60 """Frequency of reading events. 61 62 Watching thread sleeps max(0, READ_FREQ - (TIMEOUT / 1000)) seconds. 63 """ 64 READ_FREQ = 5 65 66 """Poll timeout in milliseconds. 67 68 See https://docs.python.org/2/library/select.html#select.poll.poll""" 69 TIMEOUT = 5 70 71 def __init__(self, logpath, *args, **kwargs): 72 self._log_file = os.path.basename(logpath) 73 self._log_dir = os.path.dirname(logpath) 74 super(FastWatchedFileHandler, self).__init__(logpath, *args, **kwargs) 75 self._watch_file() 76 77 def _watch_file(self): 78 mask = pyinotify.IN_MOVED_FROM | pyinotify.IN_DELETE 79 watch_manager = pyinotify.WatchManager() 80 handler = _FileKeeper(watched_handler=self, 81 watched_file=self._log_file) 82 notifier = _EventletThreadedNotifier( 83 watch_manager, 84 default_proc_fun=handler, 85 read_freq=FastWatchedFileHandler.READ_FREQ, 86 timeout=FastWatchedFileHandler.TIMEOUT) 87 notifier.daemon = True 88 watch_manager.add_watch(self._log_dir, mask) 89 notifier.start() 90 91 def reopen_file(self): 92 try: 93 # stat the file by path, checking for existence 94 sres = os.stat(self.baseFilename) 95 except OSError as err: 96 if err.errno == errno.ENOENT: 97 sres = None 98 else: 99 raise 100 # compare file system stat with that of our stream file handle 101 if (not sres or 102 sres[stat.ST_DEV] != self.dev or 103 sres[stat.ST_INO] != self.ino): 104 if self.stream is not None: 105 # we have an open file handle, clean it up 106 self.stream.flush() 107 self.stream.close() 108 self.stream = None 109 # open a new file handle and get new stat info from that fd 110 self.stream = self._open() 111 self._statstream() 112