e-volv
Docs menu

Python

evolve-logs is the SDK for Python 3.10 or later: FastAPI and Starlette services, Django, Celery or RQ workers, and LangChain or LangGraph agents. One dependency (httpx). Trace context lives in contextvars, so it follows await and spawned tasks.

Install and initialise

shell
pip install evolve-logs                 # or: uv add evolve-logs
pip install "evolve-logs[langchain]"    # adds the LangChain / LangGraph handler
python
import os
from evolve_logs import init, log, span, trace, patch_httpx

init(
    key=os.environ["EVOLVE_LOGS_KEY"],   # the project's ingest key
    url="https://api.e-volv.io/api/public/v1/logs",
    service="orders-worker",
    environment=os.environ.get("ENV"),
    release=os.environ.get("GIT_SHA"),
)
patch_httpx()   # inject traceparent into httpx calls made inside a trace

All init() arguments are keyword-only: key, url, service, environment, release, redact_keys, sample_rate (default 1.0), capture_excepthook (default True) and client to supply your own instance. Without a key and URL the client is a no-op and warns once; it never raises into your code.

Logs

python
log.info("order created", {"orderId": "o_1", "total": 42.5})
log.warn("retrying payment", {"orderId": "o_1", "attempt": 2})
log.error("payment failed", {"orderId": "o_1"})

Errors

Pass the exception object. The SDK sends exception.type, exception.message and the formatted traceback as exception.stack, which makes the event an error occurrence with a stack on the group page. sys.excepthook capture is on by default, so an unhandled crash is reported before the process exits.

python
try:
    charge(order)
except Exception as err:
    log.exception(err, {"orderId": order.id})
    raise

Traces and spans

python
import httpx

with trace():                                   # one trace_id for everything inside
    with span("db.query", {"table": "orders"}):
        db.query("...")                         # an exception ends the span as failed
    httpx.get("https://internal/inventory")     # traceparent injected

trace() starts a root trace; span(name, attrs) is a context manager that records the span end, failed if the body raised. patch_httpx() and patch_requests() inject traceparent into outbound calls inside a trace without overwriting an existing header. traceparent() returns the current value.

Inbound requests and queue hops

python
from evolve_logs import run_with_traceparent, span, traceparent

# producer
queue.enqueue(work, payload, trace=traceparent())

# consumer: a new hop of the producer's trace
with run_with_traceparent(job.trace), span("queue.work", {"queue": "work"}):
    handle(job.payload)

An absent or malformed header starts a fresh trace, so a producer that sends nothing still yields a trace of its own.

Frameworks

Standard logging

python
import logging
from evolve_logs import LogHandler

logging.getLogger("app").addHandler(LogHandler())
logging.getLogger("app").error("payment failed", extra={"orderId": "o_1"}, exc_info=True)

Levels map DEBUG 5, INFO 9, WARNING 13, ERROR 17, CRITICAL 21; extra becomes attributes and exc_info becomes exception.*.

FastAPI and Starlette

python
from evolve_logs.integrations.fastapi import EvolveLogsMiddleware

app.add_middleware(EvolveLogsMiddleware)   # one root span per request

Pure ASGI, so streaming responses are safe. The middleware continues an inbound trace when the request carries a traceparent (Evolve’s own backend sends x-evolve-traceparent to its Python service) and starts a fresh one otherwise.

LangChain and LangGraph

python
from evolve_logs.integrations.langchain import EvolveLogsCallbackHandler

result = graph.invoke(
    {"question": "..."},
    config={"callbacks": [EvolveLogsCallbackHandler()]},
)

One span per run, nested by parent_run_id, all in one trace, so a graph node’s tool and model calls appear inside the node on the trace graph. Errors end the span as failed with exception.* attributes. The Node SDK's LangChain.js handler and Vercel AI SDK middleware emit the same span names, so a JavaScript agent and a Python agent read alike.

CallbackSpan name
on_chain_start with a langgraph_nodegraph.<node>
on_chain_start otherwisechain.<name>
on_tool_starttool.<name>
on_llm_start, on_chat_model_startllm.<model>

Django

Add a middleware that reads the request’s traceparent header into run_with_traceparent and wraps get_response in a span; attach LogHandler to the django logger for request errors.

Delivery

Batches flush at 200 events, 2 seconds or 512 KB, gzipped. A 429 is retried with exponential backoff up to three times; a 413 halves the batch. When the pending buffer exceeds twice the batch size the oldest events are dropped and counted on client.dropped. atexit flushes what is pending; call get_client().flush() before os._exit.

Package reference: packages/logs-py. Back to SDK overview.