Module core.utils.logger
Utilities for generating logs of framework executions
Note that logging should work in a duality within the framework. Meaning, that all functions within the framework should use the same logging utilities, but how they are provided will differ. All files will have a module level definition of a default logger, this will allow the utilities to log when called as simple functions through normal python execution. Within the framework, all executions will be wrapped in a global namespace that has a logger, therefor when the functions are called as part of the framework, it will be this logger that is used.
This is important in mainting that utility functions for the framework are not bound to used within the framework, but it also allows there to be dynamic functionality of how logs will be proccessed and displayed to the user.
Expand source code
"""Utilities for generating logs of framework executions
Note that logging should work in a duality within the framework. Meaning, that all functions within the framework
should use the same logging utilities, but how they are provided will differ. All files will have a module
level definition of a default logger, this will allow the utilities to log when called as simple functions
through normal python execution. Within the framework, all executions will be wrapped in a global namespace
that has a logger, therefor when the functions are called as part of the framework, it will be this logger
that is used.
This is important in mainting that utility functions for the framework are not bound to used within the framework,
but it also allows there to be dynamic functionality of how logs will be proccessed and displayed to the user.
"""
import logging.config
import logging
import os
from typing import Dict, Union
def _create_logging_settings(log_level: str) -> Dict:
return {
"version": 1,
"filename": ".cdev/logs/userlogs",
"formatters": {
"jsonFormatter": {
"class": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(name)s %(levelname)s %(message)s",
},
"simpleFormatter": {
"format": "%(asctime)s %(name)s - %(levelname)s: %(message)s"
},
"richFormatter": {"format": "%(name)s - %(levelname)s: %(message)s"},
},
"handlers": {
"fileHandler": {
"class": "logging.FileHandler",
"level": log_level,
"formatter": "jsonFormatter",
"filename": ".cdev/logs/userlogs",
},
"simpleHandler": {
"class": "logging.StreamHandler",
"level": log_level,
"formatter": "simpleFormatter",
},
"richHandler": {
"class": "rich.logging.RichHandler",
"level": log_level,
"formatter": "richFormatter",
"markup": True,
"show_path": False,
"show_time": False,
"rich_tracebacks": True,
},
},
"loggers": {
"core_file": {
"level": log_level,
"handlers": ["fileHandler"],
"propagate": False,
},
"core_rich": {
"level": log_level,
"handlers": ["richHandler"],
"propagate": False,
},
"core_simple": {
"level": log_level,
"handlers": ["simpleHandler"],
"propagate": False,
},
},
"root": {"level": "ERROR", "handlers": ["simpleHandler"]},
"disable_existing_loggers": False,
}
class cdev_logger:
"""Wrapper around pythons basic logger object to provide a layer of flexibility for logging.
Specifically, this will add the ability to always process logs into a JSON file for debugging errors, while
also allowing the user to provide a flag to set a logging level to display to the user.
It will implement the functions of a Logger obj
https://docs.python.org/3/library/logging.html#logging.Logger.propagate
Note that when formating data into a log message, it must use '%' formatted strings instead of f
strings. This is allows am optimization used by the logging library to not allocate strings unless the
log needs to be evaluated. Within the context of a framework, these allocations can add up. This should
be enforced with code styling at the PR level.
Also note the optimization around formmatted logs.
For more info read https://docs.python.org/3/howto/logging.html#optimization
"""
def __init__(
self,
is_rich_formatted=True,
show_logs: bool = False,
logging_level: Union[str, int] = "ERROR",
) -> None:
self.is_rich_formatted = is_rich_formatted
self.show_logs = show_logs
log_info = _create_logging_settings(logging_level)
fp = os.path.join(os.getcwd(), log_info.get("filename"))
if not os.path.isfile(fp):
if not os.path.isdir(os.path.dirname(os.path.dirname(fp))):
os.mkdir(os.path.dirname(os.path.dirname(fp)))
if not os.path.isdir(os.path.dirname(fp)):
os.mkdir(os.path.dirname(fp))
with open(fp, "a"):
os.utime(fp, None)
logging.config.dictConfig(log_info)
self._json_logger = logging.getLogger("core_file")
self._simple_logger = logging.getLogger("core_simple")
self._rich_logger = logging.getLogger("core_rich")
def _write_log(
self,
*args,
func_name: str,
original_msg: str,
formatted_msg: str = "",
**kw_args,
) -> None:
# Always write a log to the json file
getattr(self._json_logger, func_name)(original_msg, *args, **kw_args)
if self.show_logs:
# We need to write logs to console
# Either write a plain log or rich formatted log to the console
if not self.is_rich_formatted:
getattr(self._simple_logger, func_name)(original_msg, *args, **kw_args)
else:
getattr(self._rich_logger, func_name)(formatted_msg, *args, **kw_args)
def debug(self, msg, *args, **kw_args) -> None:
if self.is_rich_formatted:
self._write_log(
*args,
func_name="debug",
original_msg=msg,
formatted_msg=f"[bold blue]{msg}",
**kw_args,
)
else:
self._write_log(*args, func_name="debug", original_msg=msg, **kw_args)
def info(self, msg, *args, **kw_args):
if self.is_rich_formatted:
self._write_log(
*args,
func_name="info",
original_msg=msg,
formatted_msg=f"[bold blue]{msg}",
**kw_args,
)
else:
self._write_log(*args, func_name="info", original_msg=msg, **kw_args)
def warning(self, msg, *args, **kw_args) -> None:
if self.is_rich_formatted:
self._write_log(
*args,
func_name="warning",
original_msg=msg,
formatted_msg=f"[bold yellow blink]{msg}",
**kw_args,
)
else:
self._write_log(*args, func_name="warning", original_msg=msg, **kw_args)
def error(self, msg, *args, **kw_args) -> None:
if self.is_rich_formatted:
self._write_log(
*args,
func_name="error",
original_msg=msg,
formatted_msg=f"[bold red blink]:cross_mark: :cross_mark: :cross_mark: {msg} :cross_mark: :cross_mark: :cross_mark:",
**kw_args,
)
else:
self._write_log(*args, func_name="error", original_msg=msg, **kw_args)
def exception(self, msg) -> None:
self._write_log("exception", msg, msg)
class global_log_container:
def __init__(self, logger: cdev_logger) -> None:
self._logger = logger
@property
def is_rich_formatted(self) -> bool:
return self._logger.is_rich_formatted
@property
def show_logs(self) -> bool:
return self._logger.show_logs
def debug(self, msg, *args, **kw_args) -> None:
self._logger.debug(msg, *args, **kw_args)
def info(self, msg, *args, **kw_args) -> None:
self._logger.info(msg, *args, **kw_args)
def warning(self, msg, *args, **kw_args) -> None:
self._logger.warning(msg, *args, **kw_args)
def error(self, msg, *args, **kw_args) -> None:
self._logger.error(msg, *args, **kw_args)
def exception(self, msg) -> None:
self._logger.exception(msg)
log = global_log_container(cdev_logger())
def set_global_logger(new_logger: cdev_logger) -> None:
global log
log._logger = new_logger
Functions
def set_global_logger(new_logger: cdev_logger) ‑> None
-
Expand source code
def set_global_logger(new_logger: cdev_logger) -> None: global log log._logger = new_logger
Classes
class cdev_logger (is_rich_formatted=True, show_logs: bool = False, logging_level: Union[str, int] = 'ERROR')
-
Wrapper around pythons basic logger object to provide a layer of flexibility for logging.
Specifically, this will add the ability to always process logs into a JSON file for debugging errors, while also allowing the user to provide a flag to set a logging level to display to the user.
It will implement the functions of a Logger obj https://docs.python.org/3/library/logging.html#logging.Logger.propagate
Note that when formating data into a log message, it must use '%' formatted strings instead of f strings. This is allows am optimization used by the logging library to not allocate strings unless the log needs to be evaluated. Within the context of a framework, these allocations can add up. This should be enforced with code styling at the PR level.
Also note the optimization around formmatted logs.
For more info read https://docs.python.org/3/howto/logging.html#optimization
Expand source code
class cdev_logger: """Wrapper around pythons basic logger object to provide a layer of flexibility for logging. Specifically, this will add the ability to always process logs into a JSON file for debugging errors, while also allowing the user to provide a flag to set a logging level to display to the user. It will implement the functions of a Logger obj https://docs.python.org/3/library/logging.html#logging.Logger.propagate Note that when formating data into a log message, it must use '%' formatted strings instead of f strings. This is allows am optimization used by the logging library to not allocate strings unless the log needs to be evaluated. Within the context of a framework, these allocations can add up. This should be enforced with code styling at the PR level. Also note the optimization around formmatted logs. For more info read https://docs.python.org/3/howto/logging.html#optimization """ def __init__( self, is_rich_formatted=True, show_logs: bool = False, logging_level: Union[str, int] = "ERROR", ) -> None: self.is_rich_formatted = is_rich_formatted self.show_logs = show_logs log_info = _create_logging_settings(logging_level) fp = os.path.join(os.getcwd(), log_info.get("filename")) if not os.path.isfile(fp): if not os.path.isdir(os.path.dirname(os.path.dirname(fp))): os.mkdir(os.path.dirname(os.path.dirname(fp))) if not os.path.isdir(os.path.dirname(fp)): os.mkdir(os.path.dirname(fp)) with open(fp, "a"): os.utime(fp, None) logging.config.dictConfig(log_info) self._json_logger = logging.getLogger("core_file") self._simple_logger = logging.getLogger("core_simple") self._rich_logger = logging.getLogger("core_rich") def _write_log( self, *args, func_name: str, original_msg: str, formatted_msg: str = "", **kw_args, ) -> None: # Always write a log to the json file getattr(self._json_logger, func_name)(original_msg, *args, **kw_args) if self.show_logs: # We need to write logs to console # Either write a plain log or rich formatted log to the console if not self.is_rich_formatted: getattr(self._simple_logger, func_name)(original_msg, *args, **kw_args) else: getattr(self._rich_logger, func_name)(formatted_msg, *args, **kw_args) def debug(self, msg, *args, **kw_args) -> None: if self.is_rich_formatted: self._write_log( *args, func_name="debug", original_msg=msg, formatted_msg=f"[bold blue]{msg}", **kw_args, ) else: self._write_log(*args, func_name="debug", original_msg=msg, **kw_args) def info(self, msg, *args, **kw_args): if self.is_rich_formatted: self._write_log( *args, func_name="info", original_msg=msg, formatted_msg=f"[bold blue]{msg}", **kw_args, ) else: self._write_log(*args, func_name="info", original_msg=msg, **kw_args) def warning(self, msg, *args, **kw_args) -> None: if self.is_rich_formatted: self._write_log( *args, func_name="warning", original_msg=msg, formatted_msg=f"[bold yellow blink]{msg}", **kw_args, ) else: self._write_log(*args, func_name="warning", original_msg=msg, **kw_args) def error(self, msg, *args, **kw_args) -> None: if self.is_rich_formatted: self._write_log( *args, func_name="error", original_msg=msg, formatted_msg=f"[bold red blink]:cross_mark: :cross_mark: :cross_mark: {msg} :cross_mark: :cross_mark: :cross_mark:", **kw_args, ) else: self._write_log(*args, func_name="error", original_msg=msg, **kw_args) def exception(self, msg) -> None: self._write_log("exception", msg, msg)
Methods
def debug(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def debug(self, msg, *args, **kw_args) -> None: if self.is_rich_formatted: self._write_log( *args, func_name="debug", original_msg=msg, formatted_msg=f"[bold blue]{msg}", **kw_args, ) else: self._write_log(*args, func_name="debug", original_msg=msg, **kw_args)
def error(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def error(self, msg, *args, **kw_args) -> None: if self.is_rich_formatted: self._write_log( *args, func_name="error", original_msg=msg, formatted_msg=f"[bold red blink]:cross_mark: :cross_mark: :cross_mark: {msg} :cross_mark: :cross_mark: :cross_mark:", **kw_args, ) else: self._write_log(*args, func_name="error", original_msg=msg, **kw_args)
def exception(self, msg) ‑> None
-
Expand source code
def exception(self, msg) -> None: self._write_log("exception", msg, msg)
def info(self, msg, *args, **kw_args)
-
Expand source code
def info(self, msg, *args, **kw_args): if self.is_rich_formatted: self._write_log( *args, func_name="info", original_msg=msg, formatted_msg=f"[bold blue]{msg}", **kw_args, ) else: self._write_log(*args, func_name="info", original_msg=msg, **kw_args)
def warning(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def warning(self, msg, *args, **kw_args) -> None: if self.is_rich_formatted: self._write_log( *args, func_name="warning", original_msg=msg, formatted_msg=f"[bold yellow blink]{msg}", **kw_args, ) else: self._write_log(*args, func_name="warning", original_msg=msg, **kw_args)
class global_log_container (logger: cdev_logger)
-
Expand source code
class global_log_container: def __init__(self, logger: cdev_logger) -> None: self._logger = logger @property def is_rich_formatted(self) -> bool: return self._logger.is_rich_formatted @property def show_logs(self) -> bool: return self._logger.show_logs def debug(self, msg, *args, **kw_args) -> None: self._logger.debug(msg, *args, **kw_args) def info(self, msg, *args, **kw_args) -> None: self._logger.info(msg, *args, **kw_args) def warning(self, msg, *args, **kw_args) -> None: self._logger.warning(msg, *args, **kw_args) def error(self, msg, *args, **kw_args) -> None: self._logger.error(msg, *args, **kw_args) def exception(self, msg) -> None: self._logger.exception(msg)
Instance variables
var is_rich_formatted : bool
-
Expand source code
@property def is_rich_formatted(self) -> bool: return self._logger.is_rich_formatted
var show_logs : bool
-
Expand source code
@property def show_logs(self) -> bool: return self._logger.show_logs
Methods
def debug(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def debug(self, msg, *args, **kw_args) -> None: self._logger.debug(msg, *args, **kw_args)
def error(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def error(self, msg, *args, **kw_args) -> None: self._logger.error(msg, *args, **kw_args)
def exception(self, msg) ‑> None
-
Expand source code
def exception(self, msg) -> None: self._logger.exception(msg)
def info(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def info(self, msg, *args, **kw_args) -> None: self._logger.info(msg, *args, **kw_args)
def warning(self, msg, *args, **kw_args) ‑> None
-
Expand source code
def warning(self, msg, *args, **kw_args) -> None: self._logger.warning(msg, *args, **kw_args)