summaryrefslogtreecommitdiffstats
path: root/pylog/onaplogging/mdcContext.py
diff options
context:
space:
mode:
authorEli Halych <illia.halych@t-mobile.pl>2020-09-02 13:10:35 +0000
committerEli Halych <illia.halych@t-mobile.pl>2020-09-07 08:52:24 +0000
commit7035aed700a463fd171807526475baf84c1434e7 (patch)
tree9d0e7c4c8252f431ac3e12c0f5086c1c8b625f79 /pylog/onaplogging/mdcContext.py
parent314ee85cf7c8a98dd21d5d12e4013b9f742b1012 (diff)
onaplogging: Docstrings, refactor, type hinting
Identify and document functionalities. Describe parameters and their types, exception descriptions and types, extensions, return types and descriptions. Preserve Python 2.7 and 3.x compatibility. Add Python 2.7 to Tox testing. Extract code to utility files. Add properties for readability and maintainability, fix naming conventions. Deprecate old methods and attributes. Issue-ID: REQ-420 Signed-off-by: Eli Halych <illia.halych@t-mobile.pl> Change-Id: I19297e40fad743ec68aa04612ecbb11f61f2abec
Diffstat (limited to 'pylog/onaplogging/mdcContext.py')
-rw-r--r--pylog/onaplogging/mdcContext.py205
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()