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 updated 2025-02-19 23:43:45 -egs-

  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 updated 2025-02-19 23:43:45 -egs-
  9"""
 10
 11import logging
 12from pathlib import Path
 13
 14DEFAULT_LOG_LEVEL = logging.WARNING
 15
 16
 17def set_logging_level_for_all_handlers(log_level: int):
 18    """
 19    Sets the logging level for all handlers of all loggers in the proteusPy package.
 20
 21    :param log_level: The logging level to set.
 22    :type log_level: int
 23    """
 24    # Get the root logger
 25    root_logger = logging.getLogger()
 26
 27    # Set the level for the root logger
 28    root_logger.setLevel(log_level)
 29
 30    # Iterate through all loggers
 31    for logger_name in logging.Logger.manager.loggerDict:
 32        _logger = logging.getLogger(logger_name)
 33
 34        # Set the level for the logger itself
 35        _logger.setLevel(log_level)
 36
 37        # Iterate through all handlers of the logger
 38        for handler in _logger.handlers:
 39            handler.setLevel(log_level)
 40
 41
 42def disable_stream_handlers_for_namespace(namespace: str):
 43    """
 44    Disables all stream handlers for all loggers under the specified namespace.
 45
 46    :param namespace: The namespace whose stream handlers should be disabled.
 47    :type namespace: str
 48    """
 49    # First check the specific logger for the namespace
 50    logger = logging.getLogger(namespace)
 51    for handler in logger.handlers[
 52        :
 53    ]:  # Create a copy of the list to safely modify during iteration
 54        if isinstance(handler, logging.StreamHandler):
 55            logger.removeHandler(handler)
 56
 57    # Then check all loggers in the manager's dictionary that start with the namespace
 58    for logger_name in logging.Logger.manager.loggerDict:
 59        if logger_name.startswith(namespace):
 60            _logger = logging.getLogger(logger_name)
 61            for handler in _logger.handlers[
 62                :
 63            ]:  # Create a copy of the list to safely modify during iteration
 64                if isinstance(handler, logging.StreamHandler):
 65                    _logger.removeHandler(handler)
 66
 67
 68def configure_master_logger(
 69    log_file: str,
 70    file_path: str = "~/logs",
 71    log_level: int = logging.ERROR,
 72    disabled: bool = False,
 73) -> None:
 74    """
 75    Configures the root logger to write to a specified log file.
 76
 77    :param log_file: Name of the log file.
 78    :type log_file: str
 79    :param file_path: Path to the directory where log files will be stored. Defaults to '~/logs'.
 80    :type file_path: str
 81    :param log_level: The logging level to set. Defaults to logging.ERROR.
 82    :type log_level: int
 83    :param disabled: If True, the logger will be disabled. Defaults to False.
 84    :type disabled: bool
 85    """
 86    # Expand user path
 87    file_path = Path(file_path).expanduser()
 88
 89    # Ensure the directory exists
 90    file_path.mkdir(parents=True, exist_ok=True)
 91
 92    # Full path to the log file
 93    full_log_file_path = file_path / log_file
 94
 95    root_logger = logging.getLogger()
 96
 97    # Set the root logger level to DEBUG to capture all messages
 98    root_logger.setLevel(log_level)
 99
100    # Remove all existing handlers
101    for handler in root_logger.handlers[:]:
102        root_logger.removeHandler(handler)
103
104    # Create a new FileHandler
105    handler = logging.FileHandler(full_log_file_path, mode="w")
106
107    formatter = logging.Formatter(
108        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
109    )
110    handler.setFormatter(formatter)
111
112    root_logger.addHandler(handler)
113    for handler in root_logger.handlers:
114        handler.setLevel(log_level)
115
116    if disabled:
117        root_logger.disabled = True
118    else:
119        root_logger.disabled = False  # Enable the root logger
120
121    return
122
123
124def create_logger(
125    name: str,
126    log_level: int = logging.INFO,
127) -> logging.Logger:
128    """
129    Returns a logger with the specified name, configured to use both StreamHandler
130    and RotatingFileHandler with the predefined formats.
131
132    :param name: The name of the logger.
133    :type name: str
134    :param log_level: The logging level, defaults to logging.INFO
135    :type log_level: int, optional
136    :return: Configured logger instance.
137    :rtype: logging.Logger
138    """
139    logger = logging.getLogger(name)
140    logger.setLevel(log_level)
141
142    # Clear all existing handlers
143    if logger.hasHandlers():
144        logger.handlers.clear()
145
146    # Define formatter
147    formatter = logging.Formatter(
148        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
149    )
150
151    # StreamHandler setup
152    stream_handler = logging.StreamHandler()
153    stream_handler.setLevel(log_level)
154    stream_handler.setFormatter(formatter)
155    logger.addHandler(stream_handler)
156
157    # Allows log messages to propagate to the root logger
158    logger.propagate = True
159
160    return logger
161
162
163def set_logger_level(name, level):
164    """
165    Sets the logging level for the logger with the specified name.
166
167    :param name: The name of the logger.
168    :type name: str
169    :param level: The logging level to set. Must be one of ["WARNING", "ERROR", "INFO", "DEBUG"].
170    :type level: str
171    :raises ValueError: If the provided level is not one of the allowed values.
172    """
173    level_dict = {
174        "WARNING": logging.WARNING,
175        "ERROR": logging.ERROR,
176        "INFO": logging.INFO,
177        "DEBUG": logging.DEBUG,
178    }
179
180    if level not in level_dict:
181        raise ValueError(
182            (
183                f"--> set_logger_level(): Invalid logging level: {level}."
184                f"Must be one of ['WARNING', 'ERROR', 'INFO', 'DEBUG']"
185            )
186        )
187
188    _logger = logging.getLogger(name)
189    _logger.setLevel(level_dict[level])
190
191    for handler in _logger.handlers:
192        handler.setLevel(level_dict[level])
193
194
195def toggle_stream_handler(name, enable):
196    """
197    Enables or disables the StreamHandler for the logger with the specified name.
198
199    :param name: The name of the logger.
200    :type name: str
201    :param enable: If True, enables the StreamHandler; if False, disables it.
202    :type enable: bool
203    """
204    logger = logging.getLogger(name)
205    stream_handler = None
206
207    # Find the StreamHandler if it exists
208    for handler in logger.handlers:
209        if isinstance(handler, logging.StreamHandler):
210            stream_handler = handler
211            break
212
213    if enable:
214        if stream_handler is None:
215            # Define formatter
216            formatter = logging.Formatter(
217                "stream proteusPy: %(levelname)-7s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
218            )
219            # Create and add a new StreamHandler
220            stream_handler = logging.StreamHandler()
221            stream_handler.setLevel(logger.level)
222            stream_handler.setFormatter(formatter)
223            logger.addHandler(stream_handler)
224    else:
225        if stream_handler is not None:
226            # Remove the existing StreamHandler
227            logger.removeHandler(stream_handler)
228
229
230def list_all_loggers():
231    """
232    Lists all loggers that have been created in the application.
233
234    :return: List of logger names.
235    :rtype: list
236    """
237    logger_dict = logging.Logger.manager.loggerDict
238    loggers = [
239        name
240        for name, logger in logger_dict.items()
241        if isinstance(logger, logging.Logger)
242    ]
243    return loggers
244
245
246def list_handlers(name):
247    """
248    Lists the handlers for the logger with the specified name.
249
250    :param name: The name of the logger.
251    :type name: str
252    :return: List of handler types and their configurations.
253    :rtype: list
254    """
255    logger = logging.getLogger(name)
256    handlers_info = []
257
258    for handler in logger.handlers:
259        handler_type = type(handler).__name__
260        handler_info = {
261            "type": handler_type,
262            "level": logging.getLevelName(handler.level),
263            "formatter": handler.formatter._fmt if handler.formatter else None,
264        }
265        handlers_info.append(handler_info)
266
267    return handlers_info
268
269
270def set_logger_level_for_module(pkg_name, level=""):
271    """
272    Set the logging level for all loggers within a specified package.
273
274    This function iterates through all registered loggers and sets the logging
275    level for those that belong to the specified package.
276
277    :param pkg_name: The name of the package for which to set the logging level.
278    :type pkg_name: str
279    :param level: The logging level to set (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
280                  If not specified, the logging level will not be changed.
281    :type level: str, optional
282    :return: A list of logger names that were found and had their levels set.
283    :rtype: list
284    """
285    logger_dict = logging.Logger.manager.loggerDict
286    registered_loggers = [
287        name
288        for name, logger in logger_dict.items()
289        if isinstance(logger, logging.Logger) and name.startswith(pkg_name)
290    ]
291    for logger_name in registered_loggers:
292        logger = logging.getLogger(logger_name)
293        if level:
294            logger.setLevel(level)
295
296    return registered_loggers
297
298
299if __name__ == "__main__":
300    import doctest
301
302    doctest.testmod()
303
304# end of file
DEFAULT_LOG_LEVEL = 30
def set_logging_level_for_all_handlers(log_level: int):
18def set_logging_level_for_all_handlers(log_level: int):
19    """
20    Sets the logging level for all handlers of all loggers in the proteusPy package.
21
22    :param log_level: The logging level to set.
23    :type log_level: int
24    """
25    # Get the root logger
26    root_logger = logging.getLogger()
27
28    # Set the level for the root logger
29    root_logger.setLevel(log_level)
30
31    # Iterate through all loggers
32    for logger_name in logging.Logger.manager.loggerDict:
33        _logger = logging.getLogger(logger_name)
34
35        # Set the level for the logger itself
36        _logger.setLevel(log_level)
37
38        # Iterate through all handlers of the logger
39        for handler in _logger.handlers:
40            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):
43def disable_stream_handlers_for_namespace(namespace: str):
44    """
45    Disables all stream handlers for all loggers under the specified namespace.
46
47    :param namespace: The namespace whose stream handlers should be disabled.
48    :type namespace: str
49    """
50    # First check the specific logger for the namespace
51    logger = logging.getLogger(namespace)
52    for handler in logger.handlers[
53        :
54    ]:  # Create a copy of the list to safely modify during iteration
55        if isinstance(handler, logging.StreamHandler):
56            logger.removeHandler(handler)
57
58    # Then check all loggers in the manager's dictionary that start with the namespace
59    for logger_name in logging.Logger.manager.loggerDict:
60        if logger_name.startswith(namespace):
61            _logger = logging.getLogger(logger_name)
62            for handler in _logger.handlers[
63                :
64            ]:  # Create a copy of the list to safely modify during iteration
65                if isinstance(handler, logging.StreamHandler):
66                    _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:
 69def configure_master_logger(
 70    log_file: str,
 71    file_path: str = "~/logs",
 72    log_level: int = logging.ERROR,
 73    disabled: bool = False,
 74) -> None:
 75    """
 76    Configures the root logger to write to a specified log file.
 77
 78    :param log_file: Name of the log file.
 79    :type log_file: str
 80    :param file_path: Path to the directory where log files will be stored. Defaults to '~/logs'.
 81    :type file_path: str
 82    :param log_level: The logging level to set. Defaults to logging.ERROR.
 83    :type log_level: int
 84    :param disabled: If True, the logger will be disabled. Defaults to False.
 85    :type disabled: bool
 86    """
 87    # Expand user path
 88    file_path = Path(file_path).expanduser()
 89
 90    # Ensure the directory exists
 91    file_path.mkdir(parents=True, exist_ok=True)
 92
 93    # Full path to the log file
 94    full_log_file_path = file_path / log_file
 95
 96    root_logger = logging.getLogger()
 97
 98    # Set the root logger level to DEBUG to capture all messages
 99    root_logger.setLevel(log_level)
100
101    # Remove all existing handlers
102    for handler in root_logger.handlers[:]:
103        root_logger.removeHandler(handler)
104
105    # Create a new FileHandler
106    handler = logging.FileHandler(full_log_file_path, mode="w")
107
108    formatter = logging.Formatter(
109        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
110    )
111    handler.setFormatter(formatter)
112
113    root_logger.addHandler(handler)
114    for handler in root_logger.handlers:
115        handler.setLevel(log_level)
116
117    if disabled:
118        root_logger.disabled = True
119    else:
120        root_logger.disabled = False  # Enable the root logger
121
122    return

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) -> logging.Logger:
125def create_logger(
126    name: str,
127    log_level: int = logging.INFO,
128) -> logging.Logger:
129    """
130    Returns a logger with the specified name, configured to use both StreamHandler
131    and RotatingFileHandler with the predefined formats.
132
133    :param name: The name of the logger.
134    :type name: str
135    :param log_level: The logging level, defaults to logging.INFO
136    :type log_level: int, optional
137    :return: Configured logger instance.
138    :rtype: logging.Logger
139    """
140    logger = logging.getLogger(name)
141    logger.setLevel(log_level)
142
143    # Clear all existing handlers
144    if logger.hasHandlers():
145        logger.handlers.clear()
146
147    # Define formatter
148    formatter = logging.Formatter(
149        "proteusPy: %(levelname)s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
150    )
151
152    # StreamHandler setup
153    stream_handler = logging.StreamHandler()
154    stream_handler.setLevel(log_level)
155    stream_handler.setFormatter(formatter)
156    logger.addHandler(stream_handler)
157
158    # Allows log messages to propagate to the root logger
159    logger.propagate = True
160
161    return logger

Returns a logger with the specified name, configured to use both StreamHandler and RotatingFileHandler with the predefined formats.

Parameters
  • name: The name of the logger.
  • log_level: The logging level, defaults to logging.INFO
Returns

Configured logger instance.

def set_logger_level(name, level):
164def set_logger_level(name, level):
165    """
166    Sets the logging level for the logger with the specified name.
167
168    :param name: The name of the logger.
169    :type name: str
170    :param level: The logging level to set. Must be one of ["WARNING", "ERROR", "INFO", "DEBUG"].
171    :type level: str
172    :raises ValueError: If the provided level is not one of the allowed values.
173    """
174    level_dict = {
175        "WARNING": logging.WARNING,
176        "ERROR": logging.ERROR,
177        "INFO": logging.INFO,
178        "DEBUG": logging.DEBUG,
179    }
180
181    if level not in level_dict:
182        raise ValueError(
183            (
184                f"--> set_logger_level(): Invalid logging level: {level}."
185                f"Must be one of ['WARNING', 'ERROR', 'INFO', 'DEBUG']"
186            )
187        )
188
189    _logger = logging.getLogger(name)
190    _logger.setLevel(level_dict[level])
191
192    for handler in _logger.handlers:
193        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):
196def toggle_stream_handler(name, enable):
197    """
198    Enables or disables the StreamHandler for the logger with the specified name.
199
200    :param name: The name of the logger.
201    :type name: str
202    :param enable: If True, enables the StreamHandler; if False, disables it.
203    :type enable: bool
204    """
205    logger = logging.getLogger(name)
206    stream_handler = None
207
208    # Find the StreamHandler if it exists
209    for handler in logger.handlers:
210        if isinstance(handler, logging.StreamHandler):
211            stream_handler = handler
212            break
213
214    if enable:
215        if stream_handler is None:
216            # Define formatter
217            formatter = logging.Formatter(
218                "stream proteusPy: %(levelname)-7s %(asctime)s - %(name)s.%(funcName)s - %(message)s"
219            )
220            # Create and add a new StreamHandler
221            stream_handler = logging.StreamHandler()
222            stream_handler.setLevel(logger.level)
223            stream_handler.setFormatter(formatter)
224            logger.addHandler(stream_handler)
225    else:
226        if stream_handler is not None:
227            # Remove the existing StreamHandler
228            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():
231def list_all_loggers():
232    """
233    Lists all loggers that have been created in the application.
234
235    :return: List of logger names.
236    :rtype: list
237    """
238    logger_dict = logging.Logger.manager.loggerDict
239    loggers = [
240        name
241        for name, logger in logger_dict.items()
242        if isinstance(logger, logging.Logger)
243    ]
244    return loggers

Lists all loggers that have been created in the application.

Returns

List of logger names.

def list_handlers(name):
247def list_handlers(name):
248    """
249    Lists the handlers for the logger with the specified name.
250
251    :param name: The name of the logger.
252    :type name: str
253    :return: List of handler types and their configurations.
254    :rtype: list
255    """
256    logger = logging.getLogger(name)
257    handlers_info = []
258
259    for handler in logger.handlers:
260        handler_type = type(handler).__name__
261        handler_info = {
262            "type": handler_type,
263            "level": logging.getLevelName(handler.level),
264            "formatter": handler.formatter._fmt if handler.formatter else None,
265        }
266        handlers_info.append(handler_info)
267
268    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=''):
271def set_logger_level_for_module(pkg_name, level=""):
272    """
273    Set the logging level for all loggers within a specified package.
274
275    This function iterates through all registered loggers and sets the logging
276    level for those that belong to the specified package.
277
278    :param pkg_name: The name of the package for which to set the logging level.
279    :type pkg_name: str
280    :param level: The logging level to set (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
281                  If not specified, the logging level will not be changed.
282    :type level: str, optional
283    :return: A list of logger names that were found and had their levels set.
284    :rtype: list
285    """
286    logger_dict = logging.Logger.manager.loggerDict
287    registered_loggers = [
288        name
289        for name, logger in logger_dict.items()
290        if isinstance(logger, logging.Logger) and name.startswith(pkg_name)
291    ]
292    for logger_name in registered_loggers:
293        logger = logging.getLogger(logger_name)
294        if level:
295            logger.setLevel(level)
296
297    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.