From f4ed2727a3e32f50d6ff5e63c8c1ca6f169547be Mon Sep 17 00:00:00 2001 From: Oscar Levin Date: Tue, 4 Feb 2025 16:04:06 -0700 Subject: [PATCH] move logging logic to its own module (#910) * move logging logic to its own module * add log-setting functions, documentation --- CHANGELOG.md | 6 ++++- README.md | 7 +++++- docs/api.md | 24 ++++++++++++++++++++ pretext/__init__.py | 3 --- pretext/cli.py | 35 ++++------------------------- pretext/logger.py | 44 +++++++++++++++++++++++++++++++++++++ pretext/project/__init__.py | 5 +++++ 7 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 docs/api.md create mode 100644 pretext/logger.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e265c733..3c6b9ebb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,13 @@ Instructions: Add a subsection under `[Unreleased]` for additions, fixes, change ## [Unreleased] +### Added + +- Logging is now available when the CLI is used programmatically (as a library). See [docs/api.md](docs/api.md). + ### Changed -- Asset generation of asymptote, latex-image, and sageplot now utilize a *generated-cache* of images (stored in `.generated-cache` in the root of a project, but customizable in `project.ptx`). This should speed up building and generating assets. +- Asset generation of asymptote, latex-image, and sageplot now utilize a *generated-cache* of images (stored in `.cache` in the root of a project, but customizable in `project.ptx`). This should speed up building and generating assets. ## [2.12.0] - 2025-01-16 diff --git a/README.md b/README.md index 5f65c01e..c4cfdf06 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,11 @@ Similarly, `entities.ent` may be used: _Note: previously this was achieved with a `pretext-href` attribute - this is now deprecated and will be removed in a future release._ +--- +## Using this package as a library/API + +We have started documenting how you can use this CLI programmatically in [docs/api.md](docs/api.md). + --- ## Development @@ -212,7 +217,7 @@ poetry shell pretext --version # returns version being developed ``` -When inside a `poetry shell` you can navegate to other folders and run pretext commands. Doing so will use the current development environment version of pretext. +When inside a `poetry shell` you can navigate to other folders and run pretext commands. Doing so will use the current development environment version of pretext. ### Updating dependencies diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..cfb12f12 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,24 @@ +# Using PreTeXt as a library + +The PreTeXt-CLI includes `Project` and `Target` classes which can be used when this package is imported as a library. + +More details are coming soon. + +## Logging + +This package uses python's logging library and implements a logger with name `"ptxlogger"`. To get the same messages as the CLI gives (with default level of `INFO`), you can include the following. + +```python +import logging +from pretext import logger + +log = logging.getLogger("ptxlogger") +logger.add_log_stream_handler() +log.setLevel(logging.INFO) +``` + +The `logger.add_log_stream_handler()` function simply creates a stream-handler that outputs to stdout. You could set up `log` however you like using the options provided by the `logging` library. If you would like to get spit out the logs to a file as the CLI does, you could include the line + +```python +logger.add_log_file_handler(path_to_log_directory) +``` diff --git a/pretext/__init__.py b/pretext/__init__.py index d48e4c5d..14a583af 100644 --- a/pretext/__init__.py +++ b/pretext/__init__.py @@ -13,12 +13,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import logging from pathlib import Path from single_version import get_version -log = logging.getLogger("ptxlogger") - VERSION = get_version("pretext", Path(__file__).parent.parent) CORE_COMMIT = "3ce0b18284473f5adf52cea46374688299b6d643" diff --git a/pretext/cli.py b/pretext/cli.py index 6220f28d..5c3f5c17 100644 --- a/pretext/cli.py +++ b/pretext/cli.py @@ -4,7 +4,6 @@ import click import click_log import shutil -import datetime import os import zipfile import requests @@ -23,34 +22,18 @@ utils, resources, constants, + logger, plastex, server, VERSION, CORE_COMMIT, ) - from .project import Project log = logging.getLogger("ptxlogger") - -# click_handler logs all messages to stdout as the CLI runs -click_handler = logging.StreamHandler(sys.stdout) -click_handler.setFormatter(click_log.ColorFormatter()) -log.addHandler(click_handler) - -# error_flush_handler captures error/critical logs for flushing to stderr at the end of a CLI run -sh = logging.StreamHandler(sys.stderr) -sh.setFormatter(click_log.ColorFormatter()) -sh.setLevel(logging.ERROR) -error_flush_handler = logging.handlers.MemoryHandler( - capacity=1024 * 100, - flushLevel=100, - target=sh, - flushOnClose=False, -) -error_flush_handler.setLevel(logging.ERROR) -log.addHandler(error_flush_handler) +logger.add_log_stream_handler() +error_flush_handler = logger.get_log_error_flush_handler() # Add a decorator to provide nice exception handling for validation errors for all commands. It avoids printing a confusing traceback, and also nicely formats validation errors. @@ -153,15 +136,7 @@ def main(ctx: click.Context, targets: bool) -> None: project = Project.parse(pp) log.info(f"PreTeXt project found in `{utils.project_path()}`.") - # create file handler which logs even debug messages - logdir = pp / "logs" - logdir.mkdir(exist_ok=True) - logfile = logdir / f"{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}.log" - fh = logging.FileHandler(logfile, mode="w") - fh.setLevel(logging.DEBUG) - file_log_format = logging.Formatter("{levelname:<8}: {message}", style="{") - fh.setFormatter(file_log_format) - log.addHandler(fh) + logger.add_log_file_handler(pp / "logs") # permanently change working directory for rest of process os.chdir(pp) @@ -565,7 +540,6 @@ def build( ) targets = [target] log.debug(f"Building standalone document with target {target.name}") - log.debug(target) else: targets = [project.get_target(name=target_name)] except AssertionError as e: @@ -592,7 +566,6 @@ def build( for t in targets: t.source = Path(source_file).resolve() log.warning(f"Overriding source file for target with: {t.source}") - log.debug(t) # Call generate if flag is set if generate and not no_generate: diff --git a/pretext/logger.py b/pretext/logger.py new file mode 100644 index 00000000..7c33b691 --- /dev/null +++ b/pretext/logger.py @@ -0,0 +1,44 @@ +import datetime +from pathlib import Path +import sys +import logging +import click_log + +log = logging.getLogger("ptxlogger") + + +def add_log_stream_handler() -> None: + # Set up logging: + # click_handler logs all messages to stdout as the CLI runs + click_handler = logging.StreamHandler(sys.stdout) + click_handler.setFormatter(click_log.ColorFormatter()) + log.addHandler(click_handler) + + +def get_log_error_flush_handler() -> logging.handlers.MemoryHandler: + # error_flush_handler captures error/critical logs for flushing to stderr at the end of a CLI run + sh = logging.StreamHandler(sys.stderr) + sh.setFormatter(click_log.ColorFormatter()) + sh.setLevel(logging.ERROR) + error_flush_handler = logging.handlers.MemoryHandler( + capacity=1024 * 100, + flushLevel=100, + target=sh, + flushOnClose=False, + ) + error_flush_handler.setLevel(logging.ERROR) + log.addHandler(error_flush_handler) + return error_flush_handler + + +def add_log_file_handler(log_folder_path: Path) -> None: + # create file handler which logs even debug messages + log_folder_path.mkdir(exist_ok=True) + logfile = ( + log_folder_path / f"{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}.log" + ) + fh = logging.FileHandler(logfile, mode="w") + fh.setLevel(logging.DEBUG) + file_log_format = logging.Formatter("{levelname:<8}: {message}", style="{") + fh.setFormatter(file_log_format) + log.addHandler(fh) diff --git a/pretext/project/__init__.py b/pretext/project/__init__.py index 524864e0..e18c871c 100644 --- a/pretext/project/__init__.py +++ b/pretext/project/__init__.py @@ -40,6 +40,11 @@ from .. import VERSION +# If we want to always spit out log messages to stdout even when this module is imported as a library, we could do so with the following two lines: +# from .. import logger +# log = logger.log + +# Otherwise, we just set up the logger here to be used by the CLI. log = logging.getLogger("ptxlogger")