Skip to content
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
24 changes: 22 additions & 2 deletions src/agent_env/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
from collections.abc import Iterator
from contextlib import contextmanager

import click

Expand Down Expand Up @@ -43,14 +45,32 @@ def invoke(self, ctx: click.Context):
raise click.ClickException("\n".join([str(e), *getattr(e, "__notes__", ())])) from e


@contextmanager
def _verbose_logging() -> Iterator[None]:
"""Log agent-env's own DEBUG messages, and only warnings from other libraries, while a command runs: their debug
logs can carry credentials, as hpack's do, which log every HTTP/2 header Modal's client sends, its token among
them. The loggers' levels are put back after it, so a CLI invoked in-process leaves the caller's logging as it was."""
logging.basicConfig(level=logging.WARNING, format="%(asctime)s %(name)s %(message)s")
loggers = [logging.getLogger(name) for name in ("agent_env", "agentenv_protocol")]
levels = [logger.level for logger in loggers]
for logger in loggers:
logger.setLevel(logging.DEBUG)
try:
yield
finally:
for logger, level in zip(loggers, levels):
logger.setLevel(level)


@click.group(cls=_UserErrorsAreNotCrashes)
@click.version_option(package_name="agentenv-framework", prog_name="agent-env", message="%(prog)s %(version)s")
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging (DEBUG level)")
@click.option("--verbose", "-v", is_flag=True,
help="Enable agent-env's DEBUG logging (other libraries log warnings only)")
@click.pass_context
def cli(ctx: click.Context, verbose: bool):
"""Agent environment CLI."""
if verbose:
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(name)s %(message)s")
ctx.with_resource(_verbose_logging())
# For this command only, so a CLI invoked in-process leaves the caller's stores as they were.
ctx.with_resource(namespace_routing())

Expand Down
41 changes: 41 additions & 0 deletions tst/unit/cli/verbose_logging_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""``agent-env -v`` logs agent-env's own DEBUG messages, and only warnings from other libraries, whose debug logs can
carry credentials: hpack logs every HTTP/2 header Modal's client sends, its token among them."""

import logging
import subprocess
import sys

from click.testing import CliRunner

from agent_env.cli import cli

# In a fresh interpreter, as the CLI runs: pytest's own handlers on the root logger would make basicConfig a no-op here.
_LOGS = """
import logging
from agent_env.cli import _verbose_logging
with _verbose_logging():
logging.getLogger("agent_env.providers.sandbox_providers.modal_sandbox").debug("creating a sandbox")
logging.getLogger("agentenv_protocol.client").debug("reading a card")
logging.getLogger("hpack.hpack").debug("Adding b'x-modal-token-secret'=b'as-0000' to the header table")
logging.getLogger("hpack.hpack").warning("a header table warning")
"""


def test_verbose_logs_agent_envs_debug_messages_and_only_warnings_from_other_libraries():
done = subprocess.run([sys.executable, "-c", _LOGS], capture_output=True, text=True, timeout=120)

assert done.returncode == 0, done.stderr
assert "creating a sandbox" in done.stderr and "reading a card" in done.stderr
assert "a header table warning" in done.stderr
assert "x-modal-token-secret" not in done.stderr


def test_a_verbose_command_run_in_process_leaves_the_callers_logging_as_it_was():
"""Run in a caller's process, as the tests and any embedding code run it, -v lasts only for its command."""
loggers = [logging.getLogger(name) for name in ("agent_env", "agentenv_protocol")]
before = [logger.level for logger in loggers]

result = CliRunner().invoke(cli, ["-v", "config", "--help"])

assert result.exit_code == 0, result.output
assert [logger.level for logger in loggers] == before
Loading