Skip to content

Commit

Permalink
Add easy import log logger
Browse files Browse the repository at this point in the history
  • Loading branch information
mfrata committed Mar 23, 2021
1 parent d1e7c00 commit 010f52d
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 4 deletions.
73 changes: 69 additions & 4 deletions lodge.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def _get_log_level_from_env_var(logger_name: str) -> str:
return os.environ.get(env_var_key, DEFAULT_LOG_LEVEL)


def _get_format():
def _get_default_format():
DEFAULT_LOG_ENV = os.environ.get("LOG_ENV", "PROD")
LOG_BASE_FIELDS = eval(
os.environ.get(
Expand All @@ -38,12 +38,24 @@ def _add_base_configs(logger: logging.Logger):
logging.addLevelName(logging.WARNING, "WARN")


def _add_handlers(logger: logging.Logger):
def _add_default_handler(logger: logging.Logger):
stderr = logging.StreamHandler(DEFAULT_LOG_STREAM)
stderr.setFormatter(_get_format())
stderr.setFormatter(_get_default_format())
logger.addHandler(stderr)


# https://github.com/python/cpython/blob/e8e341993e3f80a3c456fb8e0219530c93c13151/Lib/logging/__init__.py#L162
if hasattr(sys, '_getframe'):
currentframe = lambda level: sys._getframe(level) # noqa
else: # pragma: no cover
def currentframe(level: int):
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back # type: ignore


def get_logger(name: str) -> logging.Logger:
"""
Return a logger with configs based on the environment.
Expand All @@ -54,7 +66,60 @@ def get_logger(name: str) -> logging.Logger:
logger = logging.getLogger(name)

_add_base_configs(logger)
_add_handlers(logger)
_add_default_handler(logger)

logger.setLevel(_get_log_level_from_env_var(name))
return logger


class _ProxyLogger:
"""
Internal proxy Logger Class. It should not be used directly. Use `log`
or `logger`!
From the proxy methods it will select the proper log to call. If it
not exists it will create a new one based on the callers `__name__`.
The created logger will have the config loaded from the env vars.
"""
def __init__(self):
self.manager = logging.root.manager

def _get_or_create_logger(self, name: str) -> logging.Logger:
logger_already_initialized = name in self.manager.loggerDict.keys()
if logger_already_initialized:
return logging.getLogger(name)
else:
return get_logger(name)

def _get_caller_module(self) -> str:
# Calling currentframe from here we need to go 4 levels deep to reach the module
# By changing currentframe call location this number will probably change.
STACK_LEVEL = 4
frame = currentframe(STACK_LEVEL)
return frame.f_globals["__name__"]

def _get_logger(self) -> logging.Logger:
caller_name = self._get_caller_module()
logger = self._get_or_create_logger(caller_name)
return logger

# Proxy methods
def debug(self, msg, *args, **kwargs):
self._get_logger().debug(msg, *args, **kwargs)

def info(self, msg, *args, **kwargs):
self._get_logger().info(msg, *args, **kwargs)

def warn(self, msg, *args, **kwargs):
self._get_logger().warn(msg, *args, **kwargs)

def error(self, msg, *args, **kwargs):
self._get_logger().error(msg, *args, **kwargs)

def fatal(self, msg, *args, **kwargs):
self._get_logger().fatal(msg, *args, **kwargs)


logger = _ProxyLogger()
# If you prefer `log` instead of `logger`
log = logger
29 changes: 29 additions & 0 deletions test_lodge.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,32 @@ def test_set_base_fields_on_logging(stream, monkeypatch):

assert log_structured["message"] == "Logging other base fields"
assert log_structured["anotherField"] == "yes"


def test_import_proxy_log(stream, monkeypatch):
monkeypatch.setenv("LOG_ENV", "DEV")

class StubFrame:
f_globals = {"__name__": "package.module.submodule"}

with import_lodge() as lodge:
monkeypatch.setattr(lodge, "currentframe", lambda x: StubFrame())
lodge.log.info("logging with lodge.log")

log_entry = stream.read()
log_entry_splitted = log_entry.split('|')

assert log_entry_splitted[-1] == " logging with lodge.log\n"
assert log_entry_splitted[1] == " INFO "
assert log_entry_splitted[2] == " package.module.submodule "


def test_proxy_log_warn_with_level_error_should_not_log(stream, monkeypatch):
monkeypatch.setenv("LOG_LEVEL", "ERROR")

from lodge import log
log.warn("warn")

log_entry = stream.read()

assert log_entry == ""

0 comments on commit 010f52d

Please sign in to comment.