Audit: Console script logging setup¶
What we check¶
Every file named as an entry point in pyproject.toml -- by
[project.scripts], [project.gui-scripts] or
[project.entry-points."console_scripts"] -- that calls
shakenfist_utilities.logs.setup_console() must also:
- call
logging.basicConfig(); and - stop its own logger propagating into the root handler.
LOG = logs.setup_console(__name__)
logging.basicConfig(level=logging.INFO)
logging.getLogger(__name__).propagate = False
The propagate assignment is matched against the entry point's own
logger -- the name bound to setup_console()'s return, or
getLogger() called with the same argument it was given. A line
silencing an unrelated third-party logger does not satisfy it, because
the entry point still emits every one of its own lines twice. Where a
file makes several setup_console() calls, the one given __name__
is the file configuring itself, and is the one that decides this.
All three matches are made against the file's code: comments and
string literals are blanked first. A commented-out
logging.basicConfig() is the state of anything somebody was
debugging and is exactly the misconfiguration this exists to catch, so
counting it as a call would pass the file for the defect it has. The
audit-ok marker is read from a complementary view in which string
bodies are blanked and comments survive, because a marker is a
comment: reading it from the whole file let a docstring that merely
mentioned the marker exempt the module that mentioned it.
setup_console() raises the root logger's level to INFO but attaches
its handler to the named logger only. Records from every other
module therefore propagate up to a root logger with no handler on it
and are dropped -- so without basicConfig() the entry point sees its
own INFO messages and nothing else. Once root does have a handler, the
entry point's own records reach both it and the handler
setup_console() installed, which is what propagate = False
prevents.
Only declared entry points are examined. occystrap calls
logs.setup_console(__name__) at the top of all 24 of its modules,
and only occystrap/main.py is an entry point; a call anywhere else
is a module getting a logger, not a console script setting up logging.
A repository that declares no console scripts, or whose entry points
do not use the helper, is not applicable -- this is a rule about how
the helper is used, not a requirement to use it.
An entry point is resolved to a file by trying pkg/mod.py,
pkg/__init__.py and both of those under src/. A repository whose
layout is none of those -- lib/, say -- is reported as having
declared entry points that did not resolve, naming them, rather than
as having declared none: the two are different facts, and the second
is a clean bill for a file nobody looked at. A declaration that is
malformed rather than merely unresolvable -- scripts given as a
string, a target that is not one -- is named the same way, and does
not stop the rest of the repository being audited.
An unresolved declaration is named in every outcome, not only when nothing resolved at all. A repository with a mixed layout collected a pass on the entry points that were found while the one that was not went unmentioned, which is the same clean bill for a file nobody opened. A pass is a statement about every entry point the repository declares, so one that resolved to nothing withholds it: the ones that did resolve are reported as compliant, but the criterion has not been assessed.
A file that genuinely should not configure logging -- because
something else in the process already has -- carries an
audit-ok: console-logging comment, ideally with a reason. The marker
is read per file rather than per line: the finding is about the file's
logging setup as a whole, so there is no single line for it to sit on.
Verbose handling¶
Part of the standard, and reviewed rather than measured. When a
--verbose or --debug flag is handled, the root handler level has
to move too, or raising LOG's level alone changes nothing:
if verbose:
logging.root.setLevel(logging.DEBUG)
for handler in logging.root.handlers:
handler.setLevel(logging.DEBUG)
LOG.setLevel(logging.DEBUG)
This is not checked because there is no reliable signal for "this
entry point has a verbosity flag" that does not also match parsers
that pass the value straight through. occystrap's main.py is the
worked example of the shape.
Template¶
No template -- this is a code-level pattern.
Projects¶
Per-project compliance for this criterion is regenerated every morning by the consistency audit: see the compliance page.