pydrex.logger
PyDRex: logger settings and boilerplate.
Python's logging module is weird and its methods don't allow us to specify
which logger to use, so just using logging.debug
for example always uses
the "root" logger, which spams a bunch of messages from other imports/modules.
Instead, the methods in this module are thin wrappers that use custom
logging objects (LOGGER
and CONSOLE_LOGGER
).
The method quiet_aliens
can be invoked to suppress most messages
from third-party modules, except critical errors and warnings from Numba.
By default, PyDRex emits INFO level messages to the console.
This can be changed globally by setting the new level with CONSOLE_LOGGER.setLevel
:
from pydrex import logger as _log
_log.info("this message will be printed to the console")
_log.CONSOLE_LOGGER.setLevel("ERROR")
_log.info("this message will NOT be printed to the console")
_log.error("this message will be printed to the console")
To change the console logging level for a particular local context,
use the handler_level
context manager:
_log.CONSOLE_LOGGER.setLevel("INFO")
_log.info("this message will be printed to the console")
with handler_level("ERROR"):
_log.info("this message will NOT be printed to the console")
_log.info("this message will be printed to the console")
To save logs to a file, the pydrex.io.logfile_enable
context manager is recommended.
Always use the old printf style formatting for log messages, not fstrings,
otherwise compute time will be wasted on string conversions when logging is disabled:
from pydrex import io as _io
_log.quiet_aliens() # Suppress third-party log messages except CRITICAL from Numba.
with _io.logfile_enable("my_log_file.log"): # Overwrite existing file unless mode="a".
value = 42
_log.critical("critical error with value: %s", value)
_log.error("runtime error with value: %s", value)
_log.warning("warning with value: %s", value)
_log.info("information message with value: %s", value)
_log.debug("verbose debugging message with value: %s", value)
... # Construct Minerals, update orientations, etc.
1"""> PyDRex: logger settings and boilerplate. 2 3Python's logging module is weird and its methods don't allow us to specify 4which logger to use, so just using `logging.debug` for example always uses 5the "root" logger, which spams a bunch of messages from other imports/modules. 6Instead, the methods in this module are thin wrappers that use custom 7logging objects (`pydrex.logger.LOGGER` and `pydrex.logger.CONSOLE_LOGGER`). 8The method `quiet_aliens` can be invoked to suppress most messages 9from third-party modules, except critical errors and warnings from Numba. 10 11By default, PyDRex emits INFO level messages to the console. 12This can be changed globally by setting the new level with `CONSOLE_LOGGER.setLevel`: 13 14```python 15from pydrex import logger as _log 16_log.info("this message will be printed to the console") 17 18_log.CONSOLE_LOGGER.setLevel("ERROR") 19_log.info("this message will NOT be printed to the console") 20_log.error("this message will be printed to the console") 21``` 22 23To change the console logging level for a particular local context, 24use the `handler_level` context manager: 25 26```python 27_log.CONSOLE_LOGGER.setLevel("INFO") 28_log.info("this message will be printed to the console") 29 30with handler_level("ERROR"): 31 _log.info("this message will NOT be printed to the console") 32 33_log.info("this message will be printed to the console") 34``` 35 36To save logs to a file, the `pydrex.io.logfile_enable` context manager is recommended. 37Always use the old printf style formatting for log messages, not fstrings, 38otherwise compute time will be wasted on string conversions when logging is disabled: 39 40```python 41from pydrex import io as _io 42_log.quiet_aliens() # Suppress third-party log messages except CRITICAL from Numba. 43with _io.logfile_enable("my_log_file.log"): # Overwrite existing file unless mode="a". 44 value = 42 45 _log.critical("critical error with value: %s", value) 46 _log.error("runtime error with value: %s", value) 47 _log.warning("warning with value: %s", value) 48 _log.info("information message with value: %s", value) 49 _log.debug("verbose debugging message with value: %s", value) 50 ... # Construct Minerals, update orientations, etc. 51 52``` 53 54""" 55 56import contextlib as cl 57import functools as ft 58import logging 59import sys 60 61import numpy as np 62 63# NOTE: Do NOT import any pydrex submodules here to avoid cyclical imports. 64 65np.set_printoptions( 66 formatter={ 67 "float_kind": np.format_float_scientific, 68 "object": ft.partial(np.array2string, separator=", "), 69 }, 70 linewidth=1000, 71) 72 73 74class ConsoleFormatter(logging.Formatter): 75 """Log formatter that uses terminal color codes.""" 76 77 def colorfmt(self, code): 78 return ( 79 f"\033[{code}m%(levelname)s [%(asctime)s]\033[m" 80 + " \033[1m%(name)s:\033[m %(message)s" 81 ) 82 83 def format(self, record): 84 format_specs = { 85 logging.CRITICAL: self.colorfmt("1;31"), 86 logging.ERROR: self.colorfmt("31"), 87 logging.INFO: self.colorfmt("32"), 88 logging.WARNING: self.colorfmt("33"), 89 logging.DEBUG: self.colorfmt("34"), 90 } 91 self._style._fmt = format_specs.get(record.levelno) 92 return super().format(record) 93 94 95# To create a new logger we use getLogger as recommended by the logging docs. 96LOGGER = logging.getLogger("pydrex") 97# To allow for multiple handlers at different levels, default level must be DEBUG. 98LOGGER.setLevel(logging.DEBUG) 99# Set up console handler. 100CONSOLE_LOGGER = logging.StreamHandler() 101CONSOLE_LOGGER.setFormatter(ConsoleFormatter(datefmt="%H:%M")) 102CONSOLE_LOGGER.setLevel(logging.INFO) 103# Turn on console logger by default. 104LOGGER.addHandler(CONSOLE_LOGGER) 105 106 107def handle_exception(exec_type, exec_value, exec_traceback): 108 # Ignore KeyboardInterrupt so ^C (ctrl + C) works as expected. 109 if issubclass(exec_type, KeyboardInterrupt): 110 sys.__excepthook__(exec_type, exec_value, exec_traceback) 111 return 112 # Send other exceptions to the logger. 113 LOGGER.exception( 114 "uncaught exception", exc_info=(exec_type, exec_value, exec_traceback) 115 ) 116 117 118# Make our logger handle uncaught exceptions. 119sys.excepthook = handle_exception 120 121 122@cl.contextmanager 123def handler_level(level: str, handler: logging.Handler = CONSOLE_LOGGER): 124 """Set logging handler level for current context. 125 126 - `level` — logging level name e.g. "DEBUG", "ERROR", etc. See Python's logging 127 module for details. 128 - `handler` (optional) — alternative handler to control instead of the default, 129 `CONSOLE_LOGGER`. 130 131 """ 132 default_level = handler.level 133 handler.setLevel(level) 134 yield 135 handler.setLevel(default_level) 136 137 138def critical(msg, *args, **kwargs): 139 """Log a CRITICAL message in PyDRex.""" 140 LOGGER.critical(msg, *args, **kwargs) 141 142 143def error(msg, *args, **kwargs): 144 """Log an ERROR message in PyDRex.""" 145 LOGGER.error(msg, *args, **kwargs) 146 147 148def warning(msg, *args, **kwargs): 149 """Log a WARNING message in PyDRex.""" 150 LOGGER.warning(msg, *args, **kwargs) 151 152 153def info(msg, *args, **kwargs): 154 """Log an INFO message in PyDRex.""" 155 LOGGER.info(msg, *args, **kwargs) 156 157 158def debug(msg, *args, **kwargs): 159 """Log a DEBUG message in PyDRex.""" 160 LOGGER.debug(msg, *args, **kwargs) 161 162 163def exception(msg, *args, **kwargs): 164 """Log a message with level ERROR but retain exception information. 165 166 This function should only be called from an exception handler. 167 168 """ 169 LOGGER.exception(msg, *args, **kwargs) 170 171 172def quiet_aliens(): 173 """Restrict alien loggers 👽 because I'm trying to find MY bugs, thanks.""" 174 # Only allow warnings or above from root logger. 175 logging.getLogger().setLevel(logging.WARNING) 176 # Only allow critical stuff from other things. 177 for name in logging.Logger.manager.loggerDict.keys(): 178 if name != "pydrex": 179 logging.getLogger(name).setLevel(logging.CRITICAL) 180 # Numba is not in the list for some reason, I guess we can leave warnings. 181 logging.getLogger("numba").setLevel(logging.WARNING)
75class ConsoleFormatter(logging.Formatter): 76 """Log formatter that uses terminal color codes.""" 77 78 def colorfmt(self, code): 79 return ( 80 f"\033[{code}m%(levelname)s [%(asctime)s]\033[m" 81 + " \033[1m%(name)s:\033[m %(message)s" 82 ) 83 84 def format(self, record): 85 format_specs = { 86 logging.CRITICAL: self.colorfmt("1;31"), 87 logging.ERROR: self.colorfmt("31"), 88 logging.INFO: self.colorfmt("32"), 89 logging.WARNING: self.colorfmt("33"), 90 logging.DEBUG: self.colorfmt("34"), 91 } 92 self._style._fmt = format_specs.get(record.levelno) 93 return super().format(record)
Log formatter that uses terminal color codes.
84 def format(self, record): 85 format_specs = { 86 logging.CRITICAL: self.colorfmt("1;31"), 87 logging.ERROR: self.colorfmt("31"), 88 logging.INFO: self.colorfmt("32"), 89 logging.WARNING: self.colorfmt("33"), 90 logging.DEBUG: self.colorfmt("34"), 91 } 92 self._style._fmt = format_specs.get(record.levelno) 93 return super().format(record)
Format the specified record as text.
The record's attribute dictionary is used as the operand to a string formatting operation which yields the returned string. Before formatting the dictionary, a couple of preparatory steps are carried out. The message attribute of the record is computed using LogRecord.getMessage(). If the formatting string uses the time (as determined by a call to usesTime(), formatTime() is called to format the event time. If there is exception information, it is formatted using formatException() and appended to the message.
Inherited Members
- logging.Formatter
- Formatter
- converter
- datefmt
- default_time_format
- default_msec_format
- formatTime
- formatException
- usesTime
- formatMessage
- formatStack
108def handle_exception(exec_type, exec_value, exec_traceback): 109 # Ignore KeyboardInterrupt so ^C (ctrl + C) works as expected. 110 if issubclass(exec_type, KeyboardInterrupt): 111 sys.__excepthook__(exec_type, exec_value, exec_traceback) 112 return 113 # Send other exceptions to the logger. 114 LOGGER.exception( 115 "uncaught exception", exc_info=(exec_type, exec_value, exec_traceback) 116 )
123@cl.contextmanager 124def handler_level(level: str, handler: logging.Handler = CONSOLE_LOGGER): 125 """Set logging handler level for current context. 126 127 - `level` — logging level name e.g. "DEBUG", "ERROR", etc. See Python's logging 128 module for details. 129 - `handler` (optional) — alternative handler to control instead of the default, 130 `CONSOLE_LOGGER`. 131 132 """ 133 default_level = handler.level 134 handler.setLevel(level) 135 yield 136 handler.setLevel(default_level)
Set logging handler level for current context.
level
— logging level name e.g. "DEBUG", "ERROR", etc. See Python's logging module for details.handler
(optional) — alternative handler to control instead of the default,CONSOLE_LOGGER
.
139def critical(msg, *args, **kwargs): 140 """Log a CRITICAL message in PyDRex.""" 141 LOGGER.critical(msg, *args, **kwargs)
Log a CRITICAL message in PyDRex.
144def error(msg, *args, **kwargs): 145 """Log an ERROR message in PyDRex.""" 146 LOGGER.error(msg, *args, **kwargs)
Log an ERROR message in PyDRex.
149def warning(msg, *args, **kwargs): 150 """Log a WARNING message in PyDRex.""" 151 LOGGER.warning(msg, *args, **kwargs)
Log a WARNING message in PyDRex.
154def info(msg, *args, **kwargs): 155 """Log an INFO message in PyDRex.""" 156 LOGGER.info(msg, *args, **kwargs)
Log an INFO message in PyDRex.
159def debug(msg, *args, **kwargs): 160 """Log a DEBUG message in PyDRex.""" 161 LOGGER.debug(msg, *args, **kwargs)
Log a DEBUG message in PyDRex.
164def exception(msg, *args, **kwargs): 165 """Log a message with level ERROR but retain exception information. 166 167 This function should only be called from an exception handler. 168 169 """ 170 LOGGER.exception(msg, *args, **kwargs)
Log a message with level ERROR but retain exception information.
This function should only be called from an exception handler.
173def quiet_aliens(): 174 """Restrict alien loggers 👽 because I'm trying to find MY bugs, thanks.""" 175 # Only allow warnings or above from root logger. 176 logging.getLogger().setLevel(logging.WARNING) 177 # Only allow critical stuff from other things. 178 for name in logging.Logger.manager.loggerDict.keys(): 179 if name != "pydrex": 180 logging.getLogger(name).setLevel(logging.CRITICAL) 181 # Numba is not in the list for some reason, I guess we can leave warnings. 182 logging.getLogger("numba").setLevel(logging.WARNING)
Restrict alien loggers 👽 because I'm trying to find MY bugs, thanks.