Skip to content

Commit

Permalink
Merge pull request #706 from materialsproject/custom-log-fmt
Browse files Browse the repository at this point in the history
`JobflowSettings` add `LOG_FORMAT`, also directly passable to `run_locally`
  • Loading branch information
utf authored Nov 18, 2024
2 parents 1187fae + ddb53dd commit cef3584
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 16 deletions.
11 changes: 7 additions & 4 deletions src/jobflow/managers/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

def run_locally(
flow: jobflow.Flow | jobflow.Job | list[jobflow.Job],
log: bool = True,
log: bool | str = True,
store: jobflow.JobStore | None = None,
create_folders: bool = False,
root_dir: str | Path | None = None,
Expand All @@ -30,8 +30,11 @@ def run_locally(
----------
flow : Flow | Job | list[Job]
A job or flow.
log : bool
Whether to print log messages.
log : bool | str
Controls logging. Defaults to True. Can be:
- False: disable logging
- True: use default logging format (read from ~/.jobflow.yaml)
- str: custom logging format string (e.g. "%(message)s" for more concise output)
store : JobStore
A job store. If a job store is not specified then
:obj:`JobflowSettings.JOB_STORE` will be used. By default this is a maggma
Expand Down Expand Up @@ -77,7 +80,7 @@ def run_locally(
store.connect()

if log:
initialize_logger()
initialize_logger(fmt=log if isinstance(log, str) else "")

flow = get_flow(flow, allow_external_references=allow_external_references)

Expand Down
15 changes: 13 additions & 2 deletions src/jobflow/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from jobflow import JobStore

DEFAULT_CONFIG_FILE_PATH = Path("~/.jobflow.yaml").expanduser().as_posix()
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"
DEFAULT_DIRECTORY_FORMAT = "%Y-%m-%d-%H-%M-%S-%f"


def _default_additional_store():
Expand All @@ -28,7 +30,7 @@ class JobflowSettings(BaseSettings):
"""
Settings for jobflow.
The default way to modify these is to modify ~/.jobflow.yaml. Alternatively,
The default way to modify these is to create a ~/.jobflow.yaml. Alternatively,
the environment variable ``JOBFLOW_CONFIG_FILE`` can be set to point to a yaml file
with jobflow settings.
Expand Down Expand Up @@ -114,9 +116,18 @@ class JobflowSettings(BaseSettings):
"accepted formats.",
)
DIRECTORY_FORMAT: str = Field(
"%Y-%m-%d-%H-%M-%S-%f",
DEFAULT_DIRECTORY_FORMAT,
description="Date stamp format used to create directories",
)
LOG_FORMAT: str = Field(
DEFAULT_LOG_FORMAT,
description="""Logging format string. Common format codes:
- %(message)s - The logged message
- %(asctime)s - Human-readable time
- %(levelname)s - DEBUG, INFO, WARNING, ERROR, or CRITICAL
- %(name)s - Logger name
See Python logging documentation for more format codes.""",
)

UID_TYPE: str = Field(
"uuid4", description="Type of unique identifier to use to track jobs. "
Expand Down
18 changes: 15 additions & 3 deletions src/jobflow/utils/log.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
"""Tools for logging."""

from __future__ import annotations

import logging


def initialize_logger(level: int = logging.INFO) -> logging.Logger:
def initialize_logger(level: int = logging.INFO, fmt: str = "") -> logging.Logger:
"""Initialize the default logger.
Parameters
----------
level
The log level.
fmt
Custom logging format string. Defaults to JobflowSettings.LOG_FORMAT.
Common format codes:
- %(message)s - The logged message
- %(asctime)s - Human-readable time
- %(levelname)s - DEBUG, INFO, WARNING, ERROR, or CRITICAL
See Python logging documentation for more format codes.
Returns
-------
Expand All @@ -18,12 +27,15 @@ def initialize_logger(level: int = logging.INFO) -> logging.Logger:
"""
import sys

from jobflow import SETTINGS

log = logging.getLogger("jobflow")
log.setLevel(level)
log.handlers = [] # reset logging handlers if they already exist

fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
formatter = logging.Formatter(fmt or SETTINGS.LOG_FORMAT)

screen_handler = logging.StreamHandler(stream=sys.stdout)
screen_handler.setFormatter(fmt)
screen_handler.setFormatter(formatter)
log.addHandler(screen_handler)
return log
10 changes: 9 additions & 1 deletion tests/managers/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ def test_simple_flow(memory_jobstore, clean_dir, simple_flow, capsys):
assert "INFO Started executing jobs locally" not in captured.out
assert "INFO Finished executing jobs locally" not in captured.out

# run with log
# run with custom log format
custom_fmt = "%(name)s: %(levelname)s - %(message)s"
run_locally(flow, store=memory_jobstore, log=custom_fmt)
stdout, stderr = capsys.readouterr()
assert "jobflow.managers.local: INFO - Started executing jobs locally" in stdout
assert "jobflow.managers.local: INFO - Finished executing jobs locally" in stdout
assert stderr == ""

# run with log=True
responses = run_locally(flow, store=memory_jobstore)

# check responses has been filled
Expand Down
24 changes: 18 additions & 6 deletions tests/utils/test_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ def test_initialize_logger(capsys):
logger.info("123")
logger.debug("ABC")

captured = capsys.readouterr()
assert "INFO 123" in captured.out
assert "DEBUG" not in captured.out
stdout, stderr = capsys.readouterr()
assert stdout.endswith("INFO 123\n")
assert stdout.count("DEBUG") == 0
assert stderr == ""

# initialize logger with debug level
initialize_logger(level=logging.DEBUG)
logger.info("123")
stdout, stderr = capsys.readouterr()
assert stdout.endswith("INFO 123\n")

logger.debug("ABC")
stdout, stderr = capsys.readouterr()
assert stdout.endswith("DEBUG ABC\n")
assert stderr == ""

# test with custom format string
custom_fmt = "%(levelname)s - %(message)s"
initialize_logger(fmt=custom_fmt)
logger.info("custom format")

captured = capsys.readouterr()
assert "INFO 123" in captured.out
assert "DEBUG ABC" in captured.out
stdout, stderr = capsys.readouterr()
assert stdout == "INFO - custom format\n"
assert stderr == ""

0 comments on commit cef3584

Please sign in to comment.