diff options
Diffstat (limited to 'pylog/onaplogging/mdcContext.py')
-rw-r--r-- | pylog/onaplogging/mdcContext.py | 205 |
1 files changed, 150 insertions, 55 deletions
diff --git a/pylog/onaplogging/mdcContext.py b/pylog/onaplogging/mdcContext.py index ecdc2d9..c1852b3 100644 --- a/pylog/onaplogging/mdcContext.py +++ b/pylog/onaplogging/mdcContext.py @@ -19,9 +19,16 @@ import os import traceback import sys import functools -from .marker import Marker -from .marker import MARKER_TAG +from deprecated import deprecated +from typing import Dict, Optional, Any, Callable, List, Tuple +from logging import LogRecord + +from onaplogging.utils.system import is_above_python_3_2 + +from .marker import Marker, MARKER_TAG + +# TODO change to patch_logging_mdc after deprecated method is removed __all__ = ['patch_loggingMDC', 'MDC'] _replace_func_name = ['info', 'critical', 'fatal', 'debug', @@ -29,123 +36,186 @@ _replace_func_name = ['info', 'critical', 'fatal', 'debug', 'handle', 'findCaller'] -class MDCContext(threading.local): +def fetchkeys(func): # type: Callable[[str, List, Dict], None] + # type: (...) -> Callable[[str, List, Dict], None] + """MDC decorator. + + Fetchs contextual information from a logging call. + Wraps by adding MDC to the `extra` field. Executes + the call with the updated contextual information. """ - A Thread local instance to storage mdc values + + @functools.wraps(func) + def replace(*args, **kwargs): + # type: () -> None + kwargs['extra'] = _getmdcs(extra=kwargs.get('extra', None)) + func(*args, **kwargs) + + return replace + + +class MDCContext(threading.local): + """A Thread local instance that stores MDC values. + + Is initializ with an empty dictionary. Manages that + dictionary to created Mapped Diagnostic Context. + + Extends: + threading.local + Property: + local_dict : a placeholder for MDC keys and values. """ - def __init__(self): + @property + def local_dict(self): + # type: () -> Dict + return self._local_dict + + @local_dict.setter + def local_dict(self, value): + # type: (Dict) -> None + self._local_dict = value + + def __init__(self): super(MDCContext, self).__init__() - self._localDict = {} + self.local_dict = {} def get(self, key): - - return self._localDict.get(key, None) + # type: (str) -> Any + """Retrieve a value by key.""" + return self.local_dict.get(key, None) def put(self, key, value): - - self._localDict[key] = value + # type: (str, Any) -> None + """Insert or update a value by key.""" + self.local_dict[key] = value def remove(self, key): - - if key in self._localDict: - del self._localDict[key] + # type: (str) -> None + """Remove a value by key, if exists.""" + if key in self.local_dict: + del self.local_dict[key] def clear(self): + # type: () -> None + """Empty the MDC dictionary.""" + self.local_dict.clear() - self._localDict.clear() - + @deprecated(reason="Use local_mdc property instead.") def result(self): + """Getter for the MDC dictionary.""" + return self.local_dict - return self._localDict + def empty(self): + # type: () -> bool + """Checks whether the local dictionary is empty.""" + return self.local_dict == {} or \ + self.local_dict is None + @deprecated(reason="Will be replaced. Use empty() instead.") def isEmpty(self): - - return self._localDict == {} or self._localDict is None + """See empty().""" + return self.empty() MDC = MDCContext() -def fetchkeys(func): - - @functools.wraps(func) - def replace(*args, **kwargs): - kwargs['extra'] = _getmdcs(extra=kwargs.get('extra', None)) - func(*args, **kwargs) - return replace - - def _getmdcs(extra=None): + # type: (Optional[Dict]) -> Dict """ - Put mdc dict in logging record extra filed with key 'mdc' - :param extra: dict - :return: mdc dict + Puts an MDC dict in the `extra` field with key 'mdc'. This provides + the contextual information with MDC. + + Args: + extra : Contextual information. Defaults to None. + Raises: + KeyError : a key from extra is attempted to be overwritten. + Returns: + dict : contextual information named `extra` with MDC. """ - if MDC.isEmpty(): + if MDC.empty(): return extra - mdc = MDC.result() + mdc = MDC.local_dict if extra is not None: for key in extra: - # make sure extra key dosen't override mdckey - if key in mdc or key == 'mdc': + if key in mdc or \ + key == 'mdc': raise KeyError("Attempt to overwrite %r in MDC" % key) else: extra = {} extra['mdc'] = mdc - del mdc + return extra @fetchkeys def info(self, msg, *args, **kwargs): - + # type: (str) -> None + """If INFO enabled, deletage an info call with MDC.""" if self.isEnabledFor(logging.INFO): self._log(logging.INFO, msg, args, **kwargs) @fetchkeys def debug(self, msg, *args, **kwargs): + # type: (str) -> None + """If DEBUG enabled, deletage a debug call with MDC.""" if self.isEnabledFor(logging.DEBUG): self._log(logging.DEBUG, msg, args, **kwargs) @fetchkeys def warning(self, msg, *args, **kwargs): + # type: (str) -> None + """If WARNING enabled, deletage a warning call with MDC.""" if self.isEnabledFor(logging.WARNING): self._log(logging.WARNING, msg, args, **kwargs) @fetchkeys def exception(self, msg, *args, **kwargs): - + # type: (str) -> None + """Deletage an exception call and set exc_info code to 1.""" kwargs['exc_info'] = 1 self.error(msg, *args, **kwargs) @fetchkeys def critical(self, msg, *args, **kwargs): - + # type: (str) -> None + """If CRITICAL enabled, deletage a critical call with MDC.""" if self.isEnabledFor(logging.CRITICAL): self._log(logging.CRITICAL, msg, args, **kwargs) @fetchkeys def error(self, msg, *args, **kwargs): + # type: (str) -> None + """If ERROR enabled, deletage an error call with MDC.""" if self.isEnabledFor(logging.ERROR): self._log(logging.ERROR, msg, args, **kwargs) @fetchkeys def log(self, level, msg, *args, **kwargs): + # type: (int, str) -> None + """ + If a specific logging level enabled and the code is represented + as an integer value, delegate the call to the underlying logger. + + Raises: + TypeError: if the logging level code is not an integer. + """ if not isinstance(level, int): if logging.raiseExceptions: - raise TypeError("level must be an integer") + raise TypeError("Logging level code must be an integer." + "Got %s instead." % type(level)) else: return @@ -154,55 +224,80 @@ def log(self, level, msg, *args, **kwargs): def handle(self, record): - + # type: (LogRecord) -> None cmarker = getattr(self, MARKER_TAG, None) if isinstance(cmarker, Marker): setattr(record, MARKER_TAG, cmarker) - if (not self.disabled) and self.filter(record): + if not self.disabled and \ + self.filter(record): self.callHandlers(record) def findCaller(self, stack_info=False): + # type: (bool) -> Tuple + """ + Find the stack frame of the caller so that we can note the source file + name, line number and function name. Enhances the logging.findCaller(). + """ + + frame = logging.currentframe() - f = logging.currentframe() - if f is not None: - f = f.f_back + if frame is not None: + frame = frame.f_back rv = "(unkown file)", 0, "(unknow function)" - while hasattr(f, "f_code"): - co = f.f_code + + while hasattr(frame, "f_code"): + co = frame.f_code filename = os.path.normcase(co.co_filename) # jump through local 'replace' func frame - if filename == logging._srcfile or co.co_name == "replace": - f = f.f_back + if filename == logging._srcfile or \ + co.co_name == "replace": + + frame = frame.f_back continue - if sys.version_info > (3, 2): + + if is_above_python_3_2(): + sinfo = None if stack_info: + sio = io.StringIO() sio.write("Stack (most recent call last):\n") - traceback.print_stack(f, file=sio) + traceback.print_stack(frame, file=sio) sinfo = sio.getvalue() + if sinfo[-1] == '\n': sinfo = sinfo[:-1] + sio.close() - rv = (co.co_filename, f.f_lineno, co.co_name, sinfo) + rv = (co.co_filename, frame.f_lineno, co.co_name, sinfo) + else: - rv = (co.co_filename, f.f_lineno, co.co_name) + rv = (co.co_filename, frame.f_lineno, co.co_name) break return rv -def patch_loggingMDC(): - """ - The patch to add MDC ability in logging Record instance at runtime +def patch_logging_mdc(): + # type: () -> None + """MDC patch. + + Sets MDC in a logging record instance at runtime. """ localModule = sys.modules[__name__] + for attr in dir(logging.Logger): if attr in _replace_func_name: newfunc = getattr(localModule, attr, None) if newfunc: setattr(logging.Logger, attr, newfunc) + + +@deprecated(reason="Will be removed. Call patch_logging_mdc() instead.") +def patch_loggingMDC(): + """See patch_logging_ymdc().""" + patch_logging_mdc() |