Allow oslo.log to work on non-linux platforms
Especially needed for all the HyperV work on windows. Depends-On: I1a0a2dacbefa548296a6cfef1663ea9d48253c0c Change-Id: Ifcc854e6eb6f4d0e575d1489f0d00f3339705e0b
This commit is contained in:
parent
97614133a8
commit
8ad1403d8f
12
doc/source/api/watchers.rst
Normal file
12
doc/source/api/watchers.rst
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
==================
|
||||||
|
oslo_log.watchers
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: oslo_log.watchers
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:doc:`../usage`
|
@ -10,15 +10,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import errno
|
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import logging.config
|
import logging.config
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import pyinotify
|
|
||||||
import stat
|
|
||||||
import time
|
|
||||||
try:
|
try:
|
||||||
import syslog
|
import syslog
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -119,88 +115,3 @@ class ColorHandler(logging.StreamHandler):
|
|||||||
def format(self, record):
|
def format(self, record):
|
||||||
record.color = self.LEVEL_COLORS[record.levelno]
|
record.color = self.LEVEL_COLORS[record.levelno]
|
||||||
return logging.StreamHandler.format(self, record)
|
return logging.StreamHandler.format(self, record)
|
||||||
|
|
||||||
|
|
||||||
class FileKeeper(pyinotify.ProcessEvent):
|
|
||||||
def my_init(self, watched_handler, watched_file):
|
|
||||||
self._watched_handler = watched_handler
|
|
||||||
self._watched_file = watched_file
|
|
||||||
|
|
||||||
def process_default(self, event):
|
|
||||||
if event.name == self._watched_file:
|
|
||||||
self._watched_handler.reopen_file()
|
|
||||||
|
|
||||||
|
|
||||||
class EventletThreadedNotifier(pyinotify.ThreadedNotifier):
|
|
||||||
|
|
||||||
def loop(self):
|
|
||||||
"""Eventlet friendly ThreadedNotifier
|
|
||||||
|
|
||||||
EventletFriendlyThreadedNotifier contains additional time.sleep()
|
|
||||||
call insude loop to allow switching to other thread when eventlet
|
|
||||||
is used.
|
|
||||||
It can be used with eventlet and native threads as well.
|
|
||||||
"""
|
|
||||||
|
|
||||||
while not self._stop_event.is_set():
|
|
||||||
self.process_events()
|
|
||||||
time.sleep(0)
|
|
||||||
ref_time = time.time()
|
|
||||||
if self.check_events():
|
|
||||||
self._sleep(ref_time)
|
|
||||||
self.read_events()
|
|
||||||
|
|
||||||
|
|
||||||
class FastWatchedFileHandler(logging.handlers.WatchedFileHandler, object):
|
|
||||||
"""Frequency of reading events.
|
|
||||||
|
|
||||||
Watching thread sleeps max(0, READ_FREQ - (TIMEOUT / 1000)) seconds.
|
|
||||||
"""
|
|
||||||
READ_FREQ = 1
|
|
||||||
|
|
||||||
"""Poll timeout in milliseconds.
|
|
||||||
|
|
||||||
See https://docs.python.org/2/library/select.html#select.poll.poll"""
|
|
||||||
TIMEOUT = 500
|
|
||||||
|
|
||||||
def __init__(self, logpath, *args, **kwargs):
|
|
||||||
self._log_file = os.path.basename(logpath)
|
|
||||||
self._log_dir = os.path.dirname(logpath)
|
|
||||||
super(FastWatchedFileHandler, self).__init__(logpath, *args, **kwargs)
|
|
||||||
self._watch_file()
|
|
||||||
|
|
||||||
def _watch_file(self):
|
|
||||||
mask = pyinotify.IN_MOVED_FROM | pyinotify.IN_DELETE
|
|
||||||
watch_manager = pyinotify.WatchManager()
|
|
||||||
handler = FileKeeper(watched_handler=self,
|
|
||||||
watched_file=self._log_file)
|
|
||||||
notifier = EventletThreadedNotifier(
|
|
||||||
watch_manager,
|
|
||||||
default_proc_fun=handler,
|
|
||||||
read_freq=FastWatchedFileHandler.READ_FREQ,
|
|
||||||
timeout=FastWatchedFileHandler.TIMEOUT)
|
|
||||||
notifier.daemon = True
|
|
||||||
watch_manager.add_watch(self._log_dir, mask)
|
|
||||||
notifier.start()
|
|
||||||
|
|
||||||
def reopen_file(self):
|
|
||||||
try:
|
|
||||||
# stat the file by path, checking for existence
|
|
||||||
sres = os.stat(self.baseFilename)
|
|
||||||
except OSError as err:
|
|
||||||
if err.errno == errno.ENOENT:
|
|
||||||
sres = None
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
# compare file system stat with that of our stream file handle
|
|
||||||
if (not sres or
|
|
||||||
sres[stat.ST_DEV] != self.dev or
|
|
||||||
sres[stat.ST_INO] != self.ino):
|
|
||||||
if self.stream is not None:
|
|
||||||
# we have an open file handle, clean it up
|
|
||||||
self.stream.flush()
|
|
||||||
self.stream.close()
|
|
||||||
self.stream = None
|
|
||||||
# open a new file handle and get new stat info from that fd
|
|
||||||
self.stream = self._open()
|
|
||||||
self._statstream()
|
|
||||||
|
@ -313,7 +313,8 @@ def _setup_logging_from_conf(conf, project, version):
|
|||||||
logpath = _get_log_file_path(conf)
|
logpath = _get_log_file_path(conf)
|
||||||
if logpath:
|
if logpath:
|
||||||
if conf.watch_log_file and platform.system() == 'Linux':
|
if conf.watch_log_file and platform.system() == 'Linux':
|
||||||
file_handler = handlers.FastWatchedFileHandler
|
from oslo_log import watchers
|
||||||
|
file_handler = watchers.FastWatchedFileHandler
|
||||||
else:
|
else:
|
||||||
file_handler = logging.handlers.WatchedFileHandler
|
file_handler = logging.handlers.WatchedFileHandler
|
||||||
|
|
||||||
|
@ -684,8 +684,9 @@ class FastWatchedFileHandlerTestCase(BaseTestCase):
|
|||||||
self._config()
|
self._config()
|
||||||
logger = log._loggers[None].logger
|
logger = log._loggers[None].logger
|
||||||
self.assertEqual(1, len(logger.handlers))
|
self.assertEqual(1, len(logger.handlers))
|
||||||
|
from oslo_log import watchers
|
||||||
self.assertIsInstance(logger.handlers[0],
|
self.assertIsInstance(logger.handlers[0],
|
||||||
handlers.FastWatchedFileHandler)
|
watchers.FastWatchedFileHandler)
|
||||||
|
|
||||||
def test_log(self):
|
def test_log(self):
|
||||||
log_path = self._config()
|
log_path = self._config()
|
||||||
|
111
oslo_log/watchers.py
Normal file
111
oslo_log/watchers.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
import logging.handlers
|
||||||
|
import os
|
||||||
|
import pyinotify
|
||||||
|
import stat
|
||||||
|
import time
|
||||||
|
try:
|
||||||
|
import syslog
|
||||||
|
except ImportError:
|
||||||
|
syslog = None
|
||||||
|
|
||||||
|
"""Linux specific pyinotify based logging handlers"""
|
||||||
|
|
||||||
|
|
||||||
|
class _FileKeeper(pyinotify.ProcessEvent):
|
||||||
|
def my_init(self, watched_handler, watched_file):
|
||||||
|
self._watched_handler = watched_handler
|
||||||
|
self._watched_file = watched_file
|
||||||
|
|
||||||
|
def process_default(self, event):
|
||||||
|
if event.name == self._watched_file:
|
||||||
|
self._watched_handler.reopen_file()
|
||||||
|
|
||||||
|
|
||||||
|
class _EventletThreadedNotifier(pyinotify.ThreadedNotifier):
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
"""Eventlet friendly ThreadedNotifier
|
||||||
|
|
||||||
|
EventletFriendlyThreadedNotifier contains additional time.sleep()
|
||||||
|
call insude loop to allow switching to other thread when eventlet
|
||||||
|
is used.
|
||||||
|
It can be used with eventlet and native threads as well.
|
||||||
|
"""
|
||||||
|
|
||||||
|
while not self._stop_event.is_set():
|
||||||
|
self.process_events()
|
||||||
|
time.sleep(0)
|
||||||
|
ref_time = time.time()
|
||||||
|
if self.check_events():
|
||||||
|
self._sleep(ref_time)
|
||||||
|
self.read_events()
|
||||||
|
|
||||||
|
|
||||||
|
class FastWatchedFileHandler(logging.handlers.WatchedFileHandler, object):
|
||||||
|
"""Frequency of reading events.
|
||||||
|
|
||||||
|
Watching thread sleeps max(0, READ_FREQ - (TIMEOUT / 1000)) seconds.
|
||||||
|
"""
|
||||||
|
READ_FREQ = 1
|
||||||
|
|
||||||
|
"""Poll timeout in milliseconds.
|
||||||
|
|
||||||
|
See https://docs.python.org/2/library/select.html#select.poll.poll"""
|
||||||
|
TIMEOUT = 500
|
||||||
|
|
||||||
|
def __init__(self, logpath, *args, **kwargs):
|
||||||
|
self._log_file = os.path.basename(logpath)
|
||||||
|
self._log_dir = os.path.dirname(logpath)
|
||||||
|
super(FastWatchedFileHandler, self).__init__(logpath, *args, **kwargs)
|
||||||
|
self._watch_file()
|
||||||
|
|
||||||
|
def _watch_file(self):
|
||||||
|
mask = pyinotify.IN_MOVED_FROM | pyinotify.IN_DELETE
|
||||||
|
watch_manager = pyinotify.WatchManager()
|
||||||
|
handler = _FileKeeper(watched_handler=self,
|
||||||
|
watched_file=self._log_file)
|
||||||
|
notifier = _EventletThreadedNotifier(
|
||||||
|
watch_manager,
|
||||||
|
default_proc_fun=handler,
|
||||||
|
read_freq=FastWatchedFileHandler.READ_FREQ,
|
||||||
|
timeout=FastWatchedFileHandler.TIMEOUT)
|
||||||
|
notifier.daemon = True
|
||||||
|
watch_manager.add_watch(self._log_dir, mask)
|
||||||
|
notifier.start()
|
||||||
|
|
||||||
|
def reopen_file(self):
|
||||||
|
try:
|
||||||
|
# stat the file by path, checking for existence
|
||||||
|
sres = os.stat(self.baseFilename)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno == errno.ENOENT:
|
||||||
|
sres = None
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
# compare file system stat with that of our stream file handle
|
||||||
|
if (not sres or
|
||||||
|
sres[stat.ST_DEV] != self.dev or
|
||||||
|
sres[stat.ST_INO] != self.ino):
|
||||||
|
if self.stream is not None:
|
||||||
|
# we have an open file handle, clean it up
|
||||||
|
self.stream.flush()
|
||||||
|
self.stream.close()
|
||||||
|
self.stream = None
|
||||||
|
# open a new file handle and get new stat info from that fd
|
||||||
|
self.stream = self._open()
|
||||||
|
self._statstream()
|
@ -12,4 +12,4 @@ oslo.i18n>=1.5.0 # Apache-2.0
|
|||||||
oslo.utils>=2.0.0 # Apache-2.0
|
oslo.utils>=2.0.0 # Apache-2.0
|
||||||
oslo.serialization>=1.4.0 # Apache-2.0
|
oslo.serialization>=1.4.0 # Apache-2.0
|
||||||
debtcollector>=0.3.0 # Apache-2.0
|
debtcollector>=0.3.0 # Apache-2.0
|
||||||
pyinotify>=0.9.6 # MIT
|
pyinotify>=0.9.6;sys_platform!='win32' and sys_platform!='darwin' # MIT
|
||||||
|
Loading…
x
Reference in New Issue
Block a user