Skip to content

Latest commit

 

History

188 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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. HTTP traffic in both directions, pytest results, log records, and crash tracebacks all appear in one timeline. Prefix your command with smello run; no code changes are needed.

Why port 5110? Read it as 5-1-1-0S-L-L-Osmello.

Smello dashboard showing a pytest hierarchy with a nested HTTP request

Quick start

1. Install and start the server

pip install smello smello-server
smello-server

Or run with Docker:

docker run -p 127.0.0.1:5110:5110 ghcr.io/smelloscope/smello

2. Run your code with Smello

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

That's it. Outgoing HTTP requests, pytest results, unhandled exceptions, and optional log records are captured automatically. Subprocess instrumentation propagates automatically, so smello run gunicorn app:app also captures traffic from worker processes.

Browse captured events at http://localhost:5110.

Debugging sessions

Tag events with --app and --session to isolate a debugging run without clearing existing data:

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

Then filter the dashboard or API to see only those events:

curl -s 'http://localhost:5110/api/events?app=myapp&session=debug-payment'

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 and run with smello run:

FastAPI:

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

app = FastAPI()
app.add_middleware(SmelloMiddleware, ignore_paths=["/health"])
smello run uvicorn app:app

Django:

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

Every request your server handles appears in the dashboard with method, path, status code, duration, route pattern, and client IP. Unhandled exceptions are captured with full tracebacks.

AI agent skills

Smello ships with Agent Skills for Claude Code, Cursor, GitHub Copilot, and 20+ other AI coding tools.

npx skills add smelloscope/smello
Skill Install individually Description
/smello-setup npx skills add smelloscope/smello --skill smello-setup Explores your codebase and proposes a plan to integrate Smello (package install, entrypoint placement, Docker Compose, env vars). Does not make changes without approval.
/smello npx skills add smelloscope/smello --skill smello Queries the Smello API to inspect captured events — HTTP traffic, log records, and exceptions. Also activates automatically when you ask about debugging.

What Smello captures

Outgoing HTTP requests

For every outgoing HTTP and gRPC call:

  • Method, URL, headers, and body
  • Response status code, headers, and body
  • Duration in milliseconds
  • Library used (requests, httpx, aiohttp, grpc, or botocore)

gRPC calls are displayed with a grpc:// URL scheme. Protobuf bodies are automatically serialized to JSON. Sensitive headers (Authorization, X-Api-Key) are redacted by default.

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.

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.

Incoming HTTP requests

With the FastAPI or Django middleware, Smello captures every request your server handles:

  • Method, path, full URL, and route pattern
  • Request and response headers and bodies
  • Status code, duration, and client IP
  • Exception type and traceback (if the handler raises)

Pytest tests

The bundled plugin supports pytest 7 and later. Install the pytest extra if your project doesn't already include pytest:

pip install "smello[pytest]"

Prefix your pytest command with smello run to capture one event for every test function or method invocation. Parametrized cases produce separate events.

  • Outcome: passed, failed, error, skipped, xfailed, or xpassed
  • Total duration and setup, call, and teardown timings
  • Fixture names used by the test
  • Failure details for assertion, setup, or teardown errors, including pytest's formatted traceback
  • Test node ID, source location, run ID, and xdist worker ID

Each test invocation also carries runtime trace and span IDs plus stable references to its directory, file, class, and unparameterized test case. Outgoing Requests, HTTPX, aiohttp, botocore, and gRPC calls inherit the active test membership and appear as child operations. Runtime parenthood is always visible. The dashboard's Group selector can add one available pytest level or remote-host grouping 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.

Smello stores fixture names as structured data. The traceback generated by pytest may also include repr() output for parameter, fixture, or local variable values.

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

Logs

When capture_logs=True, Smello hooks into Python's logging module and captures log records at or above the configured level:

  • Log level, logger name, and formatted message
  • Source file, line number, and function name
  • Extra attributes attached to the record

Exceptions

Unhandled exceptions are captured automatically (enabled by default):

  • Exception type, message, and module
  • Full formatted traceback
  • Stack frames with file, line, function, and source context

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.

Query and API

Smello provides a JSON API for exploring captured events from the command line. The smello query command wraps the API's common filters and output formats:

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 query --type log --format jsonl
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 an eight-character ID from text output, a full UUID, or a dashboard URL to print the complete event as formatted JSON. smello meta lists every app, session, host, event type, and method on the server, so you can see what is worth filtering for. Use --server or SMELLO_URL to query a different server.

List events

# All captured events (unified timeline) — returns {epoch, max_seq, events}
curl -s http://localhost:5110/api/events | python -m json.tool

# Filter by event type
curl -s 'http://localhost:5110/api/events?event_type=log'

# Filter by method, host, or status (HTTP events)
curl -s 'http://localhost:5110/api/events?method=POST&host=api.stripe.com'

# Filter by app or session
curl -s 'http://localhost:5110/api/events?app=myapp&session=debug-payment'

# Full-text search across summaries and event data
curl -s 'http://localhost:5110/api/events?search=ValueError'

# Limit results (default: 200, max: 10000)
curl -s 'http://localhost:5110/api/events?limit=10'

# Only what arrived after a previous call's max_seq
curl -s 'http://localhost:5110/api/events?after_seq=1642'

Count events

Aggregate counts by event type, test status, and HTTP status class — the fast way to learn how a run of thousands of events went.

curl -s 'http://localhost:5110/api/events/stats?session=debug-payment' | python -m json.tool

Get event details

Returns the full event data, including headers and bodies for HTTP, test outcomes and timings, traceback frames for exceptions, and message metadata for logs.

curl -s http://localhost:5110/api/events/{id} | python -m json.tool

Clear all events

smello clear

# Or start one command with an empty timeline
smello run --clear my_app.py

Python version support

Package Python
smello (client SDK) >= 3.10
smello-server >= 3.14

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

AWS libraries (boto3)

boto3 uses botocore, which calls urllib3 directly, bypassing requests and httpx. Smello patches botocore's HTTP session to capture all AWS API calls. Just run your script with smello run:

smello run my_aws_script.py

AWS calls appear at http://localhost:5110 — XML responses show as a collapsible tree, just like JSON.

Google Cloud libraries

Many Google Cloud Python libraries use gRPC under the hood. Smello automatically captures these calls with zero additional configuration:

  • Google BigQuery (google-cloud-bigquery)
  • Google Cloud Firestore (google-cloud-firestore)
  • Google Cloud Pub/Sub (google-cloud-pubsub)
  • Google Analytics Data API (google-analytics-data) — GA4 reporting
  • Google Cloud Vertex AI (google-cloud-aiplatform)
  • Google Cloud Speech-to-Text (google-cloud-speech)
  • Google Cloud Vision (google-cloud-vision)
  • Google Cloud Translation (google-cloud-translate)
  • Google Cloud Secret Manager (google-cloud-secret-manager)
  • Google Cloud Spanner (google-cloud-spanner)
  • Google Cloud Bigtable (google-cloud-bigtable)

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

Development

Requires uv, Node.js 22+, and just.

git clone https://github.com/smelloscope/smello.git
cd smello
uv sync

# Terminal 1: API server with auto-reload (http://localhost:5110)
just server

# Terminal 2: Frontend dev server (http://localhost:5111, proxies /api to :5110)
just frontend-install
just frontend-dev

# Terminal 3: Run an example
uv run python examples/python/basic_requests.py

# Or exercise runtime trees with optional remote-host grouping
uv run smello run --capture-logs --log-level INFO -- \
  uvicorn examples.python.hierarchy_demo:app --port 8001

Run just to see all available recipes.

Worktree development

Worktrunk creates an isolated worktree for each branch. Install its shell integration once, then approve this repository's setup hook after reviewing it:

wt config shell install
wt config approvals add

Create a worktree with wt switch --create feature-name. It is created beside this checkout, copies your .env, and prepares Python and frontend dependencies. Use wt switch feature-name to return to it, wt list to see worktrees, and wt remove feature-name after the branch has been merged.

Architecture

smello run my_app.py ──→ Smello Server ──→ Web Dashboard
                         (FastAPI+SQLite)   (localhost:5110)
  • smello (client SDK): Monkey-patches requests, httpx, aiohttp, grpc, and botocore to capture outgoing traffic. Includes FastAPI and Django middleware for incoming request capture. Hooks sys.excepthook for exceptions and logging.Logger.callHandlers for log records. Sends everything to the server in a background thread.
  • smello-server: FastAPI app with SQLite. Receives captured events and serves a JSON API plus a React web dashboard with a unified timeline.

Project structure

smello/
├── server/              # smello-server (FastAPI + Tortoise ORM + SQLite)
│   └── tests/
├── frontend/            # React SPA (MUI + TanStack Query + jotai)
├── clients/python/      # smello client SDK
│   └── tests/
├── tests/test_e2e/      # End-to-end tests
└── examples/python/

Changelog

Contact

Questions, feedback, or ideas? Reach out at roman@smello.io.

License

MIT

About

A developer tool that captures outgoing HTTP requests from your code and displays them in a local web dashboard

Topics

Resources

Security policy

Stars

78 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages