Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JobflowSettings add LOG_FORMAT, also directly passable to run_locally #706

Merged
merged 5 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 == ""
Loading