You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Affected:easyscience.global_object.logger, all modules that
previously used print() or warnings.warn() for runtime/deprecation
messages.
Context
Until now, runtime information was presented in two ad-hoc ways:
print() statements — which always write to stdout, cannot be
silenced by the host application, and pollute notebooks, test reports,
and downstream CLIs.
warnings.warn(...) — which is better, but routes through Python's
warning machinery. Warnings are easy to accidentally suppress
globally, are de-duplicated by default (so a repeated message is shown
once), and are awkward to redirect to a log file or structured sink.
Neither gives the host application real control over what is shown and where it goes, which is the defining requirement for a well-behaved
library.
Decision
Route all informational, warning, and deprecation messages through
Python's standard logging module, under a package-root logger named easyscience, exposed through a small controller global_object.log (an instance of Logger).
Key rules:
A named logger hierarchy, not warning categories. Every subsystem
logs under a dotted child of easyscience
(easyscience.fitting.bumps, easyscience.legacy, easyscience.deprecated, easyscience.variable, …). Setting a level
on a parent silences all of its children. This replaces what warning categories used to give us, but is filterable per-subsystem rather
than per-category.
Library-safe by construction. The controller never calls logging.basicConfig() and never attaches a handler. EasyScience
only creates log records; the host application decides where they
go. With no handler configured, Python's logging.lastResort handler
still prints WARNING and above to stderr, so a standalone user
sees warnings out of the box, while INFO/DEBUG stay hidden until
they opt in.
Child loggers inherit. global_object.log.getLogger('fitting.bumps') returns a logger left
at NOTSET, so it inherits level and handlers from the easyscience
root. Modules obtain their logger through this controller method
(rather than logging.getLogger('easyscience.…') directly) so the
controller remains the single point of control if the policy changes.
The only exceptions are the modules that participate in GlobalObject's own construction (global_object/undo_redo.py, global_object/hugger/property.py), which call logging.getLogger
directly to avoid a bootstrap cycle; they are already correctly
namespaced.
Level control is a property.global_object.log.level is a
property whose getter reads the live level off the package-root
logger and whose setter applies it — there is no mirrored attribute
that can drift out of sync. A bad level string raises ValueError;
the EASYSCIENCE_LOG_LEVEL environment variable is parsed leniently
(an unset or malformed value falls back to the default rather than
breaking import).
How to use it
Emitting messages (library/internal code)
Get a child logger named for your subsystem and log on it:
fromeasyscienceimportglobal_objectlog=global_object.log.getLogger('fitting.bumps')
log.warning('Fit did not converge within %d evaluations', n)
For top-level package messages (e.g. deprecation shims), the convenience
methods log on the root easyscience logger:
global_object.log.warning('module X is deprecated; import from Y instead')
Prefer global_object.log.getLogger(...) over logging.getLogger('easyscience.…') so all output flows through the
controller.
Or via the standard library / environment, and the full set of recipes
(per-subsystem filtering, embedding in another library, routing to a
file, keeping test output clean), see the user guide: docs/docs/user-guide/controlling-log-output.md.
Consequences
Positive
Host applications and test frameworks get full control over output and
its destination; nothing is hard-wired to stdout.
Per-subsystem verbosity via the logger hierarchy.
No global side effects on import (no basicConfig, no handlers).
Negative / trade-offs
We lose Python's warning category machinery (-W flags, DeprecationWarning filtering, pytest filterwarnings). The
hierarchy covers per-subsystem filtering, but for genuine API
deprecations that external tooling should detect, emitting a real DeprecationWarning (in addition to, or instead of, a log message)
remains a valid option to revisit.
Code must obtain loggers through the controller, a small convention
contributors need to follow.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
ADR: Logging via the standard
loggingmoduleeasyscience.global_object.logger, all modules thatpreviously used
print()orwarnings.warn()for runtime/deprecationmessages.
Context
Until now, runtime information was presented in two ad-hoc ways:
print()statements — which always write tostdout, cannot besilenced by the host application, and pollute notebooks, test reports,
and downstream CLIs.
warnings.warn(...)— which is better, but routes through Python'swarning machinery. Warnings are easy to accidentally suppress
globally, are de-duplicated by default (so a repeated message is shown
once), and are awkward to redirect to a log file or structured sink.
Neither gives the host application real control over what is shown and
where it goes, which is the defining requirement for a well-behaved
library.
Decision
Route all informational, warning, and deprecation messages through
Python's standard
loggingmodule, under a package-root logger namedeasyscience, exposed through a small controllerglobal_object.log(an instance ofLogger).Key rules:
A named logger hierarchy, not warning categories. Every subsystem
logs under a dotted child of
easyscience(
easyscience.fitting.bumps,easyscience.legacy,easyscience.deprecated,easyscience.variable, …). Setting a levelon a parent silences all of its children. This replaces what warning
categories used to give us, but is filterable per-subsystem rather
than per-category.
Library-safe by construction. The controller never calls
logging.basicConfig()and never attaches a handler. EasyScienceonly creates log records; the host application decides where they
go. With no handler configured, Python's
logging.lastResorthandlerstill prints
WARNINGand above tostderr, so a standalone usersees warnings out of the box, while
INFO/DEBUGstay hidden untilthey opt in.
Child loggers inherit.
global_object.log.getLogger('fitting.bumps')returns a logger leftat
NOTSET, so it inherits level and handlers from theeasyscienceroot. Modules obtain their logger through this controller method
(rather than
logging.getLogger('easyscience.…')directly) so thecontroller remains the single point of control if the policy changes.
The only exceptions are the modules that participate in
GlobalObject's own construction (global_object/undo_redo.py,global_object/hugger/property.py), which calllogging.getLoggerdirectly to avoid a bootstrap cycle; they are already correctly
namespaced.
Level control is a property.
global_object.log.levelis aproperty whose getter reads the live level off the package-root
logger and whose setter applies it — there is no mirrored attribute
that can drift out of sync. A bad level string raises
ValueError;the
EASYSCIENCE_LOG_LEVELenvironment variable is parsed leniently(an unset or malformed value falls back to the default rather than
breaking import).
How to use it
Emitting messages (library/internal code)
Get a child logger named for your subsystem and log on it:
For top-level package messages (e.g. deprecation shims), the convenience
methods log on the root
easysciencelogger:Prefer
global_object.log.getLogger(...)overlogging.getLogger('easyscience.…')so all output flows through thecontroller.
Silencing / redirecting (consumers)
Or via the standard library / environment, and the full set of recipes
(per-subsystem filtering, embedding in another library, routing to a
file, keeping test output clean), see the user guide:
docs/docs/user-guide/controlling-log-output.md.Consequences
Positive
its destination; nothing is hard-wired to
stdout.basicConfig, no handlers).Negative / trade-offs
-Wflags,DeprecationWarningfiltering, pytestfilterwarnings). Thehierarchy covers per-subsystem filtering, but for genuine API
deprecations that external tooling should detect, emitting a real
DeprecationWarning(in addition to, or instead of, a log message)remains a valid option to revisit.
contributors need to follow.
Beta Was this translation helpful? Give feedback.
All reactions