Merge pull request #71 from macaodha/enhancement/logs-to-file
Some checks are pending
CI / Checks (push) Waiting to run
CI / Tests (Python ${{ matrix.python-version }}) (3.10) (push) Waiting to run
CI / Tests (Python ${{ matrix.python-version }}) (3.11) (push) Waiting to run
CI / Tests (Python ${{ matrix.python-version }}) (3.12) (push) Waiting to run
Docs Pages / Build Docs (push) Waiting to run
Docs Pages / Deploy Docs (push) Blocked by required conditions

Add CLI log file output
This commit is contained in:
Santiago Martinez Balvanera 2026-06-22 21:39:12 -06:00 committed by GitHub
commit 833db23855
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 77 additions and 3 deletions

View File

@ -17,6 +17,8 @@ for the full option list.
## Notes
- Global CLI options are documented in {doc}`base`.
- Use `--log-file path/to/cli.log` to save CLI logs to a file while still
showing them in the terminal.
- Paths with spaces should be wrapped in quotes.
- Input audio is expected to be mono.
- `process` uses the optional `--detection-threshold` override.

View File

@ -97,6 +97,23 @@ What this does:
- runs the model on each recording,
- saves the results in `path/to/outputs`.
```{eval-rst}
.. admonition:: Save CLI logs to a file
:collapsible: closed
:class: tip dropdown
If you want to keep a copy of the CLI logs, add ``--log-file`` before the
subcommand:
.. code-block:: bash
batdetect2 --log-file path/to/cli.log process directory \
path/to/audio \
path/to/outputs
This writes the same CLI logs to ``path/to/cli.log`` and to the terminal.
```
You do not need to choose a model for this first run.
If you do nothing, BatDetect2 uses the built-in default UK model.

View File

@ -1,5 +1,7 @@
"""BatDetect2 command line interface."""
from pathlib import Path
import click
from batdetect2.cli.ascii import BATDETECT_ASCII_ART
@ -22,8 +24,17 @@ BatDetect2
count=True,
help="Increase verbosity. -v for INFO, -vv for DEBUG.",
)
@click.option(
"--log-file",
type=click.Path(path_type=Path),
help="Write CLI logs to a file in addition to the terminal.",
)
@click.pass_context
def cli(ctx: click.Context, verbose: int = 0):
def cli(
ctx: click.Context,
verbose: int = 0,
log_file: Path | None = None,
) -> None:
"""Run the BatDetect2 CLI.
Use subcommands to run processing, training, evaluation, and dataset
@ -37,4 +48,4 @@ def cli(ctx: click.Context, verbose: int = 0):
from batdetect2.logging import enable_logging
enable_logging(verbose)
enable_logging(verbose, log_file=log_file)

View File

@ -50,7 +50,7 @@ __all__ = [
]
def enable_logging(level: int):
def enable_logging(level: int, log_file: Path | None = None) -> None:
logger.remove()
if level == 0:
@ -61,6 +61,11 @@ def enable_logging(level: int):
log_level = "DEBUG"
logger.add(sys.stderr, level=log_level)
if log_file is not None:
log_file.parent.mkdir(parents=True, exist_ok=True)
logger.add(log_file, level=log_level)
logger.enable("batdetect2")

View File

@ -1,9 +1,13 @@
"""Behavior-focused tests for top-level CLI command discovery."""
from pathlib import Path
from click.testing import CliRunner
from batdetect2.cli import cli
BASE_DIR = Path(__file__).parent.parent.parent
def test_cli_base_help_lists_main_commands() -> None:
"""User story: discover available workflows from top-level help."""
@ -11,8 +15,43 @@ def test_cli_base_help_lists_main_commands() -> None:
result = CliRunner().invoke(cli, ["--help"])
assert result.exit_code == 0
assert "--log-file" in result.output
assert "process" in result.output
assert "train" in result.output
assert "evaluate" in result.output
assert "data" in result.output
assert "detect" in result.output
def test_cli_writes_logs_to_file_and_terminal(
tmp_path: Path,
tiny_checkpoint_path: Path,
) -> None:
"""User story: save CLI logs to a file while keeping terminal output."""
output_dir = tmp_path / "eval_out"
log_file = tmp_path / "logs" / "cli.log"
result = CliRunner().invoke(
cli,
[
"--log-file",
str(log_file),
"-v",
"evaluate",
str(BASE_DIR / "example_data" / "dataset.yaml"),
"--model",
str(tiny_checkpoint_path),
"--base-dir",
str(BASE_DIR),
"--workers",
"0",
"--output-dir",
str(output_dir),
],
)
assert result.exit_code == 0
assert "Initiating evaluation process..." in result.output
assert log_file.exists()
assert "Initiating evaluation process..." in log_file.read_text()