Source code for janus_core.helpers.log
"""Configure logger with yaml-styled format."""
from __future__ import annotations
import json
import logging
from typing import Literal
from codecarbon import OfflineEmissionsTracker
from codecarbon.output import LoggerOutput
from janus_core.helpers.janus_types import LogLevel
[docs]
class YamlFormatter(logging.Formatter): # numpydoc ignore=PR02
"""
Custom formatter to convert multiline messages into yaml list.
Parameters
----------
fmt
A format string in the given style for the logged output as a whole. Default is
defined by `FORMAT`.
datefmt
A format string in the given style for the date/time portion of the logged
output. Default is taken from logging.Formatter.formatTime().
style
Determines how the format string will be merged with its data. Can be one of
'%', '{' or '$'. Default is '%'.
validate
If True, incorrect or mismatched fmt and style will raise a ValueError. Default
is True.
defaults
A dictionary with default values to use in custom fields. Default is None.
*args
Arguments to pass to logging.Formatter.__init__.
**kwargs
Keyword arguments to pass to logging.Formatter.__init__.
"""
FORMAT = """
- timestamp: %(asctime)s
level: %(levelname)s
message: %(message)s
trace: %(module)s
line: %(lineno)d
"""
[docs]
def __init__(self, *args, **kwargs) -> None:
"""
Set default string format to yaml style.
Parameters
----------
*args
Arguments to pass to logging.Formatter.__init__.
**kwargs
Keyword arguments to pass to logging.Formatter.__init__.
"""
kwargs.setdefault("fmt", self.FORMAT)
super().__init__(*args, **kwargs)
[docs]
def format(self, record: logging.LogRecord) -> str:
"""
Format log message to convert new lines into a yaml list.
Parameters
----------
record
Log record to be formatted.
Returns
-------
str
Formatted log message.
"""
# Parse JSON dump from codecarbon
record.msg = str(record.msg)
if len(record.msg) > 1 and (record.msg[0] == "{" and record.msg[-1] == "}"):
msg_dict = json.loads(record.msg)
record.msg = ""
for key, value in msg_dict.items():
record.msg += f"\n {key}: {value}"
else:
# Replace "" with '' to prevent invalid wrapping
record.msg = record.msg.replace('"', "'")
# Convert new lines into yaml list
record.msg = "\n" + "\n".join(
f' - "{line.strip()}"'
for line in record.msg.splitlines()
if line.strip()
)
return super().format(record)
[docs]
def config_logger(
name: str,
filename: str | None = None,
level: LogLevel = logging.INFO,
capture_warnings: bool = True,
filemode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"] = "w",
force: bool = True,
) -> logging.Logger | None:
"""
Configure logger with yaml-styled format.
Parameters
----------
name
Name of logger. Default is None.
filename
Name of log file if writing logs. Default is None.
level
Threshold for logger. Default is logging.INFO.
capture_warnings
Whether to capture warnings in log. Default is True.
filemode
Mode of file to open. Default is "w".
force
If true, remove and close existing handlers attached to the root logger before
configuration. Default is True.
Returns
-------
logging.Logger | None
Configured logger if `filename` has been specified.
"""
if filename:
logger = logging.getLogger(name)
handler = logging.FileHandler(
filename,
filemode,
encoding="utf-8",
)
handler.setLevel(level)
formatter = YamlFormatter()
handler.setFormatter(formatter)
logging.basicConfig(
level=level,
force=force,
handlers=[handler],
)
logging.captureWarnings(capture=capture_warnings)
else:
logger = None
return logger
[docs]
def config_tracker(
janus_logger: logging.Logger | None,
track_carbon: bool = True,
*,
country_iso_code: str = "GBR",
save_to_file: bool = False,
log_level: Literal["debug", "info", "warning", "error", "critical"] = "critical",
**kwargs,
) -> OfflineEmissionsTracker | None:
"""
Configure codecarbon tracker to log outputs.
Parameters
----------
janus_logger
Logger for codecarbon output.
track_carbon
Whether to track carbon emissions for calculation. Default is True.
country_iso_code
3 letter ISO Code of the country where the code is running. Default is "GBR".
save_to_file
Whether to also output results to a csv file. Default is False.
log_level
Log level of internal carbon tracker log. Default is "critical".
**kwargs
Additional keyword arguments to pass to OfflineEmissionsTracker.
Returns
-------
OfflineEmissionsTracker | None
Configured offline codecarbon tracker, if logger is specified.
"""
if janus_logger and track_carbon:
carbon_logger = LoggerOutput(janus_logger)
tracker = OfflineEmissionsTracker(
country_iso_code=country_iso_code,
save_to_file=save_to_file,
save_to_logger=True,
logging_logger=carbon_logger,
project_name="janus-core",
log_level=log_level,
allow_multiple_runs=True,
**kwargs,
)
# Suppress further logging from codecarbon
carbon_logger = logging.getLogger("codecarbon")
while carbon_logger.hasHandlers():
carbon_logger.removeHandler(carbon_logger.handlers[0])
if not hasattr(tracker, "_emissions"):
raise ValueError(
"Carbon tracker has not been configured correctly. Please try "
"reconfiguring, or disable the tracker."
)
else:
tracker = None
return tracker