Volans has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/366735 )

Change subject: Logging: add a custom trace() logging level
......................................................................

Logging: add a custom trace() logging level

* Add an additional custom logging level after DEBUG called TRACE mainly
  for development debugging.
* Fail in case the same log level is already set with a different name.
  This could happen when used as a library.
* Add the --trace option to the CLI to enable said logging level.

Change-Id: Ia9725ec18031cc2af0a41410fd99d20a26186606
---
M cumin/__init__.py
M cumin/backends/__init__.py
M cumin/cli.py
M cumin/tests/unit/test_cli.py
M cumin/tests/unit/test_init.py
M cumin/transports/__init__.py
6 files changed, 70 insertions(+), 5 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/software/cumin 
refs/changes/35/366735/1

diff --git a/cumin/__init__.py b/cumin/__init__.py
index 7d450dd..20905f4 100644
--- a/cumin/__init__.py
+++ b/cumin/__init__.py
@@ -1,4 +1,5 @@
 """Automation and orchestration framework written in Python."""
+import logging
 import os
 import pkgutil
 
@@ -17,6 +18,34 @@
     """Base Exception class for all Cumin's custom Exceptions."""
 
 
+##############################################################################
+# Add a custom log level TRACE to logging for development debugging
+
+LOGGING_TRACE_LEVEL_NUMBER = 8
+LOGGING_TRACE_LEVEL_NAME = 'TRACE'
+
+# Fail if the custom logging slot is already in use with a different name or
+# Access to a private property of logging was preferred over matching the 
default string returned by
+# logging.getLevelName() for unused custom slots.
+if (logging.getLevelName(LOGGING_TRACE_LEVEL_NUMBER) != 
LOGGING_TRACE_LEVEL_NAME and
+        LOGGING_TRACE_LEVEL_NUMBER in logging._levelNames):  # pylint: 
disable=protected-access
+    raise CuminError("Unable to set custom logging for trace, logging level 
{level} is alredy set for '{name}'.".format(
+        level=LOGGING_TRACE_LEVEL_NUMBER, 
name=logging.getLevelName(LOGGING_TRACE_LEVEL_NUMBER)))
+
+
+def trace(self, msg, *args, **kwargs):
+    """Additional logging level for development debugging."""
+    if self.isEnabledFor(LOGGING_TRACE_LEVEL_NUMBER):
+        self._log(LOGGING_TRACE_LEVEL_NUMBER, msg, args, **kwargs)  # pylint: 
disable=protected-access
+
+
+# Do not override an existing custom logging method with the same name, even 
if has a different level
+if not hasattr(logging.Logger, 'trace'):
+    logging.addLevelName(LOGGING_TRACE_LEVEL_NUMBER, LOGGING_TRACE_LEVEL_NAME)
+    logging.Logger.trace = trace
+##############################################################################
+
+
 class Config(dict):
     """Singleton-like dictionary class to load the configuration from a given 
path only once."""
 
diff --git a/cumin/backends/__init__.py b/cumin/backends/__init__.py
index bdbb231..3edfd33 100644
--- a/cumin/backends/__init__.py
+++ b/cumin/backends/__init__.py
@@ -25,6 +25,8 @@
         """
         self.config = config
         self.logger = logger or logging.getLogger(__name__)
+        self.logger.trace('Backend {name} created with config: 
{config}'.format(
+            name=type(self).__name__, config=config))
 
     @abstractmethod
     def add_category(self, category, key, value=None, operator='=', neg=False):
diff --git a/cumin/cli.py b/cumin/cli.py
index 52d6c79..49b023c 100644
--- a/cumin/cli.py
+++ b/cumin/cli.py
@@ -109,6 +109,8 @@
                         help='Do not execute any command, just return the list 
of matching hosts and exit.')
     parser.add_argument('--version', action='store_true', help='Print current 
version and exit.')
     parser.add_argument('-d', '--debug', action='store_true', help='Set log 
level to DEBUG.')
+    parser.add_argument('--trace', action='store_true',
+                        help='Set log level to TRACE, a custom logging level 
intended for development debugging.')
     parser.add_argument('-i', '--interactive', action='store_true', help='Drop 
into a Python shell with the results.')
     parser.add_argument('hosts', metavar='HOSTS_QUERY', help='Hosts selection 
query')
     parser.add_argument('commands', metavar='COMMAND', nargs='*',
@@ -146,7 +148,7 @@
     return os.getenv('SUDO_USER')
 
 
-def setup_logging(filename, debug=False):
+def setup_logging(filename, debug=False, trace=False):
     """Setup the logger instance.
 
     Arguments:
@@ -164,7 +166,9 @@
     logger.addHandler(log_handler)
     logger.raiseExceptions = False
 
-    if debug:
+    if trace:
+        logger.setLevel(cumin.LOGGING_TRACE_LEVEL_NUMBER)
+    elif debug:
         logger.setLevel(logging.DEBUG)
     else:
         logger.setLevel(logging.INFO)
@@ -352,7 +356,7 @@
 
         user = get_running_user()
         config = cumin.Config(args.config)
-        setup_logging(config['log_file'], debug=args.debug)
+        setup_logging(config['log_file'], debug=args.debug, trace=args.trace)
     except cumin.CuminError as e:
         stderr(e)
         return 2
diff --git a/cumin/tests/unit/test_cli.py b/cumin/tests/unit/test_cli.py
index fcee34d..d2dba92 100644
--- a/cumin/tests/unit/test_cli.py
+++ b/cumin/tests/unit/test_cli.py
@@ -4,7 +4,7 @@
 import mock
 import pytest
 
-from cumin import cli, CuminError
+from cumin import cli, CuminError, LOGGING_TRACE_LEVEL_NUMBER
 
 # Environment variables
 _ENV = {'USER': 'root', 'SUDO_USER': 'user'}
@@ -70,11 +70,15 @@
     mocked_os.path.exists.return_value = False
     cli.setup_logging('/path/to/filename')
     logging.setLevel.assert_called_with(INFO)
+    assert file_handler.called
 
     mocked_os.path.exists.return_value = True
     cli.setup_logging('filename', debug=True)
     logging.setLevel.assert_called_with(DEBUG)
-    assert file_handler.called
+
+    mocked_os.path.exists.return_value = True
+    cli.setup_logging('filename', trace=True)
+    logging.setLevel.assert_called_with(LOGGING_TRACE_LEVEL_NUMBER)
 
 
 @mock.patch('cumin.cli.stderr')
diff --git a/cumin/tests/unit/test_init.py b/cumin/tests/unit/test_init.py
index c2b9e3f..20e77bd 100644
--- a/cumin/tests/unit/test_init.py
+++ b/cumin/tests/unit/test_init.py
@@ -1,5 +1,6 @@
 """Cumin package tests."""
 # pylint: disable=invalid-name
+import logging
 import os
 
 import pytest
@@ -111,3 +112,26 @@
     base_path = get_fixture_path(os.path.join('config', 
'valid_with_invalid_aliases'))
     with pytest.raises(cumin.CuminError, match='Unable to parse configuration 
file'):
         cumin.load_backend_aliases({}, base_path)
+
+
+def test_trace_logging_level_conflict():
+    """If the logging level for trace is already registered, should raise 
CuminError."""
+    reload(logging)  # Avoid conflict given the singleton nature of this module
+    logging.addLevelName(cumin.LOGGING_TRACE_LEVEL_NUMBER, 'CONFLICT')
+    match = 'Unable to set custom logging for trace'
+    try:  # pytest.raises doesn't catch the reload exception
+        reload(cumin)
+    except cumin.CuminError as e:
+        assert str(e).startswith(match)
+    else:
+        raise AssertionError("Failed: DID NOT RAISE {exc} matching 
'{match}'".format(
+            exc=cumin.CuminError, match=match))
+
+
+def test_trace_logging_level_existing():
+    """If there is already a trace level registered, it should use it without 
problems."""
+    reload(logging)  # Avoid conflict given the singleton nature of this module
+    logging.Logger.trace = cumin.trace
+    reload(cumin)
+    assert logging.getLevelName(cumin.LOGGING_TRACE_LEVEL_NUMBER) != 
cumin.LOGGING_TRACE_LEVEL_NAME
+    assert hasattr(logging.Logger, 'trace')
diff --git a/cumin/transports/__init__.py b/cumin/transports/__init__.py
index 6460232..22d5596 100644
--- a/cumin/transports/__init__.py
+++ b/cumin/transports/__init__.py
@@ -241,6 +241,8 @@
         """
         self.config = config
         self.logger = logger or logging.getLogger(__name__)
+        self.logger.trace('Transport {name} created with config: 
{config}'.format(
+            name=type(self).__name__, config=config))
 
         # Initialize setters values
         self._hosts = None

-- 
To view, visit https://gerrit.wikimedia.org/r/366735
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia9725ec18031cc2af0a41410fd99d20a26186606
Gerrit-PatchSet: 1
Gerrit-Project: operations/software/cumin
Gerrit-Branch: master
Gerrit-Owner: Volans <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to