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