Group by tenant
Multi-tenant app, shared codebase. An alert fires. Whose customer just tripped it?
If your tenants are services in the same product, forum mode is now the recommended path: one bot, one supergroup, one topic per tenant. See the Forum mode guide. The recipe below remains valid when topics aren’t an option.
The problem
You can bolt a tenant_id argument onto every notify() and every log.warning() call site. You’ll miss some. The ones you miss are always the ones that fire in production. The alternative is one middleware that scopes the request, and every notify, @watch_slow, and crash report in that scope carries the tenant automatically.
The recipe
# middleware.py — FastAPI, but the pattern works anywhere
import snitchbot
from fastapi import FastAPI, Request
snitchbot.init("billing")
app = FastAPI()
@app.middleware("http")
async def scope_tenant(request: Request, call_next):
with snitchbot.request_context(
trace_id=request.headers.get("x-request-id"),
tenant_id=request.state.tenant.id,
region=request.state.tenant.region,
):
return await call_next(request)
What you see
🟠 notify · billing · 156afe
Invoice generation failed
Details
time 17:02:11 UTC
pid 42
caller invoices.py:88 in generate()
Context
trace_id req-abc-123
tenant_id acme-corp
region eu-west-1
Notes
request_contextrides oncontextvars, so it crossesawaitandasyncio.create_task— but notthreading.Thread. For thread pools, wrap withcontextvars.copy_context().run(...).- Nested contexts merge. Inner
extrasoverride outer keys;trace_idinherits from the enclosing block if omitted. - Works identically for Flask (
@app.before_request) and Litestar (ASGI middleware). Seerequest_context()for the full signature.