Add Log class and logging config#184
Conversation
- plugins/module_utils/log.py: Log class for configuring Python logging across the ND collection via an external JSON DictConfig file - plugins/module_utils/logging_config.json: Ready-to-use logging config writing to /tmp/nd.log via RotatingFileHandler - tests/unit/test_log.py: Unit tests for the Log class Note: test_log.py imports from the shared test infrastructure (common_utils, enums, etc.) which resides in the nd42_rest_send branch. Tests will not run until both branches are merged into nd42_integration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ansible sanity apparently does not like the docstring above the from __future__ import.
It could be that Ansible sanity is using a regex to exactly match the import below. Trying to see if we can break this into two imports. ERROR: Found 1 future-import-boilerplate issue(s) which need to be resolved: ERROR: plugins/module_utils/log.py:0:0: missing: from __future__ import (absolute_import, division, print_function)
We may not need annotations since our type hints already import Optional. Let me try removing it.
- Move plugins/module_utils/log.py to plugins/module_utils/common/log.py - Add setup_logging() helper to simplify logging setup in Ansible modules - Update import paths in test_log.py and docstring examples - Add tests/unit package __init__.py files - Add unit tests for setup_logging() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ValidLogHandlers enum now holds handler class names rather than key names, so validation correctly rejects console/stream handlers regardless of what the user names them, and accepts file handlers with non-standard names. Added tests 00231 and 00232 to cover both previously broken cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adding for Ansible sanity tests
| @@ -0,0 +1,569 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
Since this may be one of the first set of unit tests created for this repo, the Unit test CI step can now be enabled. If it is working in this branch already it can be turned on in this PR or later in the nd42_integrated branch.
Edit:
As the note mentions it will only work in nd42_integrated, please enable the Unit test step when all is merged.
There was a problem hiding this comment.
@samiib should we track CI step in a separate issue? Do you want to create an issue for this?
There was a problem hiding this comment.
@samiib lets open a separate PR again nd42_integrated that turns on unit tests.
There was a problem hiding this comment.
👍 I can enable the step when the PRs are merged together. #196
| logging.raiseExceptions = False | ||
|
|
||
| self._config: Optional[str] = environ.get("ND_LOGGING_CONFIG", None) | ||
| self._develop: bool = False |
There was a problem hiding this comment.
I was looking at the _develop attribute and have a short question to clarify my understanding. Is my understanding correct that this attribute is used to prevent logging failures to be the reason that a module fails?
This has no importance now, but do you think as a future enhancement we should track displaying a warning to the ansible user that logs are being ignored for whatever reason? Or should we accept that logging would silently ignore and user should be responsible for having appropriate logging capabilities like disk space and permissions set etc?
There was a problem hiding this comment.
Yes, the _develop attribute disables any exceptions coming from the Python logging module. If set to True, and logging itself raises an exception, this gets passed to us to handle.
And, yes, I think, it would be good to alert the user when _develop == True so would agree that an issue should be raised to track adding this. @mikewiebe the warning would probably be via an Action plugin, right?
There was a problem hiding this comment.
@allenrobel Not sure if we could use an action plugin for this. This gets initialized in the module when log = Log() is called right? In that case it would be to late to alert the user in an action plugin.
| @@ -0,0 +1,569 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
@samiib should we track CI step in a separate issue? Do you want to create an issue for this?
| @@ -0,0 +1,569 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
@samiib lets open a separate PR again nd42_integrated that turns on unit tests.
Co-authored-by: Lionel Hercot <lhercot@cisco.com>
Co-authored-by: Lionel Hercot <lhercot@cisco.com>
Good catch @samiib
Good catch @samiib
Allows callers to pass config path and develop flag directly to the constructor while preserving property setter validation for both. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert all docstrings from ### heading style to standard # Summary / ## sections, replace double-backtick references with single backticks, and remove premature line wraps to conform to the 159-character line length limit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four categories of issues were addressed:
1. Test isolation (env var leakage between tests)
All tests that assigned directly to environ["ND_LOGGING_CONFIG"] did not
clean up afterward. Tests relying on the env var being absent (e.g.
test_log_00100) could pass for the wrong reason depending on execution
order.
Fix: replaced all direct environ[...] assignments with monkeypatch.setenv()
(pytest's built-in fixture that automatically restores env state after each
test). Added monkeypatch.delenv("ND_LOGGING_CONFIG", raising=False) to
every test that requires the env var to be absent. Removed the now-unused
"from os import environ" import.
Affected tests: test_log_00010, test_log_00100, test_log_00110,
test_log_00120, test_log_00200, test_log_00210, test_log_00220,
test_log_00230, test_log_00231, test_log_00232, test_log_00240,
test_log_00250, test_setup_logging_00010, test_setup_logging_00020.
2. Missing initialization test (test_log_00000)
No test verified the default state of Log() when ND_LOGGING_CONFIG is not
set. Added test_log_00000 to assert instance.config is None,
instance.develop is False, and logging.raiseExceptions is False.
3. Missing constructor parameter tests (test_log_00020, test_log_00030)
The Log(config=...) and Log(develop=True) constructor paths were never
exercised directly; only the property setters were tested.
- test_log_00020: passes config= directly to Log(), calls commit(), and
verifies messages appear in the log file without setting ND_LOGGING_CONFIG.
- test_log_00030: passes develop=True to Log() and asserts both
instance.develop is True and logging.raiseExceptions is True.
4. Missing property/setter tests
- test_log_00130: verifies that setting instance.config to a different file
path after construction overrides ND_LOGGING_CONFIG; confirms messages
appear in the new file and not the env-var file.
- test_log_00320 (parametrized True/False): verifies the develop setter side
effect -- that logging.raiseExceptions is updated to match the value set.
test_log_00310 already verified instance.develop == develop but never
checked the raiseExceptions side effect at setter line 404 of log.py.
- test_log_00233/00234/00235: ValidLogHandlers defines four accepted handler
classes; only RotatingFileHandler was covered. Added one test per
remaining class (FileHandler, TimedRotatingFileHandler,
WatchedFileHandler) to confirm commit() succeeds for each.
- test_setup_logging_00030: setup_logging() with ND_LOGGING_CONFIG absent
should return a Log instance with logging disabled without calling
fail_json. No test covered this path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- setup_logging() now accepts develop: bool = False, passing it through to Log() so callers can enable logging exceptions without bypassing the helper. - Updated Log class docstring Usage section to recommend setup_logging() as the primary approach, with direct Log() usage shown as an alternative. - Added test_setup_logging_00040 (parametrized True/False) to verify develop propagates correctly through setup_logging(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| try: | ||
| log = Log(develop=develop) | ||
| log.commit() | ||
| except ValueError as error: |
There was a problem hiding this comment.
Shall we take care of this or is this not required?->
The develop parameter has a type hint of bool and a default value of False. Since Python does not enforce type hints at runtime, if a caller passes a non boolean value (eg develop="yes"),
it flows into init() which calls the develop setter. The setter validates the type with isinstance() and raises TypeError for non boolean values. However, this except block only catches ValueError, so the TypeError propagates up as an unhandled exception, bypassing fail_json().
There was a problem hiding this comment.
@allenrobel My apologies for the last minute comment. Everything else looks good!
There was a problem hiding this comment.
Will merge the current PR and we should address this in a subsequent PR that also remove the need for fail_json to adhere to the placement of it only in modules main()
Will be addressed in subsequent PR against integration branch
Summary
Adds the
Logclass for configuring Python logging across the ND collection. This PR is one of two companion PRs targetingnd42_integration:nd42_logging—Logclass, logging config, andtest_log.pynd42_rest_send— RestSend framework,enums.py, and all shared test infrastructureFiles added
plugins/module_utils/log.pyLogclass — configures Python logging from an external JSONdictConfigfile; validates that only file-based handlers are used (no stdout/stderr interference with Ansible)plugins/module_utils/logging_config.jsonlogging.config.dictConfig-compatible config writing to/tmp/nd.logviaRotatingFileHandlertests/unit/test_log.pyLogclassLog class behaviour
ND_LOGGING_CONFIGenv var to locate the logging config JSON filefile-based handlers to avoid interfering with Ansible's JSON outputlogging.config.dictConfig()to configure the rootndloggerNullHandler) when no config is provideddevelopproperty controls whether the logging module raises internal exceptions (default:False)Merge order
nd42_rest_send(Add RestSend framework, enums, and shared unit test infrastructure #185) →nd42_integration(provides enums.py and shared test infrastructure)nd42_integrationTest plan
nd42_integration:python -m pytest tests/unit/test_log.pypasses cleanlytox -e linterspasses (black, isort, pylint, mypy)ansible-test sanity --dockerpasses🤖 Generated with Claude Code