Skip to content

Commit 76b4124

Browse files
authored
chore: add more debug logging messages (#63)
* chore: enable more debug logging * chore: fix logging when proxied to wrangler * chore: put back comment
1 parent 3ddb7b9 commit 76b4124

File tree

5 files changed

+207
-5
lines changed

5 files changed

+207
-5
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ docs/_build/
6262
# OS specific
6363
.DS_Store
6464
Thumbs.db
65+
66+
tests/test_workspace

src/pywrangler/cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
WRANGLER_COMMAND,
1212
WRANGLER_CREATE_COMMAND,
1313
check_wrangler_version,
14+
log_startup_info,
1415
setup_logging,
1516
write_success,
1617
)
@@ -56,6 +57,8 @@ def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command:
5657
command = super().get_command(ctx, cmd_name)
5758

5859
if command is None:
60+
log_startup_info()
61+
5962
try:
6063
cmd_index = sys.argv.index(cmd_name)
6164
remaining_args = sys.argv[cmd_index + 1 :]
@@ -106,6 +109,8 @@ def app(debug: bool = False) -> None:
106109
if debug:
107110
logger.setLevel(logging.DEBUG)
108111

112+
log_startup_info()
113+
109114

110115
@app.command("types")
111116
@click.option(
@@ -135,6 +140,7 @@ def sync_command(force: bool = False) -> None:
135140
136141
Also creates a virtual env for Workers that you can use for testing.
137142
"""
143+
138144
sync(force, directly_requested=True)
139145
write_success("Sync process completed successfully.")
140146

src/pywrangler/sync.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ def parse_requirements() -> list[str]:
147147
dependencies = pyproject_data.get("project", {}).get("dependencies", [])
148148

149149
logger.info(f"Found {len(dependencies)} dependencies.")
150+
if dependencies:
151+
for dep in dependencies:
152+
logger.debug(f" - {dep}")
150153
return dependencies
151154

152155

@@ -215,6 +218,9 @@ def _install_requirements_to_vendor(requirements: list[str]) -> None:
215218
"Installation of packages into the Python Worker failed. Possibly because these packages are not currently supported. See above for details."
216219
)
217220
raise click.exceptions.Exit(code=result.returncode)
221+
222+
_log_installed_packages(get_pyodide_venv_path())
223+
218224
pyv = get_python_version()
219225
shutil.rmtree(vendor_path)
220226
shutil.copytree(
@@ -231,6 +237,20 @@ def _install_requirements_to_vendor(requirements: list[str]) -> None:
231237
)
232238

233239

240+
def _log_installed_packages(venv_path: Path) -> None:
241+
result = run_command(
242+
["uv", "pip", "list", "--format=freeze"],
243+
env=os.environ | {"VIRTUAL_ENV": venv_path},
244+
capture_output=True,
245+
check=False,
246+
)
247+
if result.returncode == 0 and result.stdout.strip():
248+
logger.debug("Installed packages:")
249+
for line in result.stdout.strip().split("\n"):
250+
if line.strip():
251+
logger.debug(f" {line.strip()}")
252+
253+
234254
def _install_requirements_to_venv(requirements: list[str]) -> None:
235255
# Create a requirements file for .venv-workers that includes pyodide-py
236256
venv_workers_path = get_venv_workers_path()
@@ -312,12 +332,15 @@ def sync(force: bool = False, directly_requested: bool = False) -> None:
312332
# Check if sync is needed based on file timestamps
313333
sync_needed = force or is_sync_needed()
314334
if not sync_needed:
335+
logger.debug("Sync not needed - no changes detected")
315336
if directly_requested:
316337
logger.warning(
317338
"pyproject.toml hasn't changed since last sync, use --force to ignore timestamp check"
318339
)
319340
return
320341

342+
logger.debug("Sync needed - proceeding with installation")
343+
321344
# Check to make sure a wrangler config file exists.
322345
check_wrangler_config()
323346

src/pywrangler/utils.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import logging
2+
import os
3+
import platform
24
import re
35
import subprocess
6+
import sys
47
import tomllib
58
from datetime import datetime
69
from functools import cache
@@ -20,12 +23,47 @@
2023

2124
logger = logging.getLogger(__name__)
2225

23-
SUCCESS_LEVEL = 100
24-
RUNNING_LEVEL = 15
25-
OUTPUT_LEVEL = 16
26+
SUCCESS_LEVEL = logging.CRITICAL + 50
27+
RUNNING_LEVEL = logging.DEBUG + 5
28+
OUTPUT_LEVEL = logging.DEBUG + 6
2629

30+
# Valid log levels for PYWRANGLER_LOG environment variable
31+
_LOG_LEVEL_MAP = {
32+
"debug": logging.DEBUG,
33+
"info": logging.INFO,
34+
"warning": logging.WARNING,
35+
"warn": logging.WARNING, # alias
36+
"error": logging.ERROR,
37+
}
38+
39+
40+
def setup_logging() -> int:
41+
"""
42+
Configure logging with Rich handler.
43+
44+
Reads PYWRANGLER_LOG environment variable to set log level.
45+
Valid values: debug, info, warning, warn, error (case-insensitive).
46+
Defaults to INFO if not set or invalid.
47+
48+
Returns:
49+
The configured logging level (e.g., logging.DEBUG, logging.INFO).
50+
"""
51+
# Determine log level from environment variable
52+
env_level = os.environ.get("PYWRANGLER_LOG", "").lower().strip()
53+
if env_level and env_level not in _LOG_LEVEL_MAP:
54+
# Print warning to stderr for invalid value (before logging is configured)
55+
print(
56+
f"Warning: Invalid PYWRANGLER_LOG value '{env_level}'. "
57+
f"Valid values: {', '.join(sorted(set(_LOG_LEVEL_MAP.keys())))}. "
58+
"Defaulting to 'info'.",
59+
file=sys.stderr,
60+
)
61+
log_level = logging.INFO
62+
elif env_level:
63+
log_level = _LOG_LEVEL_MAP[env_level]
64+
else:
65+
log_level = logging.INFO
2766

28-
def setup_logging() -> None:
2967
console = Console(
3068
theme=Theme(
3169
{
@@ -39,7 +77,7 @@ def setup_logging() -> None:
3977

4078
# Configure Rich logger
4179
logging.basicConfig(
42-
level=logging.INFO,
80+
level=log_level,
4381
format="%(message)s",
4482
force=True, # Ensure this configuration is applied
4583
handlers=[
@@ -52,6 +90,28 @@ def setup_logging() -> None:
5290
logging.addLevelName(RUNNING_LEVEL, "RUNNING")
5391
logging.addLevelName(OUTPUT_LEVEL, "OUTPUT")
5492

93+
return log_level
94+
95+
96+
def _get_pywrangler_version() -> str:
97+
"""Get the version of pywrangler."""
98+
try:
99+
from importlib.metadata import version
100+
101+
return version("workers-py")
102+
except Exception:
103+
return "unknown"
104+
105+
106+
def log_startup_info() -> None:
107+
"""
108+
Log startup information for debugging.
109+
"""
110+
logger.debug(f"pywrangler version: {_get_pywrangler_version()}")
111+
logger.debug(f"Python: {platform.python_version()}")
112+
logger.debug(f"Platform: {sys.platform}")
113+
logger.debug(f"Working directory: {Path.cwd()}")
114+
55115

56116
def write_success(msg: str) -> None:
57117
logging.log(SUCCESS_LEVEL, msg)

tests/test_cli.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,114 @@ def test_check_wrangler_version_insufficient(mock_run_command):
542542

543543
with pytest.raises(click.exceptions.Exit):
544544
check_wrangler_version()
545+
546+
547+
# Tests for PYWRANGLER_LOG environment variable
548+
549+
550+
def test_env_var_debug_level(test_dir, monkeypatch, caplog):
551+
monkeypatch.setenv("PYWRANGLER_LOG", "debug")
552+
create_test_pyproject(test_dir)
553+
create_test_wrangler_jsonc(test_dir)
554+
555+
# Need to reimport to pick up env var change
556+
import importlib
557+
558+
import pywrangler.utils
559+
560+
importlib.reload(pywrangler.utils)
561+
562+
from pywrangler.utils import setup_logging
563+
564+
level = setup_logging()
565+
assert level == logging.DEBUG
566+
567+
568+
def test_env_var_error_level(test_dir, monkeypatch):
569+
"""Test that PYWRANGLER_LOG=error sets ERROR level."""
570+
monkeypatch.setenv("PYWRANGLER_LOG", "error")
571+
572+
import importlib
573+
574+
import pywrangler.utils
575+
576+
importlib.reload(pywrangler.utils)
577+
578+
from pywrangler.utils import setup_logging
579+
580+
level = setup_logging()
581+
assert level == logging.ERROR
582+
583+
584+
def test_env_var_case_insensitive(test_dir, monkeypatch):
585+
"""Test that PYWRANGLER_LOG is case-insensitive."""
586+
monkeypatch.setenv("PYWRANGLER_LOG", "DEBUG")
587+
588+
import importlib
589+
590+
import pywrangler.utils
591+
592+
importlib.reload(pywrangler.utils)
593+
594+
from pywrangler.utils import setup_logging
595+
596+
level = setup_logging()
597+
assert level == logging.DEBUG
598+
599+
600+
def test_debug_flag_overrides_env(test_dir, monkeypatch, caplog):
601+
"""Test that --debug flag overrides PYWRANGLER_LOG=error."""
602+
monkeypatch.setenv("PYWRANGLER_LOG", "error")
603+
create_test_pyproject(test_dir)
604+
create_test_wrangler_jsonc(test_dir)
605+
606+
runner = CliRunner()
607+
runner.invoke(app, ["--debug", "sync"])
608+
609+
debug_logs = [
610+
record for record in caplog.records if record.levelno == logging.DEBUG
611+
]
612+
assert len(debug_logs) > 0, "--debug flag should override PYWRANGLER_LOG=error"
613+
614+
615+
def test_env_var_invalid(test_dir, monkeypatch, capsys):
616+
"""Test that invalid PYWRANGLER_LOG value produces warning."""
617+
monkeypatch.setenv("PYWRANGLER_LOG", "invalid_value")
618+
619+
import importlib
620+
621+
import pywrangler.utils
622+
623+
importlib.reload(pywrangler.utils)
624+
625+
from pywrangler.utils import setup_logging
626+
627+
level = setup_logging()
628+
629+
captured = capsys.readouterr()
630+
assert "Warning" in captured.err
631+
assert "invalid_value" in captured.err
632+
assert level == logging.INFO
633+
634+
635+
def test_startup_banner(test_dir, monkeypatch):
636+
"""Test that debug output contains version, platform, and working directory."""
637+
monkeypatch.setenv("PYWRANGLER_LOG", "debug")
638+
create_test_pyproject(test_dir)
639+
create_test_wrangler_jsonc(test_dir)
640+
641+
import importlib
642+
643+
import pywrangler.utils
644+
645+
importlib.reload(pywrangler.utils)
646+
647+
from pywrangler.utils import _get_pywrangler_version, log_startup_info
648+
649+
# Verify the functions exist and return expected content
650+
version = _get_pywrangler_version()
651+
assert version is not None
652+
653+
# Verify log_startup_info can be called without error
654+
# The actual logging is tested via integration in test_debug_flag
655+
log_startup_info()

0 commit comments

Comments
 (0)