Logging#
Since code is bound to contain bugs, and this library is no exception, logging is an important aspect of asyncutils. Users can also see what
is being done under the hood and view the timestamps of significant events, without exercising advanced reflection or metaprogramming or attaching a
tracer, profiler or debugger.
This module employs logging as provided by the standard library, which may contribute to a significant chunk of the boot time of this module but is
much too widely used for us to find alternatives such as loguru, especially due to concerns in adding a dependency and compatibility issues.
The logger is named ‘asyncutils’ and retrievable by a logging.getLogger() call as expected. No submodule-specific logger is used for fear that
output would be spread too thin. To keep things simple, the logging level defaults to warning, and only the standard levels are used; that is, there
is no level such as ‘trace’ or ‘subwarning’.
While we also use auditing for applications where it is deemed useful to have custom behaviour programatically triggered, we follow the DRY (don’t repeat yourself) philosophy, such that most audit events and logs are mutually exclusive, and anything displayed in the console banner is not logged.
Tip
If logging is still desired then, an audit hook that calls the logger if and only if the name of the event begins with ‘asyncutils’ should be added
using sys.addaudithook(), but performance may take a sizable hit.
As to how the loquacity and output whereabouts of the logger can be altered, refer to the following snippets:
config submodule#class debugging:
'''A context manager used to enter and exit debug mode, ensuring restoration of the original level if the level has not been modified externally within the context using :func:`set_logger_level`.'''
@property
def level(self) -> int: '''The current level of the :mod:`asyncutils` logger, as an integer.'''
@property
def orig_level(self) -> int|None: '''The original logger level as an integer, before this context was entered, or `None` if it was not.'''
@property
def orig_name(self) -> str|None: '''The original logger level name as a string, before this context was entered, or `None` if it was not.'''
@property
def entered(self) -> bool: '''Whether the context is entered.'''
def __enter__(self) -> Self: '''Start debugging. More output is produced; where to depends on the user's own configuration, accessible via :data:`logging_to` and :attr:`debug.level`.'''
@overload
def __exit__(self, exc_typ: ExcType, exc_val: BaseException, exc_tb: TracebackType, /) -> None: ...
@overload
def __exit__(self, exc_typ: None, exc_val: None, exc_tb: None, /) -> None: '''Stop debugging, restoring the output to its previous level if appropriate.'''
def set_logger_level(level: int) -> None: '''Set the level of the module-global logger to `level`.'''
def get_past_logs() -> str: '''Return all stored logs as a string. Logs are only stored if asyncutils was started with ``-l MEMORY``, otherwise an empty string is returned.'''
debug: Final[debugging]
'''A global instance of the :class:`debugging` context manager. Initially entered if and only if the user specified ``-d`` or ``--debug`` when starting the program.'''
silent: Final[bool]
'''Whether the user requested to run the program with no banner and exit message in the REPL.'''
logging_to: Final[str]
'''The name (path; possibly relative) of the log file currently used by this library as a string, with four exceptions:
* `'NULL'`: no logging is taking place
* `'MEMORY'`: the logs are not going to a physical file but can be retrieved by :func:`get_past_logs`
* `'STDOUT'`: logging is going to :data:`sys.stdout`
* `'STDERR'`: logging is going to :data:`sys.stderr` (following the default and fallback behaviour of :mod:`logging`)'''
{
logging_to: "STDERR",
// string file path or integer file descriptor, to which the logging is dumped
// corresponds to -l/--log-to
no_log: false,
// disable logging; conflicts with logging_to if true
// corresponds to -n/--no-log
V: 0, // increase logging verbosity; corresponds to -V
Q: 0, // decrease logging verbosity; corresponds to -Q
// V and Q are not respected when running the console
quiet: false,
// ask the console not to show introductory and closing text
// corresponds to -q/--quiet
debug: false,
// turn on debug mode by entering the global debug context manager
// incurs performance penalty due to excessive logging, so do not use outside testing
// corresponds to -d/--debug
}
The format of each log message as printed is “<asctime> - asyncutils - <levelname> - <message>”.
Note
Though the above is stable and allows deterministic parsing of a log file, it is recommended to attach custom handlers to the logger using the
logging API instead to achieve the same effect more efficiently and less hackily.