diff --git a/README.md b/README.md index 88e0419..1f9744a 100644 --- a/README.md +++ b/README.md @@ -84,14 +84,14 @@ Masks are used by the `MultiLogger` to filter loggers which are not supposed to ### Level filtering -Any logger is initialized with a `default_level` argument, which is set to `LogLevel.INFO` by default. `LogLevel` elements have an `importance` attribute, which defines a hierarchy of levels. When a logger is initialized with a given level, it will only log messages with a level of equal or higher importance. For example, if a logger is initialized with `LogLevel.WARN`, it will log messages with levels `WARN` and `ERROR`, but not `INFO` or `DEBUG`. +Any logger is initialized with a `default_priority` argument, which is set to `LogLevel.INFO` by default. `LogLevel` elements have an `importance` attribute, which defines a hierarchy of levels. When a logger is initialized with a given level, it will only log messages with a level of equal or higher importance. For example, if a logger is initialized with `LogLevel.WARN`, it will log messages with levels `WARN` and `ERROR`, but not `INFO` or `DEBUG`. The importance values for the built-in levels are: - `DEBUG`: -1 - `INFO`: 0 - `WARN`: 1 -- `ERROR`: `np.inf` (as errors should always be logged) +- `ERROR`: `sys.maxsize` (a very large number, as errors should always be logged) ### Progress bars diff --git a/mloggers/_log_levels.py b/mloggers/_log_levels.py index 618a882..d9b8701 100644 --- a/mloggers/_log_levels.py +++ b/mloggers/_log_levels.py @@ -1,7 +1,7 @@ -from typing import Any +import sys +from dataclasses import dataclass from aenum import Enum, extend_enum -from numpy import inf class LogLevel(Enum): # type:ignore[reportGeneralTypeIssues] @@ -12,13 +12,29 @@ class LogLevel(Enum): # type:ignore[reportGeneralTypeIssues] To register a new level use `mloggers.register_level`. """ - WARN = {"color": "yellow", "level": 1} - ERROR = {"color": "red", "level": inf} - DEBUG = {"color": "magenta", "level": -1} - INFO = {"color": "cyan", "level": 0} + @dataclass + class Properties: + color: str + """The color to use when printing the log.""" + priority: int + """The importance level of the log.""" -def register_level(level: str, level_info: dict[str, Any]): + ERROR = "error" + WARN = "warn" + INFO = "info" + DEBUG = "debug" + + +_log_level_properties: dict[str, LogLevel.Properties] = { + LogLevel.ERROR: LogLevel.Properties(color="red", priority=sys.maxsize), + LogLevel.WARN: LogLevel.Properties(color="yellow", priority=1), + LogLevel.INFO: LogLevel.Properties(color="cyan", priority=0), + LogLevel.DEBUG: LogLevel.Properties(color="magenta", priority=-1), +} + + +def register_level(name: str, properties: LogLevel.Properties): """ Register a customized logger level, which will then be available as a member of `LogLevel`, where its `name` is the argument `level` and its `value` is the argument `level_info`. @@ -27,10 +43,8 @@ def register_level(level: str, level_info: dict[str, Any]): ### Parameters ---------- `level`: the level name to register. - `level_info`: a dictionary with the following - - `color`: the color to use when printing the log. (It must be a valid color name from the `termcolor` package.) - - `level`: the importance level of the log. (It must be a number or `np.inf`.) + `properties`: the properties of the level. """ - level = level.upper() - extend_enum(LogLevel, level, level_info) + extend_enum(LogLevel, name.upper(), name) + _log_level_properties[name] = properties diff --git a/mloggers/console_logger.py b/mloggers/console_logger.py index d7b8818..f6b6839 100644 --- a/mloggers/console_logger.py +++ b/mloggers/console_logger.py @@ -4,15 +4,15 @@ from termcolor import colored -from mloggers._log_levels import LogLevel +from mloggers._log_levels import LogLevel, _log_level_properties from mloggers.logger import Logger class ConsoleLogger(Logger): """Logs to the console (i.e., standard I/O).""" - def __init__(self, default_level: LogLevel = LogLevel.INFO): # type:ignore[reportArgumentType] - super().__init__(default_level) + def __init__(self, default_priority: LogLevel = LogLevel.INFO): # type:ignore[reportArgumentType] + super().__init__(default_priority) def log( self, @@ -37,7 +37,7 @@ def log( # Color the level string if isinstance(level, LogLevel): - level_clr = colored(level_str, level.value["color"]) + level_clr = colored(level_str, _log_level_properties[level].color) # type:ignore[reportArgumentType] else: level_clr = colored(level_str, "green") @@ -86,4 +86,3 @@ def log( elif hasattr(messages, "__str__") and callable(getattr(messages, "__str__")): print(f"{level_clr}{time} {str(messages)}") - diff --git a/mloggers/file_logger.py b/mloggers/file_logger.py index dd8fa25..2ea770a 100644 --- a/mloggers/file_logger.py +++ b/mloggers/file_logger.py @@ -13,7 +13,7 @@ class FileLogger(Logger): """Logs to a file.""" - def __init__(self, file_path: str, default_level: LogLevel | int = LogLevel.INFO): # type:ignore[reportArgumentType] + def __init__(self, file_path: str, default_priority: LogLevel | int = LogLevel.INFO): # type:ignore[reportArgumentType] """ Initializes a file logger. @@ -21,11 +21,10 @@ def __init__(self, file_path: str, default_level: LogLevel | int = LogLevel.INFO ---------- `file_path`: the path to the file to log to. - The file will be created if it does not exist. If it does, the logs will be appended to it. - - `default_level`: the default log level to use. + `default_priority`: The default log level priority to use. """ - super().__init__(default_level) + super().__init__(default_priority) # Create the file if it does not exist if not os.path.exists(file_path): diff --git a/mloggers/logger.py b/mloggers/logger.py index f6ff48d..f69be4a 100644 --- a/mloggers/logger.py +++ b/mloggers/logger.py @@ -1,32 +1,32 @@ from abc import ABC, abstractmethod from typing import Any, Callable -from mloggers._log_levels import LogLevel +from mloggers._log_levels import LogLevel, _log_level_properties # This constant is used to assign an importance level to anything not using the LogLevel enum. -# It was chosen to be the same as LogLevel.INFO, but it can be changed to any other value. -DEFAULT_IMPORTANCE = LogLevel.INFO.value["level"] # type:ignore[reportAttributeAccessIssue] +# It was chosen to be the same as `LogLevel.INFO`, but it can be changed to any other value. +DEFAULT_IMPORTANCE = _log_level_properties[LogLevel.INFO].priority # type:ignore[reportAttributeAccessIssue] class Logger(ABC): """The abstract class for a logger.""" - def __init__(self, default_level: LogLevel | int = LogLevel.INFO): # type:ignore[reportArgumentType] + def __init__(self, default_priority: LogLevel | int = LogLevel.INFO): # type:ignore[reportArgumentType] """ Initialize the logger. ### Parameters ---------- - `log_level`: the default log level to use. + `log_level`: The default log level priority to use. - This parameter filters out messages with a lower importance level than the one provided. It can be either a `LogLevel` object or an integer. - When calling the logger with a level not from the `LogLevel` enum, the importance level will be set to 0 (same as `LogLevel.INFO`). - For example, if the log level is set to `LogLevel.INFO`, only messages with a level of `LogLevel.INFO` or higher will be printed (which excludes `LogLevel.DEBUG`). """ - self._log_level = ( - default_level.value["level"] - if isinstance(default_level, LogLevel) - else default_level + self._min_priority = ( + _log_level_properties[default_priority].priority + if isinstance(default_priority, LogLevel) + else default_priority ) @abstractmethod @@ -77,25 +77,31 @@ def log( "Expected all messages to be either strings or dictionaries, but got a mix of both." ) - # Filter out messages with a lower importance level than the current log level. - if isinstance(level, LogLevel) and level.value["level"] < self._log_level: + # Filter out messages with a lower importance level than the current priority. + if ( + isinstance(level, LogLevel) + and _log_level_properties[level].priority < self._min_priority + ): return False - elif isinstance(level, str) and DEFAULT_IMPORTANCE < self._log_level: + elif isinstance(level, str) and DEFAULT_IMPORTANCE < self._min_priority: return False return True - def set_level(self, level: LogLevel | int): + def set_min_priority(self, value: LogLevel | int): """ - Set the log level. + Set the minimum log level priority. ### Parameters ---------- - `level`: the log level to set. - - If a string, it must be a valid log level (e.g., INFO, WARN, ERROR, DEBUG, etc.). - - If a `LogLevel` object, it will be used as-is. + `value`: the log level priority to set. + - If a `LogLevel`, the priority of that log level will be considered. + - If an `int`, such number will be considered. """ - self._log_level = level.value["level"] if isinstance(level, LogLevel) else level + if isinstance(value, int): + self._min_priority = value + else: + self._min_priority = _log_level_properties[value].priority def _call_impl(self, *args, **kwargs): return self.log(*args, **kwargs) diff --git a/mloggers/multi_logger.py b/mloggers/multi_logger.py index 6b7bd0c..28117d2 100644 --- a/mloggers/multi_logger.py +++ b/mloggers/multi_logger.py @@ -11,7 +11,7 @@ def __init__( self, loggers: list[Logger], default_mask: list[type[Logger]] = [], - default_level: LogLevel | int = LogLevel.INFO, # type:ignore[reportArgumentType] + default_priority: LogLevel | int = LogLevel.INFO, # type:ignore[reportArgumentType] ): """ Initializes a multi-logger. @@ -20,30 +20,22 @@ def __init__( ---------- `loggers`: a list of the initialized loggers to use. `default_mask`: the default mask to use when logging. - `default_level`: the default log level to use. + `default_priority`: The default log level priority to use. """ - super().__init__(default_level) + super().__init__(default_priority) self._loggers = loggers self._default_mask = default_mask for logger in self._loggers: - logger.set_level(self._log_level) + logger.set_min_priority(self._min_priority) - def set_level(self, level: LogLevel | int): - """ - Sets the log level of the multi-logger. - - ### Parameters - ---------- - `level`: the level to set. - """ - - super(MultiLogger, self).set_level(level) + def set_min_priority(self, level: LogLevel | int): + super(MultiLogger, self).set_min_priority(level) for logger in self._loggers: - logger.set_level(self._log_level) + logger.set_min_priority(level) def log( self, @@ -94,7 +86,7 @@ def info( **kwargs: Any, ): """ - Wrapper for calling `log` with level=LogLevel.INFO. + Wrapper for calling `log` with `level=LogLevel.INFO`. ### Parameters ---------- @@ -117,7 +109,7 @@ def warn( **kwargs: Any, ): """ - Wrapper for calling `log` with level=LogLevel.WARN. + Wrapper for calling `log` with `level=LogLevel.WARN`. ### Parameters ---------- @@ -143,7 +135,7 @@ def error( **kwargs: Any, ): """ - Wrapper for calling `log` with level=LogLevel.ERROR. + Wrapper for calling `log` with `level=LogLevel.ERROR`. ### Parameters ---------- @@ -166,7 +158,7 @@ def debug( **kwargs: Any, ): """ - Wrapper for calling `log` with level=LogLevel.DEBUG. + Wrapper for calling `log` with `level=LogLevel.DEBUG`. ### Parameters ---------- diff --git a/mloggers/wandb_logger.py b/mloggers/wandb_logger.py index ce0b655..c660a5f 100644 --- a/mloggers/wandb_logger.py +++ b/mloggers/wandb_logger.py @@ -16,7 +16,7 @@ def __init__( project: str, group: str, experiment: str, - default_level: LogLevel | int = LogLevel.INFO, # type:ignore[reportArgumentType] + default_priority: LogLevel | int = LogLevel.INFO, # type:ignore[reportArgumentType] config: DictConfig | None = None, ): """ @@ -27,11 +27,11 @@ def __init__( `project`: the name of the project to log to. `group`: the name of the group to log to. `experiment`: the name of the experiment to log to. - `default_level`: the default log level to use. + `default_priority`: the default log level priority to use. [optional] `config`: the configuration of the experiment. """ - super().__init__(default_level) + super().__init__(default_priority) if config is not None: config = vars(config)