Recount
Python

Database spans

Trace MongoDB queries automatically with wrap_collection, or record SQL and NoSQL queries as database spans in Python.

MongoDB - wrap_collection

Wrap a synchronous PyMongo collection once and every operation is traced automatically. Import from recount.integrations.pymongo:

from recount.integrations.pymongo import wrap_collection

users_col = wrap_collection(db["users"])

Then use it exactly as you would a normal collection:

async def trace_fn():
    # db span recorded automatically - filter, collection, duration, found/not
    user = users_col.find_one({"accountId": account_id})

    results = list(
        users_col.find({"status": "active", "plan": "enterprise"})
    )


await recount.start_trace("Process application", trace_fn)

Each span captures the operation name, collection, filter/pipeline, and a result summary (found, count, insertedId, etc.). Document contents are never recorded - only metadata about the query result.

Naming spans

By default the span name is collection.operation (e.g. users.find_one). wrap_collection methods are synchronous (PyMongo is a sync driver), so recount.named() - which awaits its callback - needs an async wrapper around the call:

async def load_account():
    return users_col.find_one({"accountId": account_id})


user = await recount.named("Load account", load_account)
# -> db span named 'Load account' instead of 'users.find_one'

Cursor iteration

find() and aggregate() return a wrapped cursor - however you drain it, one span is recorded once iteration finishes, with count set to however many documents were read. Standard iteration, list(), .next(), and the with context manager are all supported:

# iterate
for user in users_col.find({"status": "active"}):
    process(user)

# list()
results = list(users_col.find({"status": "active"}))

# .next()
cursor = users_col.find({"status": "active"})
while True:
    try:
        user = cursor.next()
        process(user)
    except StopIteration:
        break

# context manager
with users_col.find({"status": "active"}) as cursor:
    for user in cursor:
        process(user)

close() finalizes the span too, for cases where you stop draining early. rewind() resets the count so a second drain records its own span.

Because wrap_collection wraps a synchronous driver, this only supports PyMongo's Collection - if your app uses Motor (the async MongoDB driver), wrap query call sites manually with recount.add_span("db", ...) instead.

Redacting filter values

If your filters contain PII, pass redact:

customers_col = wrap_collection(
    db["customers"],
    redact=["name", "billing.accountName", "billing.accountNumber"],
)

Redact paths are applied to the span's input (the filter object). See Redaction for syntax.

Supported operations

find_one, find (cursor - see Cursor iteration), insert_one, insert_many, update_one, update_many, delete_one, delete_many, replace_one, find_one_and_update, find_one_and_delete, aggregate (cursor - see Cursor iteration), count_documents

Install

pip install "recount-sdk[pymongo]"

Manual spans

For SQL databases or when you need full control, use add_span with type_="db" directly.

SQL

recount.add_span("db", "Fetch users", {
    "metadata": {"table": "users", "queryType": "sql"},
    "input": {"query": "SELECT * FROM users WHERE active = $1", "params": [True]},
    "output": {"count": 42},
    "durationMs": 12,
})

NoSQL

recount.add_span("db", "Fetch users", {
    "metadata": {"collection": "users", "queryType": "nosql"},
    "input": {
        "filter": {"active": True},
        "projection": {"name": 1, "email": 1},
        "sort": {"createdAt": -1},
    },
    "output": {"count": 42},
    "durationMs": 8,
})

Metadata fields

FieldTypeDescription
queryType"sql" | "nosql"Controls how the query is displayed in the dashboard
tablestrTable name (SQL), shown in the span header
collectionstrCollection name (NoSQL), shown in the span header

Output convention

Pass summary stats (counts, affected rows, timings) rather than raw documents.

Param redaction

Pass redactParams with the exact parameter values to redact from input.params:

recount.add_span("db", "User lookup", {
    "metadata": {"table": "users", "queryType": "sql"},
    "input": {"query": "SELECT * FROM users WHERE id = $1", "params": [user_id]},
    "output": {"rows": 1},
    "redactParams": [user_id],
})

On this page