Logging
deepcrew-ai ships a general-purpose structured logging layer that is off by default — a bare import deepcrew attaches only a NullHandler and emits nothing, following the Python logging guidance for libraries. Turn it on with one call.
This is separate from Observability: ObservabilityConfig is OpenTelemetry spans; this is plain human/JSON logs. They are independent and compose. Logging also never touches the StreamEvent queue — the streaming view is unaffected.
Turn it on
from deepcrew import configure_logging
configure_logging() # the "dev" profile: colour, DEBUG, timestamps
configure_logging("prod") # JSON lines, INFO, no colour
configure_logging("verbose") # everything, incl. TRACE token-level detail
configure_logging(level="DEBUG", format="json") # ad-hoc knobs
# or entirely from the environment, no code change
DEEPCREW_LOG_PROFILE=dev python app.py
DEEPCREW_LOG_LEVEL=DEBUG DEEPCREW_LOG_FORMAT=json python app.py
When any DEEPCREW_LOG* variable is set, logging auto-configures on first use.
Colour rendering
pip install "deepcrew-ai[log]" # pulls in rich for nicer coloured output
configure_logging uses rich for coloured output when it is installed and colour is enabled; otherwise it falls back to a plain ANSI formatter with no extra dependency. Colour honours the NO_COLOR / FORCE_COLOR conventions and auto-disables off a TTY.
Profiles
A profile just presets four knobs; each is individually overridable, and DEEPCREW_LOG* env vars always win over both the profile and explicit arguments.
| Profile | Level | Format | Colour | Timestamps |
|---|---|---|---|---|
dev | DEBUG | text | on | on |
prod | INFO | JSON | off | on (ISO-8601 UTC) |
verbose | TRACE | text | on | on |
quiet | WARNING | text | auto | off |
Levels
deepcrew-ai uses the standard library level names — no custom levels — plus TRACE (5), registered the same way structlog and loguru add it.
| Level | Value | Used for |
|---|---|---|
CRITICAL | 50 | process cannot continue |
ERROR | 40 | an operation failed (max_turns exhausted, orchestrator crash) |
WARNING | 30 | recoverable / degraded (retry, model fallback, spawn-depth ceiling) |
INFO | 20 | lifecycle milestones (agent start/done, routing decision, loop iteration, APEX) |
DEBUG | 10 | per-turn / per-tool detail |
TRACE | 5 | token-level deltas |
Environment variables
NO_COLOR / FORCE_COLOR.Sample output
15:30:47 INFO deepcrew.runner agent start agent_id=researcher model=openai/gpt-4o max_turns=10
15:30:48 DEBUG deepcrew.runner turn start step=turn:1 turn=1
15:30:51 INFO deepcrew.orchestrator routing decision mode=parallel agents=["research", "analysis"]
{"ts":"2026-08-30T15:30:47.731Z","level":"INFO","logger":"deepcrew.runner","msg":"agent start","event":"agent_start","agent_id":"researcher","model":"openai/gpt-4o","max_turns":10}
Every lifecycle log carries an event field whose value matches the corresponding StreamEvent type, so logs and the event stream share a vocabulary.
get_logger — use it in your own components
deepcrew.get_logger(name) returns a structured logger namespaced under deepcrew.<name>, so a single configure_logging() call styles your logs and the library's identically. Extra keyword arguments become structured fields.
from deepcrew import get_logger, tool
log = get_logger("myapp.tools.pricing")
@tool
def lookup_price(sku: str, region: str) -> str:
"""Look up the current price for a SKU."""
log.info("price lookup", sku=sku, region=region)
...
bound = log.bind(request_id=req_id) # context on every subsequent call
bound.warning("slow path", latency_ms=812)
bound.trace("raw payload", body=payload)
get_logger over print / logging.getLogger; pass fields as keyword arguments rather than formatting them into the message; add an event="..." slug for lifecycle logs; keep INFO for milestones and push detail to DEBUG/TRACE.LoggingConfig reference
None auto-detects the TTY (honouring NO_COLOR / FORCE_COLOR).ts key.sys.stderr.deepcrew logger propagates to the root logger. Off by default to avoid double emission.CLI
The deepcrew CLI is quiet by default (warnings and errors only).
deepcrew run workflow.yaml -v # verbose profile
deepcrew run workflow.yaml --log-profile dev
deepcrew run workflow.yaml --log-level DEBUG --log-json
deepcrew run workflow.yaml -q # errors only
Common pitfalls
- Nothing logged? You have not called
configure_logging()and noDEEPCREW_LOG*env var is set — that is the intended silent default for a library. - Double log lines? Your app configured the root logger and you set
propagate=True. Leave it at the defaultFalse. - No colour in CI / pipes? Colour auto-disables off a TTY. Force it with
DEEPCREW_LOG_COLOR=1orFORCE_COLOR=1. - This is not OpenTelemetry. For spans/traces use
ObservabilityConfig— see the Observability guide. The two are complementary.