Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

vise

The Sillo development server, with Foreman built in.

pip install sillo-vise
vise serve

One command runs the project's application, replaces uvicorn's logging with something a person can read at a glance, and mounts the Foreman operations dashboard beside it.

  ▲ vise 0.1.0                        sillo 0.2.1 · python 3.12.13

  ➜  Local      http://127.0.0.1:8000
  ➜  Foreman    http://127.0.0.1:8000/__sillo/foreman
  ➜  App        app.main:app
  ➜  Panels     9 live · 5 waiting on what they watch

  reload on · recorder on · .vise

  09:14:22  GET    /api/v1/documents             200     38ms   12.4 kB
  09:14:22  POST   /api/v1/documents             201    204ms      840 B
  09:14:23  GET    /api/v1/insights              503    2.10s       84 B  slow

What it shows

Fourteen panels, in four groups, over hooks the framework already has.

Group Panels
Monitor Overview, Requests, Queries, Cache, Outgoing
Work Queues, Workers, Schedules
Diagnose Exceptions, Logs, Real-time, Mail
Tools Routes, Config

Rows open. Click a request and you get its headers both ways, the body it sent, the body that went back, and everything it caused — the queries, cache reads, log lines and mail — each of those clickable in turn. Click a query and you get the statement, its bindings, and the route that ran it. Escape closes.

A panel appears only when it can observe something. An application with no Redis has no Queues panel — not a Queues panel showing zeroes, and certainly not one showing sample data. vise panels says which are live and why the rest are not:

  panel       group     state
  overview    Monitor   live
  queries     Monitor   no database — sillo.record is not set up
  queues      Work      no queue backend — sillo.work is not set up
  logs        Diagnose  live
  routes      Tools     live

Why the logging is different

uvicorn writes this:

INFO:     127.0.0.1:54118 - "GET /api/v1/documents HTTP/1.1" 200 OK

Everything on that line is true and almost none of it is what somebody watching a development server wants. The level is always INFO, the protocol is always HTTP/1.1, the reason phrase restates the code, and the two facts that matter — how long it took and how much came back — are not there at all, because uvicorn's access logger runs inside its protocol implementation where neither is known.

Vise writes the access line from the recorder's own measurement instead. One thing measures a request and the log reads it, so the log and the dashboard can never disagree, and turning the recorder off turns the access log off with it.

uvicorn's logging is switched off rather than reconfigured — log_config=None, access_log=False, and its loggers silenced explicitly. Passing a custom log_config would leave uvicorn owning the configuration.

Three styles: vise (aligned and coloured), plain (aligned, no colour, for a file or CI) and json (one object per line for a collector).

Configuration

A .vise file in the project root. vise init writes one with every setting present, commented out, at its default.

[app]
target = "app.main:app"

[server]
host = "127.0.0.1"
port = 8000
reload = true

[dashboard]
path = "/__sillo/foreman"
access = "local"          # local | token | open

[recorder]
buffer = 2000
slow_request_ms = 500
redact = ["x-tenant-key"]
capture_bodies = true     # request and response bodies, capped at 16 kB

[logs]
style = "vise"
level = "info"

Precedence, lowest to highest: built-in defaults, .vise, VISE_* environment variables, command-line flags. Only flags actually typed override the file.

Commands

Command What it does
vise serve Run the application with Foreman alongside it
vise init Write a starter .vise
vise doctor Report what vise can observe here, and what it cannot
vise panels List the panels, and why any are missing
vise routes The route table, with what guards each route
vise bench Measure the per-request overhead, with the recorder on and off
vise version What is installed, and what each optional piece would add

vise doctor exits non-zero when any declared panel is missing, so it is usable in a check without parsing its output.


The four rules it ships with

These are from the Foreman specification, and each is expensive to retrofit and cheap to design in.

Bodies are captured by default. That is a deliberate choice for a loopback-only development server whose Requests panel exists to answer "what did the server actually send back" — and it is one line to turn off. They are capped at max_body_bytes and pass through the same redaction as everything else.

1. Redaction happens on capture, never on read. A watcher that stores a Cookie header and hides it in the interface is a credential store with a filter on top. The redactor runs inside the recorder's emit(), before an event reaches the store, and the store has no un-redacted path at all. The tests prove this by asserting the secret is not in the store — never that the API hides it.

2. Disabled means compiled out. recorder.enabled = false does not leave a branch on the hot path. Nothing is constructed, no middleware is wrapped around the application, and no watcher is attached. Off costs one boolean at boot.

3. The cost is a published number. See Overhead.

4. One recorder, many watchers. Fourteen panels with fourteen collection paths would be fourteen storage decisions and fourteen ways to leak. There is one Recorder, one bounded Store, and one watcher per concern.

How it is put together

sillo_vise/
  config/      .vise discovery, TOML parse, environment overlay
  recorder/    Event types, ring store, time series, redaction
  watchers/    One per concern; each proves it can observe before it attaches
  panels/      Fourteen panel builders over the store
  dashboard/   Raw ASGI middleware: assets, JSON API, SSE stream, access gate
  server/      uvicorn runner, reload factory, installation
  logs/        Banner, aligned formatter, access line
  cli/         serve, init, doctor, panels, routes, version

Two ordering decisions carry most of the weight, both because sillo builds its middleware chain inside-out — the last registered runs first:

  • The request recorder is registered first, so it runs innermost, closest to the application. What it measures is the application's own time.
  • The dashboard is registered last, so it runs outermost. Its own requests are answered before the recorder is reached, which is why the dashboard cannot appear in its own charts.

The dashboard is a middleware rather than a mounted router on purpose: a mounted router in sillo claims its whole prefix subtree and can shadow routes registered later during startup. An observability tool must not do that to the thing it is observing.

Memory is bounded by configuration, not by traffic

A ring per event kind capped at recorder.buffer, plus a per-minute time series capped at recorder.window_minutes. Percentiles come from a fixed reservoir per bucket, so they are estimates — said plainly here and in the interface, because a p95 presented as exact when it is sampled is worse than one presented as sampled.

Access

access = "local" is the default and admits only loopback peers, read from the transport's peer address and never from X-Forwarded-For. A header is a claim made by whoever is talking to you, and this is the check standing between a stranger and every request the application has served.

token compares a shared secret in constant time. open has a real use behind somebody else's authentication and is spelled out rather than reachable by accident. A refusal never says which mode refused.

Overhead

Rule three, kept. Measured with vise bench, which drives the ASGI application directly — a loopback TCP round trip is tens of microseconds of noise around an overhead measured in single ones.

2,000 requests to / on a small application, Python 3.12, Apple silicon:

p50 p95 p99 overhead
sillo alone 57.0µs 84.4µs 153.7µs
vise, recorder off 57.1µs 82.9µs 145.9µs +0.1µs
vise, recorder on 121.3µs 195.2µs 251.8µs +64.3µs

The middle row is the one that matters. +0.1µs is measurement noise, which is what "disabled means compiled out" has to mean: with the recorder off nothing is constructed and no middleware is in the chain, so there is no branch to skip.

The +64.3µs when it is on is mostly redaction and event construction, and that is the design rather than an accident — headers are decoded and redacted on the way in, because the alternative is a store holding credentials. Vise is a development server; run uvicorn app.main:app in production and the cost is zero because vise is not there.

Reproduce with vise bench -n 2000.

What is not built

The Foreman specification lists more than this ships, and the gaps are deliberate rather than pending.

  • Request replay and HAR export. Both send requests to somebody's live application, and neither is needed to see what that application is doing.
  • Queue actions — retry a job, pause a queue, forget a cache key. The dashboard's only writes act on the recorder itself: pause, resume, clear. A development tool changing application state by accident is a story nobody wants to be in.
  • EXPLAIN from the interface. Same reason: it runs SQL the reader did not write against a live database.
  • Workers out of process are only partly visible. When the pool runs elsewhere, only what the shared queue backend reports can be shown — and the panel says so rather than reporting zero workers.

CHANGELOG.md carries the full list, and ARCHITECTURE.md explains the three places vise monkeypatches and why each was the only measurement point available.

The example

example/ is a working application with every subsystem switched on — database, cache, queues, workers, schedules, websockets, events, outgoing calls and mail — so all fourteen panels are live in one process with nothing to install and nothing to start.

cd example
vise serve            # then open /__sillo/foreman/
python traffic.py     # in another terminal

traffic.py drives the application the way a person would but faster: reads, writes, searches, queues exports that fail on purpose, opens websockets, and asks for the route that raises. See example/README.md for what makes each panel live and what is worth clicking.

Development

uv sync --extra dev
uv run pytest

cd ui && bun install && bun run build   # rebuild the dashboard interface

The interface is a Vite + React app under ui/, built into sillo_vise/dashboard/static/ and committed, so pip install needs no node. Changing the interface needs bun; using vise does not.

Licence

BSD-3-Clause.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sillo_vise-1.0.0a2.tar.gz (332.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sillo_vise-1.0.0a2-py3-none-any.whl (231.6 kB view details)

Uploaded Python 3

File details

Details for the file sillo_vise-1.0.0a2.tar.gz.

File metadata

  • Download URL: sillo_vise-1.0.0a2.tar.gz
  • Upload date:
  • Size: 332.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sillo_vise-1.0.0a2.tar.gz
Algorithm Hash digest
SHA256 7a5bbcdc7c41ce7645851627d95f8f39769b4fd85fefb5ed08b39401e3920c98
MD5 19598fec4ae7b8a86fbb190c5123f52d
BLAKE2b-256 f6ad615315258d75195958b02119503f309b41d25a6b00f601063356fa5c6223

See more details on using hashes here.

File details

Details for the file sillo_vise-1.0.0a2-py3-none-any.whl.

File metadata

  • Download URL: sillo_vise-1.0.0a2-py3-none-any.whl
  • Upload date:
  • Size: 231.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sillo_vise-1.0.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 ef6d2bac983fd6f9a8e3a768ad4ba4db6bc04ca74087edfe3d801006c79592d0
MD5 b5a606e1e6308159490db78e4e1b5794
BLAKE2b-256 04039ed91068623ac09cd15adc0125bd35ac7a06d495cd9241b70dd30e174c90

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.0a2 This release

2 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