How to Debug Switchyard Applications: End-to-End Logging for NVIDIA NeMo’s Python-Rust Stack
Enable logging.DEBUG in Python and set RUST_LOG=debug to trace requests across Switchyard’s bilingual architecture, using respx for mocked testing and cargo test for Rust validation.
Switchyard is a sophisticated request-routing framework that mediates between Python client code and LLM back-ends through a native Rust server. Debugging applications built with the NVIDIA-NeMo/Switchyard repository requires tracing execution across the Python façade and the PyO3 bindings that interface with the Rust core. This guide covers how to debug Switchyard applications by leveraging the logging module for Python components and the tracing crate for Rust diagnostics, with specific techniques for common failure modes.
Understanding the Switchyard Architecture
Before inserting breakpoints, you must understand where code executes. The system spans four distinct layers:
| Layer | Language | Role | Key Source File |
|---|---|---|---|
| Python façade | Python | Exposes switchyard.* APIs and loads Rust bindings |
switchyard/__init__.py |
| Rust server | Rust (PyO3) | Hosts the HTTP server and parses TOML deployment configs | switchyard_rust/server.py |
| Libsy algorithms | Python | Implements routing strategies that select endpoints | switchyard/libsy/algorithms.py |
| LLM client bindings | Rust | Translates provider-neutral protocol to HTTP calls | crates/libsy-llm-client |
All traffic flows Client → Python façade → Rust server → Provider-neutral protocol → Upstream LLM. Because the stack spans two languages, you must monitor two distinct debugging surfaces simultaneously.
Enabling Verbose Logging Across the Stack
Python Side Configuration
Most Switchyard modules use the standard logging library. To capture request routing, configuration loading, and error handling, configure the root logger or the specific "switchyard" logger:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("switchyard")
logger.debug("Switchyard debug enabled")
Setting level=logging.DEBUG ensures that high-level routing decisions in switchyard/libsy/algorithms.py emit detailed traces before invoking the native layer.
Rust Server Diagnostics
The Rust components use the tracing crate, which respects the RUST_LOG environment variable. To see granular event logs from the native server:
export RUST_LOG=debug
uv run python -m switchyard_rust.server
When launching via the CLI entry point (switchyard-server), the same variable applies. The switchyard_rust/server.py file reads this environment variable during initialization to configure the subscriber.
Unified Debug Environment
Combine both logging systems when running a full application:
export RUST_LOG=debug
python -c "import logging; logging.basicConfig(level=logging.DEBUG); import switchyard"
This prints every Python log line alongside Rust tracing events, allowing you to correlate the high-level Python flow in switchyard/__init__.py with low-level Rust request processing.
Debugging the Rust Server Process
Foreground Execution and TOML Validation
Run the Rust server in the foreground to catch startup failures immediately. The binary prints its own startup banner and configuration errors. If the server aborts, inspect the generated TOML files—misconfigured routes surface as parsing errors with exact file paths and line numbers logged to stdout.
Unit Testing with Cargo
For rapid iteration without launching an HTTP server, use the Rust test harness:
cargo test --workspace
Rust unit tests in crates/*/tests compile with the env_logger test harness and respect RUST_LOG=debug. This is the fastest way to verify that a routing algorithm behaves correctly before integration testing.
Testing and Debugging Python Bindings
Pytest with Verbose Output
The Python test suite exercises PyO3 entry points. Run with maximum verbosity to see each test name and any print or logging output:
uv run pytest -vv
This executes tests in tests/test_libsy_minimal_bindings.py, which demonstrates how Python bindings interact with the Rust layer. Use these tests as a sandbox for adding temporary logger.debug statements.
Isolating Requests with respx
When debugging a specific routing decision, use the respx library to mock upstream HTTP calls. This isolates the logic in switchyard/libsy/algorithms.py from external network variables.
import logging
import respx
from switchyard.libsy import algorithms
logging.basicConfig(level=logging.DEBUG)
@respx.mock
def test_routing_debug():
respx.get("https://api.openai.com/v1/chat/completions").mock(
return_value=respx.Response(200, json={"choices": []})
)
result = algorithms.route_request(...)
assert result is not None
Enable logging.DEBUG before the mock to see exactly which endpoint the routing algorithm selected in the selected_route field.
Common Debugging Scenarios
-
“Provider-neutral request type not found”: This indicates a mismatch between the Python wrapper and Rust struct definitions. Verify that the
switchyard-protocolcrate version matches the Python package usingpip show switchyard. -
Server crashes on startup: Invalid TOML configuration (e.g., missing endpoints) causes immediate termination. Run with
RUST_LOG=debugto see the exact parsing error and line number before the abort. -
Unexpected latency: If the routing algorithm falls back to a slower endpoint, enable
logging.DEBUGon the Python side and inspect theselected_routefield in each request log emitted byswitchyard/libsy/algorithms.py. -
Missing environment variables: API keys like
OPENAI_API_KEYmust be exported before starting the server. The Rust side reads these viastd::envduring initialization inswitchyard_rust/server.py; they are not hot-reloaded.
Quick Debugging Checklist
- Set
logging.basicConfig(level=logging.DEBUG)in your Python entry script to capture façade-level events. - Export
RUST_LOG=debug(ortrace) before launching the server to enable Rusttracingoutput. - Run
uv run pytest -vvto verify Python bindings and catch PyO3 interface errors. - Execute
cargo test --workspaceto validate Rust implementations in isolation. - Inspect server logs for lines prefixed with
[DEBUG switchyard::...]to trace native execution. - Use
respxto mock external endpoints and isolate routing logic from network variability. - Verify that all required environment variables are present in the process environment before starting
switchyard-server.
Summary
- Combine logging systems: Use Python’s
logging.DEBUGalongsideRUST_LOG=debugto trace requests fromswitchyard/__init__.pythrough the Rust server to the upstream LLM. - Validate configurations early: Run the server in foreground mode to catch TOML parsing errors with exact line numbers before they cause runtime failures.
- Test layers independently: Use
cargo test --workspacefor Rust algorithms andrespxmocks for Python routing logic to isolate defects without external dependencies.
Frequently Asked Questions
How do I enable debug logging for the Switchyard Rust server?
Set the RUST_LOG environment variable to debug or trace before launching the server. The Rust components use the tracing crate, which emits granular event logs including HTTP request parsing and TOML configuration loading. This applies whether you run switchyard-server directly or launch via uv run python -m switchyard_rust.server.
Why is my Switchyard application failing to start with a TOML error?
The Rust server strictly validates deployment configuration files at startup. Invalid TOML syntax or missing endpoint definitions cause immediate termination with parsing errors. Run the server with RUST_LOG=debug to see the exact file path and line number where the parser failed, then cross-reference with the schema requirements in switchyard_rust/server.py.
How can I test Switchyard routing algorithms without hitting real LLM endpoints?
Use the respx library to mock HTTP routes in your pytest suite. Import algorithms from switchyard.libsy, enable logging.DEBUG to capture the selected_route field, and decorate tests with @respx.mock to intercept calls to providers like OpenAI. This isolates the routing decision logic from network latency and API quota limits.
Where does Switchyard log the selected route for each request?
The routing decision is logged by the Python layer in switchyard/libsy/algorithms.py at the DEBUG level. Look for log entries containing the selected_route field after calling algorithms.route_request(). If using the Rust server directly, the tracing events in crates/libsy-llm-client log the final HTTP target before dispatch.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →