feat(verbosity): unify rust + python worker logging via TRYKE_LOG#94
feat(verbosity): unify rust + python worker logging via TRYKE_LOG#94
Conversation
The previous design had four independent verbosity knobs that didn't
talk to each other:
- `-v`/`-q` flags drove rust `log` filter only
- `RUST_LOG` was ignored entirely (env_logger::Builder::new())
- `TRYKE_WORKER_LOG` configured python workers, undocumented and
not auto-set
- the `Verbosity` reporter enum was derived from log level via
a hand-rolled match that collapsed three log levels into one
Concretely, a user running `tryke -v test` to debug a flaky worker
saw rust info logs but no python logs — they had to know about the
undocumented `TRYKE_WORKER_LOG` env var and set it manually. The
VS Code extension's `RUST_LOG=tryke=info` was being dropped on the
floor entirely, so the "Tryke Server" output panel was empty.
This change introduces a single user-facing knob (`-v`/`-q` flags or
`TRYKE_LOG` env) that propagates to both the rust process and every
python worker spawned. Mirrors uv's "RUST_LOG forwarded to PEP 517
build backends" model and avoids pytest-xdist's well-known footgun
where worker verbosity diverges from the parent.
Resolution chain (rust):
RUST_LOG > TRYKE_LOG > -v/-q > warn
Resolution chain (python worker):
TRYKE_LOG > -v/-q (only if more verbose than warn) > off
`RUST_LOG` is honored as a power-user override for the rust side
(its per-module filter syntax doesn't map onto a python log level,
so it deliberately doesn't propagate to workers).
Concrete changes:
- `tryke_config::rust_log_default` and `worker_log_level` resolvers
mirror `resolve_python`'s shape; tested across the precedence chain
- `WorkerPool::new` / `with_python_path` / `WorkerProcess::spawn`
take a `log::LevelFilter`; the spawn `Command` sets `TRYKE_LOG`
on the child env when level > Off
- `tryke/main.rs` resolves the rust default via the new helper and
feeds `env_logger::Builder::from_env(default_filter_or(...))` so
`RUST_LOG` wins natively (subsumes #91)
- `Verbosity::from_level_filter` replaces the awkward log::Level
match in main.rs with a single helper on the reporter side
- `TRYKE_WORKER_LOG` is retained as a deprecated alias for one
cycle with a one-time deprecation log line
- configuration guide documents the precedence chain end-to-end
- CONTRIBUTING.md: `cargo run -- test` (delayed copilot follow-up
from #93)
This is backwards-incompatible for direct callers of `WorkerPool` /
`WorkerProcess::spawn` — they all gain a `LevelFilter` parameter.
Most consumers already pass `LevelFilter::Off` (test helpers). The
external CLI surface is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR unifies Tryke's logging configuration so a single user-facing verbosity setting can drive both Rust-side logging and spawned Python worker logging, while still preserving RUST_LOG as a Rust-only power-user override. It also threads the resolved worker log level through the runner/server/watch paths and documents the new precedence rules.
Changes:
- Added config helpers to resolve Rust default logging and Python worker log propagation from
TRYKE_LOGand CLI verbosity. - Threaded
LevelFilterthrough worker pool, test execution, watch mode, and server startup so spawned workers can receiveTRYKE_LOG. - Updated worker/docs/reporter plumbing to support the new logging model and documented the precedence chain.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| python/tryke/worker.py | Reads TRYKE_LOG first, keeps TRYKE_WORKER_LOG as a deprecated alias, and configures worker-side logging. |
| docs/guides/configuration.md | Documents the new logging model, precedence, and examples. |
| crates/tryke_server/src/server.rs | Threads resolved worker log level into server-created worker pools. |
| crates/tryke_server/src/handler.rs | Updates tests/helpers for the new WorkerPool::new signature. |
| crates/tryke_runner/src/worker.rs | Adds child env propagation of TRYKE_LOG and tests the env-value mapping helper. |
| crates/tryke_runner/src/pool.rs | Propagates log level through pool lifecycle and worker spawning paths. |
| crates/tryke_reporter/src/text.rs | Adds Verbosity::from_level_filter to centralize reporter verbosity mapping. |
| crates/tryke_reporter/Cargo.toml | Adds log dependency needed for reporter verbosity mapping. |
| crates/tryke_config/src/lib.rs | Introduces rust_log_default and worker_log_level helpers plus unit tests. |
| crates/tryke_config/Cargo.toml | Adds log dependency for config-level log filter parsing. |
| crates/tryke/src/watch.rs | Passes resolved worker log level into watch-mode worker pools. |
| crates/tryke/src/main.rs | Resolves logging precedence, initializes env_logger from env/defaults, and threads worker logging through commands. |
| crates/tryke/src/execution.rs | Passes resolved worker log level into test execution worker pools. |
| Cargo.lock | Records new crate dependencies. |
| CONTRIBUTING.md | Updates cargo invocation example. |
| AGENTS.md | Documents the new repository logging contract. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // verbosity than the `Warn` default. | ||
| let worker_log = tryke_config::worker_log_level(tryke_log.as_deref(), cli_filter); | ||
|
|
||
| let verbosity = Verbosity::from_level_filter(cli_filter); |
| if use_legacy: | ||
| _log.warning( | ||
| "TRYKE_WORKER_LOG is deprecated; use TRYKE_LOG instead " | ||
| "(it propagates to both rust and python workers)" | ||
| ) |
| ``-v`` / ``-q`` or the ``TRYKE_LOG`` env var asks for cross-language | ||
| verbosity, so users typically don't set this directly. |
Three real findings from copilot review on the redesign: - Reporter `Verbosity` derived from raw CLI filter, not the resolved cross-language level — so `TRYKE_LOG=info tryke test` would light up logs but leave the text reporter in `Normal` mode, contradicting the documented "single knob" promise. Use the resolved `rust_default` (which already incorporates `TRYKE_LOG`) as the source. `RUST_LOG` is deliberately not consulted here: it's a rust-internal filter, not a user-facing UI knob. - The python worker emitted the `TRYKE_WORKER_LOG` deprecation warning per worker process, so multi-worker pools and watch-mode restarts spammed it N times. Move the alias resolution + warning to the rust side so it fires exactly once per `tryke` invocation, regardless of pool size or restart count. Python still honors the legacy env for the standalone-worker case but no longer warns. - Worker docstring claimed `-q` triggered cross-language verbosity; the resolver only propagates explicit `-v` (more verbose than the `warn` default). Fix the docstring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thejchap
left a comment
There was a problem hiding this comment.
Folded copilot feedback into 69a2a0b: reporter Verbosity now follows the resolved cross-language level (so TRYKE_LOG=info lifts the text reporter to Verbose), TRYKE_WORKER_LOG deprecation warning moved to the rust side so it fires once per invocation rather than once per worker, and the python docstring no longer claims -q propagates.
Declining the CONTRIBUTING.md lowercase-verb nit — the surrounding prose in that file is uniformly lowercase (run, we use, install it once, etc.), so capitalizing only line 31 would be inconsistent. Worth a separate sweep if the project wants to adopt the AGENTS.md capitalization rule for docs.
Backwards compat isn't a goal here — `TRYKE_LOG` is the only env var. Removes the rust-side legacy lookup + deprecation warning, the python-side fallback, and the docs paragraph describing the alias. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| on stdout. | ||
| """ | ||
| level_name = os.environ.get("TRYKE_WORKER_LOG", "").strip().upper() | ||
| level_name = os.environ.get("TRYKE_LOG", "").strip().upper() |
Context
Verbosity in tryke today is fragmented across four independent knobs that don't talk to each other:
-v/-qflags drive rust logging,RUST_LOGis silently dropped,TRYKE_WORKER_LOGconfigures python workers but is undocumented and not auto-set, and aVerbosityreporter enum is derived from log level via an awkward hand-rolled match.In practice that means:
tryke -v testlights up rust logs but not python worker logs. Diagnosing a flaky worker requires knowing about the undocumentedTRYKE_WORKER_LOGenv var and setting it manually.RUST_LOG=tryke=<level>(issue fix(logging): honor RUST_LOG so the VS Code server output isn't empty #91) was being dropped entirely becauseenv_logger::Builder::new()ignoresRUST_LOG.Approach
Adopt a single user-facing knob (
-v/-qflags orTRYKE_LOGenv) that propagates to both the rust process and every python worker it spawns.RUST_LOGstays as a power-user override for the rust side only — its per-module filter syntax (tryke=info,hyper=warn) doesn't map onto a python log level, so it deliberately doesn't propagate to workers. This mirrors uv's ""RUST_LOG forwarded to PEP 517 build backends"" model and avoids pytest-xdist's well-known footgun where worker verbosity diverges from the parent.Precedence
Rust log filter (consumed by
env_logger):RUST_LOG(wins natively viaBuilder::from_env).TRYKE_LOG.-v/-qflag.warn.Python worker log (set as
TRYKE_LOGon the worker's env):TRYKE_LOGif set.warn(i.e., the user passed at least one-v).Concrete changes
tryke_config::rust_log_default+worker_log_levelmirroringresolve_python's shape (8 unit tests covering the precedence chain).WorkerPool::new/with_python_path/WorkerProcess::spawntake alog::LevelFilter; the spawnCommandsetsTRYKE_LOGon the child env when level > Off. Backwards-incompatible for direct callers — the user OK'd this.tryke/main.rsresolves the rust default via the new helper and feedsenv_logger::Builder::from_env(default_filter_or(...))soRUST_LOGwins natively. This subsumes fix(logging): honor RUST_LOG so the VS Code server output isn't empty #91, which can be closed when this lands.Verbosity::from_level_filterreplaces the hand-rolledlog::Levelmatch (which collapsed three log levels into one reporter level).python/tryke/worker.py:_configure_logging_from_envreadsTRYKE_LOGfirst;TRYKE_WORKER_LOGis retained as a deprecated alias for one cycle with a one-time deprecation log line.docs/guides/configuration.mddocuments the full precedence chain end-to-end with examples.CONTRIBUTING.md:cargo run -- test(delayed copilot follow-up from refactor(tests): extract test_python_bin into tryke_testing #93).Doesn't do
Verbosityfrom log level. That's pytest's model (-v= UI detail,--log-level= diagnostic filter) and is the right end-state, but it's a separate UX change worth its own PR.TRACE→DEBUG(the current behavior) is fine.Test plan
Unit:
tryke_configprecedence-chain tests forrust_log_defaultandworker_log_level(env wins, CLI fallback, garbage values, default behavior).tryke_runner::worker::worker_log_env_valuereturnsNoneforOffand lowercase level names otherwise.Integration:
LevelFilter::Offargument threaded through.End-to-end (manual):
Related
RUST_LOG=tryke=<level>forTRYKE_LOG=<level>so worker logs also flow into the ""Tryke Server"" output panel.