6.4 KiB
swarm-debug — OpenSwarm's logger for App backends
swarm_debug (the swarm-debug package on PyPI) is OpenSwarm's opinionated
print() replacement for the App Builder's backend code. It prints colored,
indented, frame-aware log lines that read at a glance and land in the App
Builder's Terminal tab under the [BACKEND] prefix.
It's pre-installed in every App Builder workspace that has a backend (i.e.
after bash backend_init.sh). Use it instead of print().
Basic usage
from swarm_debug import debug
debug("hello") # [endpoint_name] : hello
debug({"user_id": 42, "ok": True}) # [endpoint_name] : {'user_id': 42, 'ok': True}
debug(some_dataframe) # auto-truncated to 3000 chars
The function reads the calling line of source code to extract the variable name(s), so:
result = compute_thing(input_data)
debug(result)
# prints: [my_endpoint] : result = {'sum': 42, 'rows': [...]}
Variable name is inferred from the AST of the line that called debug(). If
you pass a literal (string, number, raw dict), it just prints the value.
Multiple args
Pass several values in one call — each is labeled separately:
debug(user_id, request.body, response.status_code)
# [endpoint_name] : user_id = 42
# [endpoint_name] : request.body = {...}
# [endpoint_name] : response.status_code = 200
Calling from inside a class
swarm_debug introspects the caller's frame, so methods on a class print
under ClassName.method instead of just method:
class JobService:
def fetch(self, query):
debug(query)
# [JobService.fetch] : query = "machine learning engineer"
Indentation also scales with the call's lexical depth so nested-loop debugging stays readable:
for batch in batches:
debug(batch.id) # |\t[fn] : batch.id = 1
for item in batch.items:
debug(item) # |\t |-- [fn] : item = {...}
Error highlighting
Pass an Exception (or a name containing err/error) and swarm_debug
flips the color to red + prefixes a ❌ emoji so the error stands out in the
Terminal pane:
try:
risky_thing()
except Exception as err:
debug(err)
# ❌ [endpoint_name] : err = ValueError("bad input")
Long values are truncated
By default swarm_debug cuts values longer than 3000 characters with a
…\n… separator in the middle. Override per-call:
debug(huge_payload, override_max_chars=True)
Modes (custom log levels)
debug accepts a mode kwarg that maps to a log channel. Valid values are
'all' (always shown), 'debug' (the default), and 'test' (high
priority). Anything else raises.
debug(payload, mode='all')
debug(flaky_result, mode='test')
More tools (pretty-print, tables, diffs, timing)
debug(my_dict) # structured values pretty-print by default
debug(my_dict, pretty=False) # force flat single-line output
debug(sql_query, lang="sql") # syntax-highlight a string (sql, json, html, ...)
debug(x, y, z) # 2+ data args auto-render as a Name|Type|Value table
debug(x, y, z, table=False) # force per-line instead
debug("a", "b", sep=", ") # join args into one line, like print(sep=...)
debug("about to retry", error=True) # force red error styling on a non-exception
debug.diff(old_value, new_value) # unified diff of two values
with debug.time("fetch users"): # times the block, prints the duration
rows = fetch_users()
Visibility (why output might not show)
Output is gated per-file: only files toggled ON print. OpenSwarm re-toggles
every file ON at each backend boot, and new code needs a backend restart to
load anyway (no auto-reload), so in practice your debug() lines are always
visible after the restart that loads them. If you ever need to manage this
yourself, the CLI lives in the workspace venv:
.venv/bin/swarm-debug status # what's toggled where
.venv/bin/swarm-debug toggle on --all # everything visible (run from the workspace root)
When to use print() instead
Don't. swarm_debug.debug() IS the answer for backend logging. Reasons:
print()doesn't show the variable name or class/function context.print()doesn't truncate huge payloads.print()doesn't color-code errors.print()writes raw stdout, which is harder to scan in the Terminal pane when the agent is also running and producing output.
print() is fine for human-only one-off scripts the agent runs via Bash —
not for endpoint code.
What NOT to do
- Don't call
debug()inside a hot loop without a guard — every call reads the source file to extract variable names, which gets expensive at 10k+ iterations. - Don't pass functions / class objects expecting useful output —
you'll get something like
<function compute at 0x10a...>. Call the function or stringify intentionally. - Don't rely on
debug()for production logging. It's a development aid. For structured server logs that survive past the App Builder session, useloggingfrom the standard library.
How it lands in the Terminal tab
Every line debug() prints goes to stdout/stderr of your backend
subprocess. The App Builder's runtime captures both streams, prefixes each
line with [BACKEND], and streams them via WebSocket into the Terminal
pane in real time. Frontend console.log calls in the running app land in
the same Terminal pane prefixed [FRONTEND]. Use this to correlate cause
and effect across the two halves of your stack.
The same stream is tee'd to .openswarm/terminal.log at the workspace
root (reset on every app start), so you can read your own debug() output
directly: tail -100 .openswarm/terminal.log.
Quick reference
| Want to… | Do this |
|---|---|
| Log a value with auto-inferred name | debug(value) |
| Log several values in one call | debug(a, b, c) |
| Log an exception with red coloring | debug(err) (variable name must contain "err" or "error", or pass an Exception instance) |
| Avoid truncation | debug(value, override_max_chars=True) |
| Always-shown log channel | debug(value, mode='all') |
| Diff two values | debug.diff(old, new) |
| Time a block | with debug.time("label"): ... |
| Flat instead of pretty-printed | debug(value, pretty=False) |
| Syntax-highlight a string | debug(query, lang="sql") |