diff --git a/test/debug_logger.py b/test/debug_logger.py new file mode 100644 index 0000000000..32d3ab274a --- /dev/null +++ b/test/debug_logger.py @@ -0,0 +1,212 @@ +# Copyright (c) 2010-2021 OpenStack Foundation +# +# 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 logging +import sys + +from collections import defaultdict + +from swift.common import utils +from swift.common.utils import NOTICE + + +class WARN_DEPRECATED(Exception): + def __init__(self, msg): + self.msg = msg + print(self.msg) + + +class FakeLogger(logging.Logger, object): + # a thread safe fake logger + + def __init__(self, *args, **kwargs): + self._clear() + self.name = 'swift.unit.fake_logger' + self.level = logging.NOTSET + if 'facility' in kwargs: + self.facility = kwargs['facility'] + self.statsd_client = None + self.thread_locals = None + self.parent = None + + store_in = { + logging.ERROR: 'error', + logging.WARNING: 'warning', + logging.INFO: 'info', + logging.DEBUG: 'debug', + logging.CRITICAL: 'critical', + NOTICE: 'notice', + } + + def warn(self, *args, **kwargs): + raise WARN_DEPRECATED("Deprecated Method warn use warning instead") + + def notice(self, msg, *args, **kwargs): + """ + Convenience function for syslog priority LOG_NOTICE. The python + logging lvl is set to 25, just above info. SysLogHandler is + monkey patched to map this log lvl to the LOG_NOTICE syslog + priority. + """ + self.log(NOTICE, msg, *args, **kwargs) + + def _log(self, level, msg, *args, **kwargs): + store_name = self.store_in[level] + cargs = [msg] + if any(args): + cargs.extend(args) + captured = dict(kwargs) + if 'exc_info' in kwargs and \ + not isinstance(kwargs['exc_info'], tuple): + captured['exc_info'] = sys.exc_info() + self.log_dict[store_name].append((tuple(cargs), captured)) + super(FakeLogger, self)._log(level, msg, *args, **kwargs) + + def _clear(self): + self.log_dict = defaultdict(list) + self.lines_dict = {'critical': [], 'error': [], 'info': [], + 'warning': [], 'debug': [], 'notice': []} + + clear = _clear # this is a public interface + + def get_lines_for_level(self, level): + if level not in self.lines_dict: + raise KeyError( + "Invalid log level '%s'; valid levels are %s" % + (level, + ', '.join("'%s'" % lvl for lvl in sorted(self.lines_dict)))) + return self.lines_dict[level] + + def all_log_lines(self): + return dict((level, msgs) for level, msgs in self.lines_dict.items() + if len(msgs) > 0) + + def _store_in(store_name): + def stub_fn(self, *args, **kwargs): + self.log_dict[store_name].append((args, kwargs)) + return stub_fn + + # mock out the StatsD logging methods: + update_stats = _store_in('update_stats') + increment = _store_in('increment') + decrement = _store_in('decrement') + timing = _store_in('timing') + timing_since = _store_in('timing_since') + transfer_rate = _store_in('transfer_rate') + set_statsd_prefix = _store_in('set_statsd_prefix') + + def get_increments(self): + return [call[0][0] for call in self.log_dict['increment']] + + def get_increment_counts(self): + counts = {} + for metric in self.get_increments(): + if metric not in counts: + counts[metric] = 0 + counts[metric] += 1 + return counts + + def setFormatter(self, obj): + self.formatter = obj + + def close(self): + self._clear() + + def set_name(self, name): + # don't touch _handlers + self._name = name + + def acquire(self): + pass + + def release(self): + pass + + def createLock(self): + pass + + def emit(self, record): + pass + + def _handle(self, record): + try: + line = record.getMessage() + except TypeError: + print('WARNING: unable to format log message %r %% %r' % ( + record.msg, record.args)) + raise + self.lines_dict[record.levelname.lower()].append(line) + + def handle(self, record): + self._handle(record) + + def flush(self): + pass + + def handleError(self, record): + pass + + def isEnabledFor(self, level): + return True + + +class DebugSwiftLogFormatter(utils.SwiftLogFormatter): + + def format(self, record): + msg = super(DebugSwiftLogFormatter, self).format(record) + return msg.replace('#012', '\n') + + +class DebugLogger(FakeLogger): + """A simple stdout logging version of FakeLogger""" + + def __init__(self, *args, **kwargs): + FakeLogger.__init__(self, *args, **kwargs) + self.formatter = DebugSwiftLogFormatter( + "%(server)s %(levelname)s: %(message)s") + self.records = defaultdict(list) + + def handle(self, record): + self._handle(record) + formatted = self.formatter.format(record) + print(formatted) + self.records[record.levelname].append(formatted) + + +class DebugLogAdapter(utils.LogAdapter): + + def _send_to_logger(name): + def stub_fn(self, *args, **kwargs): + return getattr(self.logger, name)(*args, **kwargs) + return stub_fn + + # delegate to FakeLogger's mocks + update_stats = _send_to_logger('update_stats') + increment = _send_to_logger('increment') + decrement = _send_to_logger('decrement') + timing = _send_to_logger('timing') + timing_since = _send_to_logger('timing_since') + transfer_rate = _send_to_logger('transfer_rate') + set_statsd_prefix = _send_to_logger('set_statsd_prefix') + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + return getattr(self.__dict__['logger'], name) + + +def debug_logger(name='test'): + """get a named adapted debug logger""" + return DebugLogAdapter(DebugLogger(), name) diff --git a/test/unit/__init__.py b/test/unit/__init__.py index 90e141b880..5b768c4a4e 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -47,8 +47,10 @@ from six.moves.http_client import HTTPException from swift.common import storage_policy, swob, utils from swift.common.storage_policy import (StoragePolicy, ECStoragePolicy, VALID_EC_TYPES) -from swift.common.utils import Timestamp, NOTICE, md5 +from swift.common.utils import Timestamp, md5 from test import get_config +# import to namespace for backward compat +from test.debug_logger import debug_logger, DebugLogger, FakeLogger # noqa from swift.common.header_key_dict import HeaderKeyDict from swift.common.ring import Ring, RingData, RingBuilder from swift.obj import server @@ -553,197 +555,6 @@ class UnmockTimeModule(object): logging.time = UnmockTimeModule() -class WARN_DEPRECATED(Exception): - def __init__(self, msg): - self.msg = msg - print(self.msg) - - -class FakeLogger(logging.Logger, object): - # a thread safe fake logger - - def __init__(self, *args, **kwargs): - self._clear() - self.name = 'swift.unit.fake_logger' - self.level = logging.NOTSET - if 'facility' in kwargs: - self.facility = kwargs['facility'] - self.statsd_client = None - self.thread_locals = None - self.parent = None - - store_in = { - logging.ERROR: 'error', - logging.WARNING: 'warning', - logging.INFO: 'info', - logging.DEBUG: 'debug', - logging.CRITICAL: 'critical', - NOTICE: 'notice', - } - - def warn(self, *args, **kwargs): - raise WARN_DEPRECATED("Deprecated Method warn use warning instead") - - def notice(self, msg, *args, **kwargs): - """ - Convenience function for syslog priority LOG_NOTICE. The python - logging lvl is set to 25, just above info. SysLogHandler is - monkey patched to map this log lvl to the LOG_NOTICE syslog - priority. - """ - self.log(NOTICE, msg, *args, **kwargs) - - def _log(self, level, msg, *args, **kwargs): - store_name = self.store_in[level] - cargs = [msg] - if any(args): - cargs.extend(args) - captured = dict(kwargs) - if 'exc_info' in kwargs and \ - not isinstance(kwargs['exc_info'], tuple): - captured['exc_info'] = sys.exc_info() - self.log_dict[store_name].append((tuple(cargs), captured)) - super(FakeLogger, self)._log(level, msg, *args, **kwargs) - - def _clear(self): - self.log_dict = defaultdict(list) - self.lines_dict = {'critical': [], 'error': [], 'info': [], - 'warning': [], 'debug': [], 'notice': []} - - clear = _clear # this is a public interface - - def get_lines_for_level(self, level): - if level not in self.lines_dict: - raise KeyError( - "Invalid log level '%s'; valid levels are %s" % - (level, - ', '.join("'%s'" % lvl for lvl in sorted(self.lines_dict)))) - return self.lines_dict[level] - - def all_log_lines(self): - return dict((level, msgs) for level, msgs in self.lines_dict.items() - if len(msgs) > 0) - - def _store_in(store_name): - def stub_fn(self, *args, **kwargs): - self.log_dict[store_name].append((args, kwargs)) - return stub_fn - - # mock out the StatsD logging methods: - update_stats = _store_in('update_stats') - increment = _store_in('increment') - decrement = _store_in('decrement') - timing = _store_in('timing') - timing_since = _store_in('timing_since') - transfer_rate = _store_in('transfer_rate') - set_statsd_prefix = _store_in('set_statsd_prefix') - - def get_increments(self): - return [call[0][0] for call in self.log_dict['increment']] - - def get_increment_counts(self): - counts = {} - for metric in self.get_increments(): - if metric not in counts: - counts[metric] = 0 - counts[metric] += 1 - return counts - - def setFormatter(self, obj): - self.formatter = obj - - def close(self): - self._clear() - - def set_name(self, name): - # don't touch _handlers - self._name = name - - def acquire(self): - pass - - def release(self): - pass - - def createLock(self): - pass - - def emit(self, record): - pass - - def _handle(self, record): - try: - line = record.getMessage() - except TypeError: - print('WARNING: unable to format log message %r %% %r' % ( - record.msg, record.args)) - raise - self.lines_dict[record.levelname.lower()].append(line) - - def handle(self, record): - self._handle(record) - - def flush(self): - pass - - def handleError(self, record): - pass - - def isEnabledFor(self, level): - return True - - -class DebugSwiftLogFormatter(utils.SwiftLogFormatter): - - def format(self, record): - msg = super(DebugSwiftLogFormatter, self).format(record) - return msg.replace('#012', '\n') - - -class DebugLogger(FakeLogger): - """A simple stdout logging version of FakeLogger""" - - def __init__(self, *args, **kwargs): - FakeLogger.__init__(self, *args, **kwargs) - self.formatter = DebugSwiftLogFormatter( - "%(server)s %(levelname)s: %(message)s") - self.records = defaultdict(list) - - def handle(self, record): - self._handle(record) - formatted = self.formatter.format(record) - print(formatted) - self.records[record.levelname].append(formatted) - - -class DebugLogAdapter(utils.LogAdapter): - - def _send_to_logger(name): - def stub_fn(self, *args, **kwargs): - return getattr(self.logger, name)(*args, **kwargs) - return stub_fn - - # delegate to FakeLogger's mocks - update_stats = _send_to_logger('update_stats') - increment = _send_to_logger('increment') - decrement = _send_to_logger('decrement') - timing = _send_to_logger('timing') - timing_since = _send_to_logger('timing_since') - transfer_rate = _send_to_logger('transfer_rate') - set_statsd_prefix = _send_to_logger('set_statsd_prefix') - - def __getattribute__(self, name): - try: - return object.__getattribute__(self, name) - except AttributeError: - return getattr(self.__dict__['logger'], name) - - -def debug_logger(name='test'): - """get a named adapted debug logger""" - return DebugLogAdapter(DebugLogger(), name) - - original_syslog_handler = logging.handlers.SysLogHandler