-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #285 from ihmeuw/collijk/feature/add-logging-subsy…
…stem Add logging subsystem
- Loading branch information
Showing
22 changed files
with
443 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.. automodule:: vivarium.framework.logging | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
:glob: | ||
|
||
* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.. automodule:: vivarium.framework.logging.manager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.. automodule:: vivarium.framework.logging.utilities |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.. _logging_concept: | ||
|
||
================= | ||
Framework Logging | ||
================= | ||
|
||
.. todo:: | ||
|
||
Everything here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
""" | ||
======= | ||
Logging | ||
======= | ||
""" | ||
from vivarium.framework.logging.manager import LoggingInterface, LoggingManager | ||
from vivarium.framework.logging.utilities import ( | ||
configure_logging_to_file, | ||
configure_logging_to_terminal, | ||
list_loggers, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
""" | ||
===================== | ||
The Logging Subsystem | ||
===================== | ||
""" | ||
from loguru import logger | ||
from loguru._logger import Logger | ||
|
||
from vivarium.framework.logging.utilities import configure_logging_to_terminal | ||
|
||
|
||
class LoggingManager: | ||
def __init__(self): | ||
self._simulation_name = None | ||
|
||
def configure_logging( | ||
self, | ||
simulation_name: str, | ||
verbosity: int = 0, | ||
long_format: bool = True, | ||
) -> None: | ||
self._simulation_name = simulation_name | ||
if self._terminal_logging_not_configured(): | ||
configure_logging_to_terminal(verbosity=verbosity, long_format=long_format) | ||
|
||
@staticmethod | ||
def _terminal_logging_not_configured() -> bool: | ||
# This hacks into the internals of loguru to see if we've already configured a | ||
# terminal sink. Loguru maintains a global increment of the loggers it has generated | ||
# and has a default logger configured with id 0. All code paths in this library that | ||
# configure logging handlers delete the default handler with id 0, add a terminal | ||
# logging handler (with id 1) and potentially have a file logging handler with id 2. | ||
# This behavior is based on sequencing of the handle definition. This is a bit | ||
# fragile since it depends on a loguru's internals as well as the stability of code | ||
# paths in vivarium, but both are quite stable at this point, so I think it's pretty, | ||
# low risk. | ||
return 1 not in logger._core.handlers | ||
|
||
@property | ||
def name(self): | ||
return "logging_manager" | ||
|
||
def get_logger(self, component_name: str = None) -> Logger: | ||
bind_args = {"simulation": self._simulation_name} | ||
if component_name: | ||
bind_args["component"] = component_name | ||
return logger.bind(**bind_args) | ||
|
||
|
||
class LoggingInterface: | ||
def __init__(self, manager: LoggingManager): | ||
self._manager = manager | ||
|
||
def get_logger(self, component_name: str = None) -> Logger: | ||
return self._manager.get_logger(component_name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
""" | ||
================= | ||
Logging Utilities | ||
================= | ||
This module contains utilities for configuring logging. | ||
""" | ||
import logging | ||
import sys | ||
from pathlib import Path | ||
|
||
from loguru import logger | ||
|
||
|
||
def configure_logging_to_terminal(verbosity: int, long_format: bool = True) -> None: | ||
"""Configure logging to print to the sys.stdout. | ||
Parameters | ||
---------- | ||
verbosity | ||
The verbosity level of the logging. 0 logs at the WARNING level, 1 logs | ||
at the INFO level, and 2 logs at the DEBUG level. | ||
long_format | ||
Whether to use the long format for logging messages, which includes explicit | ||
information about the simulation context and component in the log messages. | ||
""" | ||
_clear_default_configuration() | ||
_add_logging_sink( | ||
sink=sys.stdout, | ||
verbosity=verbosity, | ||
long_format=long_format, | ||
colorize=True, | ||
serialize=False, | ||
) | ||
|
||
|
||
def configure_logging_to_file(output_directory: Path) -> None: | ||
"""Configure logging to write to a file in the provided output directory. | ||
Parameters | ||
---------- | ||
output_directory | ||
The directory to write the log file to. | ||
""" | ||
log_file = output_directory / "simulation.log" | ||
_add_logging_sink( | ||
log_file, | ||
verbosity=2, | ||
long_format=True, | ||
colorize=False, | ||
serialize=False, | ||
) | ||
|
||
|
||
def _clear_default_configuration(): | ||
try: | ||
logger.remove(0) # Clear default configuration | ||
except ValueError: | ||
pass | ||
|
||
|
||
def _add_logging_sink( | ||
sink, | ||
verbosity: int, | ||
long_format: bool, | ||
colorize: bool, | ||
serialize: bool, | ||
) -> int: | ||
"""Add a logging sink to the logger. | ||
Parameters | ||
---------- | ||
sink | ||
The sink to add. Can be a file path, a file object, or a callable. | ||
verbosity | ||
The verbosity level. 0 is the default and will only log warnings and errors. | ||
1 will log info messages. 2 will log debug messages. | ||
long_format | ||
Whether to use the long format for logging messages. The long format includes | ||
the simulation name and component name. The short format only includes the | ||
file name and line number. | ||
colorize | ||
Whether to colorize the log messages. | ||
serialize | ||
Whether to serialize log messages. This is useful when logging to | ||
a file or a database. | ||
""" | ||
log_formatter = _LogFormatter(long_format) | ||
logging_level = _get_log_level(verbosity) | ||
return logger.add( | ||
sink, | ||
colorize=colorize, | ||
level=logging_level, | ||
format=log_formatter.format, | ||
serialize=serialize, | ||
) | ||
|
||
|
||
class _LogFormatter: | ||
time = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>" | ||
level = "<level>{level: <8}</level>" | ||
simulation = "<cyan>{extra[simulation]}</cyan> - <cyan>{name}</cyan>:<cyan>{line}</cyan>" | ||
simulation_and_component = ( | ||
"<cyan>{extra[simulation]}</cyan>-<cyan>{extra[component]}</cyan>:<cyan>{line}</cyan>" | ||
) | ||
short_name_and_line = "<cyan>{name}</cyan>:<cyan>{line}</cyan>" | ||
message = "<level>{message}</level>" | ||
|
||
def __init__(self, long_format: bool = False): | ||
self.long_format = long_format | ||
|
||
def format(self, record): | ||
fmt = self.time + " | " | ||
|
||
if self.long_format: | ||
fmt += self.level + " | " | ||
|
||
if self.long_format and "simulation" in record["extra"]: | ||
if "component" in record["extra"]: | ||
fmt += self.simulation_and_component + " - " | ||
else: | ||
fmt += self.simulation + " - " | ||
else: | ||
fmt += self.short_name_and_line + " - " | ||
|
||
fmt += self.message + "\n{exception}" | ||
return fmt | ||
|
||
|
||
def _get_log_level(verbosity: int): | ||
if verbosity == 0: | ||
return "WARNING" | ||
elif verbosity == 1: | ||
return "INFO" | ||
elif verbosity >= 2: | ||
return "DEBUG" | ||
|
||
|
||
def list_loggers(): | ||
"""Utility function for analyzing the logging environment.""" | ||
root_logger = logging.getLogger() | ||
print("Root logger: ", root_logger) | ||
for h in root_logger.handlers: | ||
print(f" %s" % h) | ||
|
||
print("Other loggers") | ||
print("=============") | ||
for name, logger_ in logging.Logger.manager.loggerDict.items(): | ||
print("+ [%-20s] %s " % (name, logger_)) | ||
if not isinstance(logger_, logging.PlaceHolder): | ||
handlers = list(logger_.handlers) | ||
if not handlers: | ||
print(" No handlers") | ||
for h in logger_.handlers: | ||
print(" %s" % h) |
Oops, something went wrong.