Infrastructure

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

app.py
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
shell
# 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

shell
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.

ProfileLevelFormatColourTimestamps
devDEBUGtextonon
prodINFOJSONoffon (ISO-8601 UTC)
verboseTRACEtextonon
quietWARNINGtextautooff

Levels

deepcrew-ai uses the standard library level names — no custom levels — plus TRACE (5), registered the same way structlog and loguru add it.

LevelValueUsed for
CRITICAL50process cannot continue
ERROR40an operation failed (max_turns exhausted, orchestrator crash)
WARNING30recoverable / degraded (retry, model fallback, spawn-depth ceiling)
INFO20lifecycle milestones (agent start/done, routing decision, loop iteration, APEX)
DEBUG10per-turn / per-tool detail
TRACE5token-level deltas

Environment variables

DEEPCREW_LOG_PROFILE
dev | prod | verbose | quiet
Preset bundle.
DEEPCREW_LOG_LEVEL
TRACE … CRITICAL
Threshold by name.
DEEPCREW_LOG_FORMAT
text | json
Renderer.
DEEPCREW_LOG_TIME
1 | 0
Show timestamps.
DEEPCREW_LOG_COLOR
1 | 0 | auto
Colour override; also respects NO_COLOR / FORCE_COLOR.

Sample output

text — dev
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"]
json — prod
{"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.

my_tools.py
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)
Design standard for custom components: prefer 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

level
int | str = "INFO"
Threshold, by name or number.
profile
"dev" | "prod" | "verbose" | "quiet" | None
Preset bundle.
format
"text" | "json" = "text"
Renderer.
color
bool | None = None
None auto-detects the TTY (honouring NO_COLOR / FORCE_COLOR).
timestamps
bool = True
Include a time column / ts key.
stream
TextIO | None = None
Defaults to sys.stderr.
propagate
bool = False
Whether the 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).

shell
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 no DEEPCREW_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 default False.
  • No colour in CI / pipes? Colour auto-disables off a TTY. Force it with DEEPCREW_LOG_COLOR=1 or FORCE_COLOR=1.
  • This is not OpenTelemetry. For spans/traces use ObservabilityConfig — see the Observability guide. The two are complementary.
Want an AI coding agent to wire this up for you? Click Copy prompt above and paste it into Claude, ChatGPT, or your IDE's AI assistant.