proteusPy.logger_config

This module provides utility functions for configuring and managing loggers within the proteusPy package. The functions are used within the package to convey logging information at a fine-grained level. The functions are completely independent of the application and can be used in any Python project.

Author: Eric G. Suchanek, PhD Last update: 2025-04-25 19:10:55

  1"""
  2This module provides utility functions for configuring and managing loggers
  3within the proteusPy package. The functions are used within the package to
  4convey logging information at a fine-grained level. The functions are completely
  5independent of the application and can be used in any Python project.
  6
  7Author: Eric G. Suchanek, PhD
  8Last update: 2025-04-25 19:10:55
  9"""
 10
 11import logging
 12from pathlib import Path
 13
 14from rich.logging import RichHandler
 15
 16DEFAULT_LOG_LEVEL = logging.WARNING
 17
 18
 19def set_logging_level_for_all_handlers(log_level: int):
 20    """
 21    Sets the logging level for all handlers of all loggers in the proteusPy package.
 22
 23    :param log_level: The logging level to set.
 24    :type log_level: int
 25    """
 26    root_logger = logging.getLogger()
 27    root_logger.setLevel(log_level)
 28
 29    for logger_name in logging.Logger.manager.loggerDict:
 30        _logger = logging.getLogger(logger_name)
 31        _logger.setLevel(log_level)
 32        for handler in _logger.handlers:
 33            handler.setLevel(log_level)
 34
 35
 36def disable_stream_handlers_for_namespace(namespace: str):
 37    """
 38    Disables all stream handlers for all loggers under the specified namespace.
 39
 40    :param namespace: The namespace whose stream handlers should be disabled.
 41    :type namespace: str
 42    """
 43    logger = logging.getLogger(namespace)
 44    for handler in logger.handlers[:]:
 45        if isinstance(handler, logging.StreamHandler):
 46            logger.removeHandler(handler)
 47
 48    for logger_name in logging.Logger.manager.loggerDict:
 49        if logger_name.startswith(namespace):
 50            _logger = logging.getLogger(logger_name)
 51            for handler in _logger.handlers[:]:
 52                if isinstance(handler, logging.StreamHandler):
 53                    _logger.removeHandler(handler)
 54
 55
 56def configure_master_logger(
 57    log_file: str,
 58    file_path: str = "~/logs",
 59    log_level: int = logging.ERROR,
 60    disabled: bool = False,
 61) -> None:
 62    """
 63    Configures the root logger to write to a specified log file.
 64
 65    :param log_file: Name of the log file.
 66    :type log_file: str
 67    :param file_path: Path to the directory where log files will be stored. Defaults to '~/logs'.
 68    :type file_path: str
 69    :param log_level: The logging level to set. Defaults to logging.ERROR.
 70    :type log_level: int
 71    :param disabled: If True, the logger will be disabled. Defaults to False.
 72    :type disabled: bool
 73    """
 74    file_path = Path(file_path).expanduser()
 75    file_path.mkdir(parents=True, exist_ok=True)
 76    full_log_file_path = file_path / log_file
 77
 78    root_logger = logging.getLogger()
 79    root_logger.setLevel(log_level)
 80
 81    # Remove all existing handlers
 82    root_logger.handlers.clear()
 83
 84    # Add FileHandler
 85    handler = logging.FileHandler(full_log_file_path, mode="w")
 86    formatter = logging.Formatter(
 87        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
 88    )
 89    handler.setFormatter(formatter)
 90    handler.setLevel(log_level)
 91    root_logger.addHandler(handler)
 92
 93    root_logger.disabled = disabled
 94
 95
 96def create_logger(
 97    name: str,
 98    log_level: int = logging.INFO,
 99    propagate: bool = False,  # Default to False to avoid duplicates
100) -> logging.Logger:
101    """
102    Returns a logger with the specified name, configured to use a RichHandler for console output.
103
104    :param name: The name of the logger.
105    :type name: str
106    :param log_level: The logging level, defaults to logging.INFO
107    :type log_level: int
108    :param propagate: Whether to propagate messages to parent loggers, defaults to False
109    :type propagate: bool
110    :return: Configured logger instance.
111    :rtype: logging.Logger
112    """
113    logger = logging.getLogger(name)
114    logger.setLevel(log_level)
115
116    # Clear existing handlers
117    logger.handlers.clear()
118
119    # Add RichHandler for console output
120    rich_handler = RichHandler(rich_tracebacks=True)
121    rich_formatter = logging.Formatter(
122        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
123    )
124    rich_handler.setLevel(log_level)
125    rich_handler.setFormatter(rich_formatter)
126    logger.addHandler(rich_handler)
127
128    # Set propagation
129    logger.propagate = propagate
130
131    return logger
132
133
134def set_logger_level(name, level):
135    """
136    Sets the logging level for the logger with the specified name.
137
138    :param name: The name of the logger.
139    :type name: str
140    :param level: The logging level to set. Must be one of ["WARNING", "ERROR", "INFO", "DEBUG"].
141    :type level: str
142    :raises ValueError: If the provided level is not one of the allowed values.
143    """
144    level_dict = {
145        "WARNING": logging.WARNING,
146        "ERROR": logging.ERROR,
147        "INFO": logging.INFO,
148        "DEBUG": logging.DEBUG,
149    }
150
151    if level not in level_dict:
152        raise ValueError(
153            f"set_logger_level(): Invalid logging level: {level}. "
154            "Must be one of ['WARNING', 'ERROR', 'INFO', 'DEBUG']"
155        )
156
157    _logger = logging.getLogger(name)
158    _logger.setLevel(level_dict[level])
159
160    for handler in _logger.handlers:
161        handler.setLevel(level_dict[level])
162
163
164def toggle_stream_handler(name, enable):
165    """
166    Enables or disables the StreamHandler for the logger with the specified name.
167
168    :param name: The name of the logger.
169    :type name: str
170    :param enable: If True, enables the StreamHandler; if False, disables it.
171    :type enable: bool
172    """
173    logger = logging.getLogger(name)
174    stream_handler = None
175
176    for handler in logger.handlers:
177        if isinstance(handler, logging.StreamHandler):
178            stream_handler = handler
179            break
180
181    if enable:
182        if stream_handler is None:
183            formatter = logging.Formatter(
184                "stream proteusPy: %(levelname)-7s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
185            )
186            stream_handler = logging.StreamHandler()
187            stream_handler.setLevel(logger.level)
188            stream_handler.setFormatter(formatter)
189            logger.addHandler(stream_handler)
190    else:
191        if stream_handler is not None:
192            logger.removeHandler(stream_handler)
193
194
195def list_all_loggers():
196    """
197    Lists all loggers that have been created in the application.
198
199    :return: List of logger names.
200    :rtype: list
201    """
202    logger_dict = logging.Logger.manager.loggerDict
203    loggers = [name for name, logger in logger_dict.items() if isinstance(logger, logging.Logger)]
204    return loggers
205
206
207def list_handlers(name):
208    """
209    Lists the handlers for the logger with the specified name.
210
211    :param name: The name of the logger.
212    :type name: str
213    :return: List of handler types and their configurations.
214    :rtype: list
215    """
216    logger = logging.getLogger(name)
217    handlers_info = []
218
219    for handler in logger.handlers:
220        handler_type = type(handler).__name__
221        handler_info = {
222            "type": handler_type,
223            "level": logging.getLevelName(handler.level),
224            "formatter": handler.formatter._fmt if handler.formatter else None,
225        }
226        handlers_info.append(handler_info)
227
228    return handlers_info
229
230
231def set_logger_level_for_module(pkg_name, level=""):
232    """
233    Set the logging level for all loggers within a specified package.
234
235    This function iterates through all registered loggers and sets the logging
236    level for those that belong to the specified package.
237
238    :param pkg_name: The name of the package for which to set the logging level.
239    :type pkg_name: str
240    :param level: The logging level to set (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
241                  If not specified, the logging level will not be changed.
242    :type level: str, optional
243    :return: A list of logger names that were found and had their levels set.
244    :rtype: list
245    """
246    logger_dict = logging.Logger.manager.loggerDict
247    registered_loggers = [
248        name
249        for name, logger in logger_dict.items()
250        if isinstance(logger, logging.Logger) and name.startswith(pkg_name)
251    ]
252    for logger_name in registered_loggers:
253        logger = logging.getLogger(logger_name)
254        if level:
255            logger.setLevel(level)
256
257    return registered_loggers
258
259
260if __name__ == "__main__":
261    import doctest
262
263    doctest.testmod()
264
265# end of file
DEFAULT_LOG_LEVEL = 30
def set_logging_level_for_all_handlers(log_level: int):
20def set_logging_level_for_all_handlers(log_level: int):
21    """
22    Sets the logging level for all handlers of all loggers in the proteusPy package.
23
24    :param log_level: The logging level to set.
25    :type log_level: int
26    """
27    root_logger = logging.getLogger()
28    root_logger.setLevel(log_level)
29
30    for logger_name in logging.Logger.manager.loggerDict:
31        _logger = logging.getLogger(logger_name)
32        _logger.setLevel(log_level)
33        for handler in _logger.handlers:
34            handler.setLevel(log_level)

Sets the logging level for all handlers of all loggers in the proteusPy package.

Parameters
  • log_level: The logging level to set.
def disable_stream_handlers_for_namespace(namespace: str):
37def disable_stream_handlers_for_namespace(namespace: str):
38    """
39    Disables all stream handlers for all loggers under the specified namespace.
40
41    :param namespace: The namespace whose stream handlers should be disabled.
42    :type namespace: str
43    """
44    logger = logging.getLogger(namespace)
45    for handler in logger.handlers[:]:
46        if isinstance(handler, logging.StreamHandler):
47            logger.removeHandler(handler)
48
49    for logger_name in logging.Logger.manager.loggerDict:
50        if logger_name.startswith(namespace):
51            _logger = logging.getLogger(logger_name)
52            for handler in _logger.handlers[:]:
53                if isinstance(handler, logging.StreamHandler):
54                    _logger.removeHandler(handler)

Disables all stream handlers for all loggers under the specified namespace.

Parameters
  • namespace: The namespace whose stream handlers should be disabled.
def configure_master_logger( log_file: str, file_path: str = '~/logs', log_level: int = 40, disabled: bool = False) -> None:
57def configure_master_logger(
58    log_file: str,
59    file_path: str = "~/logs",
60    log_level: int = logging.ERROR,
61    disabled: bool = False,
62) -> None:
63    """
64    Configures the root logger to write to a specified log file.
65
66    :param log_file: Name of the log file.
67    :type log_file: str
68    :param file_path: Path to the directory where log files will be stored. Defaults to '~/logs'.
69    :type file_path: str
70    :param log_level: The logging level to set. Defaults to logging.ERROR.
71    :type log_level: int
72    :param disabled: If True, the logger will be disabled. Defaults to False.
73    :type disabled: bool
74    """
75    file_path = Path(file_path).expanduser()
76    file_path.mkdir(parents=True, exist_ok=True)
77    full_log_file_path = file_path / log_file
78
79    root_logger = logging.getLogger()
80    root_logger.setLevel(log_level)
81
82    # Remove all existing handlers
83    root_logger.handlers.clear()
84
85    # Add FileHandler
86    handler = logging.FileHandler(full_log_file_path, mode="w")
87    formatter = logging.Formatter(
88        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
89    )
90    handler.setFormatter(formatter)
91    handler.setLevel(log_level)
92    root_logger.addHandler(handler)
93
94    root_logger.disabled = disabled

Configures the root logger to write to a specified log file.

Parameters
  • log_file: Name of the log file.
  • file_path: Path to the directory where log files will be stored. Defaults to '~/logs'.
  • log_level: The logging level to set. Defaults to logging.ERROR.
  • disabled: If True, the logger will be disabled. Defaults to False.
def create_logger( name: str, log_level: int = 20, propagate: bool = False) -> logging.Logger:
 97def create_logger(
 98    name: str,
 99    log_level: int = logging.INFO,
100    propagate: bool = False,  # Default to False to avoid duplicates
101) -> logging.Logger:
102    """
103    Returns a logger with the specified name, configured to use a RichHandler for console output.
104
105    :param name: The name of the logger.
106    :type name: str
107    :param log_level: The logging level, defaults to logging.INFO
108    :type log_level: int
109    :param propagate: Whether to propagate messages to parent loggers, defaults to False
110    :type propagate: bool
111    :return: Configured logger instance.
112    :rtype: logging.Logger
113    """
114    logger = logging.getLogger(name)
115    logger.setLevel(log_level)
116
117    # Clear existing handlers
118    logger.handlers.clear()
119
120    # Add RichHandler for console output
121    rich_handler = RichHandler(rich_tracebacks=True)
122    rich_formatter = logging.Formatter(
123        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
124    )
125    rich_handler.setLevel(log_level)
126    rich_handler.setFormatter(rich_formatter)
127    logger.addHandler(rich_handler)
128
129    # Set propagation
130    logger.propagate = propagate
131
132    return logger

Returns a logger with the specified name, configured to use a RichHandler for console output.

Parameters
  • name: The name of the logger.
  • log_level: The logging level, defaults to logging.INFO
  • propagate: Whether to propagate messages to parent loggers, defaults to False
Returns

Configured logger instance.

def set_logger_level(name, level):
135def set_logger_level(name, level):
136    """
137    Sets the logging level for the logger with the specified name.
138
139    :param name: The name of the logger.
140    :type name: str
141    :param level: The logging level to set. Must be one of ["WARNING", "ERROR", "INFO", "DEBUG"].
142    :type level: str
143    :raises ValueError: If the provided level is not one of the allowed values.
144    """
145    level_dict = {
146        "WARNING": logging.WARNING,
147        "ERROR": logging.ERROR,
148        "INFO": logging.INFO,
149        "DEBUG": logging.DEBUG,
150    }
151
152    if level not in level_dict:
153        raise ValueError(
154            f"set_logger_level(): Invalid logging level: {level}. "
155            "Must be one of ['WARNING', 'ERROR', 'INFO', 'DEBUG']"
156        )
157
158    _logger = logging.getLogger(name)
159    _logger.setLevel(level_dict[level])
160
161    for handler in _logger.handlers:
162        handler.setLevel(level_dict[level])

Sets the logging level for the logger with the specified name.

Parameters
  • name: The name of the logger.
  • level: The logging level to set. Must be one of ["WARNING", "ERROR", "INFO", "DEBUG"].
Raises
  • ValueError: If the provided level is not one of the allowed values.
def toggle_stream_handler(name, enable):
165def toggle_stream_handler(name, enable):
166    """
167    Enables or disables the StreamHandler for the logger with the specified name.
168
169    :param name: The name of the logger.
170    :type name: str
171    :param enable: If True, enables the StreamHandler; if False, disables it.
172    :type enable: bool
173    """
174    logger = logging.getLogger(name)
175    stream_handler = None
176
177    for handler in logger.handlers:
178        if isinstance(handler, logging.StreamHandler):
179            stream_handler = handler
180            break
181
182    if enable:
183        if stream_handler is None:
184            formatter = logging.Formatter(
185                "stream proteusPy: %(levelname)-7s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
186            )
187            stream_handler = logging.StreamHandler()
188            stream_handler.setLevel(logger.level)
189            stream_handler.setFormatter(formatter)
190            logger.addHandler(stream_handler)
191    else:
192        if stream_handler is not None:
193            logger.removeHandler(stream_handler)

Enables or disables the StreamHandler for the logger with the specified name.

Parameters
  • name: The name of the logger.
  • enable: If True, enables the StreamHandler; if False, disables it.
def list_all_loggers():
196def list_all_loggers():
197    """
198    Lists all loggers that have been created in the application.
199
200    :return: List of logger names.
201    :rtype: list
202    """
203    logger_dict = logging.Logger.manager.loggerDict
204    loggers = [name for name, logger in logger_dict.items() if isinstance(logger, logging.Logger)]
205    return loggers

Lists all loggers that have been created in the application.

Returns

List of logger names.

def list_handlers(name):
208def list_handlers(name):
209    """
210    Lists the handlers for the logger with the specified name.
211
212    :param name: The name of the logger.
213    :type name: str
214    :return: List of handler types and their configurations.
215    :rtype: list
216    """
217    logger = logging.getLogger(name)
218    handlers_info = []
219
220    for handler in logger.handlers:
221        handler_type = type(handler).__name__
222        handler_info = {
223            "type": handler_type,
224            "level": logging.getLevelName(handler.level),
225            "formatter": handler.formatter._fmt if handler.formatter else None,
226        }
227        handlers_info.append(handler_info)
228
229    return handlers_info

Lists the handlers for the logger with the specified name.

Parameters
  • name: The name of the logger.
Returns

List of handler types and their configurations.

def set_logger_level_for_module(pkg_name, level=''):
232def set_logger_level_for_module(pkg_name, level=""):
233    """
234    Set the logging level for all loggers within a specified package.
235
236    This function iterates through all registered loggers and sets the logging
237    level for those that belong to the specified package.
238
239    :param pkg_name: The name of the package for which to set the logging level.
240    :type pkg_name: str
241    :param level: The logging level to set (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
242                  If not specified, the logging level will not be changed.
243    :type level: str, optional
244    :return: A list of logger names that were found and had their levels set.
245    :rtype: list
246    """
247    logger_dict = logging.Logger.manager.loggerDict
248    registered_loggers = [
249        name
250        for name, logger in logger_dict.items()
251        if isinstance(logger, logging.Logger) and name.startswith(pkg_name)
252    ]
253    for logger_name in registered_loggers:
254        logger = logging.getLogger(logger_name)
255        if level:
256            logger.setLevel(level)
257
258    return registered_loggers

Set the logging level for all loggers within a specified package.

This function iterates through all registered loggers and sets the logging level for those that belong to the specified package.

Parameters
  • pkg_name: The name of the package for which to set the logging level.
  • level: The logging level to set (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). If not specified, the logging level will not be changed.
Returns

A list of logger names that were found and had their levels set.