Logging Guide

deepretro uses structlog for structured, leveled logging. Every module creates a logger at the top of the file and emits log events with key-value context that is machine-parseable and human-readable.

Quick start

No configuration is required. By default, structlog prints colored log lines to stdout at all levels.

To control the log level or output format, call deepretro.logging.configure_logging() once at your application entry point:

from deepretro.logging import configure_logging

# Show only warnings and above
configure_logging(level="WARNING")

# Silence all deepretro logging
configure_logging(level="CRITICAL")

# Verbose JSON output for production / log aggregation
configure_logging(level="DEBUG", json_output=True)

If you already call structlog.configure() in your application (e.g. in src/main.py), that configuration applies automatically — you do not need configure_logging.

Minimal application example

Use configure_logging() once near your entry point, then create module loggers with structlog.get_logger():

import structlog

from deepretro.logging import configure_logging

configure_logging(level="INFO")

logger = structlog.get_logger(__name__)
logger.info("Starting retrosynthesis", molecule="CCO")

For production log pipelines, switch the same helper to JSON output:

configure_logging(level="DEBUG", json_output=True)

How to log in a new module

Add two lines at the top of your module:

import structlog

logger = structlog.get_logger()

Then use the standard log levels:

logger.debug("Low-level detail", key="value")
logger.info("Normal operation", molecule=smiles)
logger.warning("Recoverable issue", attempt=attempt)
logger.error("Failure", smiles=smiles, error=str(exc))

Prefer structured key-value pairs over f-string interpolation so that downstream consumers (ELK, Datadog, etc.) can parse fields automatically.

Log level conventions

Level

When to use

DEBUG

Detailed diagnostics: prompt text, raw response bodies, intermediate computation results. Hidden by default in production.

INFO

Normal milestones: “Starting retrosynthesis”, “Pipeline succeeded”, “Validity check complete”.

WARNING

Recoverable problems: retries, fallbacks, rejected pathways, missing optional config.

ERROR

Failures that prevent the current operation from completing: invalid SMILES, JSON parse errors, all API attempts exhausted.

Context propagation with bound_contextvars

Use structlog.contextvars.bound_contextvars to attach context (e.g. a job ID) that will appear in every log line emitted within the block — even from deeply nested functions:

from structlog.contextvars import bound_contextvars

def run_retrosynthesis(molecule: str, job_id: str):
    with bound_contextvars(job_id=job_id, molecule=molecule):
        logger.info("Starting retrosynthesis")
        # All downstream log calls automatically include job_id + molecule
        result = call_llm(molecule)
        return result

This replaces the previous contextvars.ContextVar / job_context pattern. There is no need to pass loggers through function arguments.

API reference