Skip to main content

Smello logo

Smello

Capture outgoing and incoming HTTP requests, pytest results, Python logs, and unhandled exceptions in a local web dashboard.

Like Mailpit, but for your entire debug output.

Setup

Install the client SDK and the server:

pip install smello smello-server

Start the server:

smello-server

Run your code with Smello:

smello run my_app.py
smello run pytest tests/
smello run uvicorn app:app

That's it. Smello activates before your code runs and captures outgoing traffic, pytest results, unhandled exceptions, and optional Python log records. No code changes needed.

Subprocess instrumentation propagates automatically through PYTHONPATH, so smello run gunicorn app:app also captures traffic from worker processes.

CLI flags map 1:1 to the SMELLO_* env vars: --server, --capture-host, --ignore-host, --capture-all / --no-capture-all, --redact-header, --redact-query-param, --capture-tests / --no-capture-tests, --capture-logs, --log-level, --app, --session. The action-only --clear flag does not set an environment variable.

Clear all events with smello clear, or begin a command with an empty timeline:

smello run --clear my_app.py

Query captured events without opening the dashboard:

smello query
smello query --session debug-payment --type http --status 500 --ancestors
smello query --session debug-payment --after-seq 1642
smello query --session debug-payment --stats
smello query 5ae54ca2
smello query 'http://localhost:5110/#5ae54ca2-45a7-45a6-a6cd-533569fc8db7'
smello meta

List queries use compact text by default and indent events by their runtime hierarchy, adding an app/session column when the results span more than one run. Pass --format json or --format jsonl for shell pipelines. Pass a displayed eight-character ID, full UUID, or dashboard URL to print the complete event as formatted JSON. smello meta lists every app, session, host, event type, and method the server has captured.

Tag events with --app and --session to isolate a debugging run:

smello run --app myapp --session debug-payment python scripts/checkout.py

Using smello.init() instead

If you prefer to activate Smello from within your code (e.g., for programmatic configuration or projects with a custom sitecustomize.py):

import smello
smello.init()  # activates only when SMELLO_URL is set

Framework middleware

To capture incoming requests, add the Smello middleware to your web framework:

FastAPI:

from smello.integrations.fastapi import SmelloMiddleware
from fastapi import FastAPI

app = FastAPI()
app.add_middleware(SmelloMiddleware, ignore_paths=["/health"])

Django:

# settings.py
MIDDLEWARE = [
    "smello.integrations.django.SmelloMiddleware",
    ...
]
SMELLO_IGNORE_PATHS = ["/health/", "/admin/"]

Then run with smello run:

smello run uvicorn app:app        # FastAPI
smello run manage.py runserver    # Django

The middleware captures method, path, status code, duration, route pattern, client IP, and request/response bodies. Unhandled exceptions are captured with full tracebacks. When Smello is inactive (no server URL configured), the middleware passes requests through without capturing anything.

Google Cloud libraries

Many Google Cloud Python libraries — BigQuery, Firestore, Pub/Sub, Analytics Data API (GA4), Vertex AI, Speech-to-Text, Vision, Translation, and others — use gRPC under the hood. Smello captures these calls automatically:

smello run my_bigquery_script.py

Any library that calls grpc.secure_channel() or grpc.insecure_channel() is automatically captured.

What Smello captures

Outgoing HTTP requests — method, URL, headers, body, response status/headers/body, duration, and library used (requests, httpx, aiohttp, grpc, or botocore).

Failed outgoing calls retain exception details. If a library raises after receiving a response, Smello stores both the response status and headers and the exception.

Incoming HTTP requests (via FastAPI or Django middleware) — method, path, route pattern, status code, duration, client IP, request/response headers and bodies, plus exception tracebacks if a handler raises.

Pytest executions (enabled by default): Smello supports pytest 7 and later and records one event per test function or method invocation. Parametrized cases remain separate. Install the integration with pip install "smello[pytest]" if your project doesn't already include pytest.

Each event includes the outcome, fixture names, source location, total and per-phase timings, and failure details. It also carries runtime trace and span IDs plus stable directory, file, class, and unparameterized case memberships. Outgoing Requests, HTTPX, aiohttp, botocore, and gRPC calls inherit those memberships and appear as child operations. The dashboard always shows runtime parenthood and can add one virtual grouping by an available pytest level or remote host without changing event deep links. Virtual groups merge only across adjacent siblings, which preserves record order. Timeline filters retain matching records' runtime ancestors, so filtered children remain connected to their runtime context. The traceback generated by pytest may include repr() output for parameter, fixture, or local variable values.

See Debug pytest tests with Smello for a runnable example and dashboard walkthrough.

Unhandled exceptions (enabled by default) — exception type, message, full traceback, and stack frames with source context.

Log records (opt-in via capture_logs=True) — level, logger name, message, source location, and extra attributes.

Smello redacts sensitive headers (Authorization, X-Api-Key) by default and optionally redacts query string parameters.

Configuration

smello.init(
    server_url="http://localhost:5110",       # where to send captured data

    # HTTP capture
    capture_hosts=["api.stripe.com"],         # only capture these hosts
    capture_all=True,                          # capture everything (default)
    ignore_hosts=["localhost"],               # skip these hosts
    redact_headers=["Authorization"],         # replace header values with [REDACTED]
    redact_query_params=["api_key", "token"], # replace query param values with [REDACTED]

    # Tests, logs, and exceptions
    capture_tests=True,                        # capture pytest executions (default)
    capture_exceptions=True,                   # capture unhandled exceptions (default)
    capture_logs=False,                        # capture log records (opt-in)
    log_level=30,                              # minimum log level to capture (WARNING)
    ignore_loggers=["uvicorn.access"],         # suppress noisy framework loggers

    # Tagging
    app="myapp",                               # tag events with an application name
    session="debug-payment",                   # tag events with a session ID
)

All parameters fall back to SMELLO_* environment variables when not passed explicitly:

Parameter Env variable Default
server_url SMELLO_URL None (inactive)
capture_all SMELLO_CAPTURE_ALL True
capture_hosts SMELLO_CAPTURE_HOSTS []
ignore_hosts SMELLO_IGNORE_HOSTS []
redact_headers SMELLO_REDACT_HEADERS ["Authorization", "X-Api-Key"]
redact_query_params SMELLO_REDACT_QUERY_PARAMS []
capture_tests SMELLO_CAPTURE_TESTS True
capture_exceptions SMELLO_CAPTURE_EXCEPTIONS True
capture_logs SMELLO_CAPTURE_LOGS False
log_level SMELLO_LOG_LEVEL 30 (WARNING)
ignore_loggers SMELLO_IGNORE_LOGGERS []
app SMELLO_APP ""
session SMELLO_SESSION ""

The server URL is the activation signal — init() does nothing unless server_url is passed or SMELLO_URL is set. Boolean env vars accept true/1/yes and false/0/no (case-insensitive). List env vars are comma-separated.

Smello Server deletes events older than seven days when it starts and every hour after that. Set SMELLO_RETENTION_DAYS on the server process to change the retention period, or set it to 0 to keep events indefinitely. The server rejects negative or malformed values at startup.

Calls to the OpenAI, Anthropic, and Gemini APIs render as a readable conversation in the dashboard, with the system prompt, tool calls, and token usage broken out. The raw JSON stays one tab away.

Supported libraries

  • requests — patches Session.send()
  • httpx — patches Client.send() and AsyncClient.send()
  • aiohttp — injects TraceConfig lifecycle hooks to capture async HTTP traffic
  • grpc — patches insecure_channel() and secure_channel() to intercept unary-unary calls
  • botocore — patches URLLib3Session.send() to capture boto3 / AWS SDK traffic
  • pytest: loads a plugin that captures each test function or method invocation

Requires

Links

Metadata

Release files for smello 0.18.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for smello 0.18.0
File Size Uploaded
smello-0.18.0.tar.gz 74.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for smello 0.18.0
File Interpreter ABI Platform
smello-0.18.0-py3-none-any.whl Python 3 none any Details

Total release size: 128.7 kB

Release files / smello-0.18.0.tar.gz

Download URL smello-0.18.0.tar.gz
Size 74.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f809dc687e3c095e11da60af8465b2aecb2ef16ae3bffc14568f66d19cbd908e
BLAKE2b-256 checksum
How to use checksums
4f2acd83a22e7468afa54eacfad2f4428b87335975a8c0457afc1374ba89da25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Release files / smello-0.18.0-py3-none-any.whl

Download URL smello-0.18.0-py3-none-any.whl
Size 53.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45e66cba2ef8967f8a831dca8f15f45d26a53dee9b454c90e2951d2240defd39
BLAKE2b-256 checksum
How to use checksums
0aa7e7d5bcd08570121a69a5eb34d34b8bd7ba63d3570d999c2be1bf2cadf541
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.18.0 This release

2 release files

0.14.1

2 release files

0.14.0

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.11.0

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page