Monitorex
Copy MarkdownReal-time HTTP telemetry dashboard for Elixir/Phoenix applications.
Monitorex monitors outbound (Tesla, Finch/Req) and inbound (Phoenix) HTTP traffic, aggregates it into ETS-backed metrics, and renders a live-updating dark-theme dashboard — no database required.

Features
- Outbound monitoring — track HTTP requests from Tesla, Finch, or Req
- Inbound monitoring — track Phoenix router dispatch with per-consumer breakdowns
- Live dashboard — 8 pages: Overview, Outbound/Inbound, host/route detail, timeline, consumer analytics
- Timeline inspector — split-pane page with event list + request/response detail viewer
- Auto-refresh — LiveView updates every 2 seconds
- Sort, filter, paginate — interactive data tables on every page
- Responsive — works on desktop and mobile (collapsible sidebar, card-layout tables)
- Dark theme — polished design system with SVG icons and custom properties
- Cluster support — aggregate data across multiple BEAM nodes
- Health check —
GET /monitorex/healthwith Collector status, queue depths, ETS sizes - Prometheus metrics —
GET /monitorex/metricsfor requests, errors, latency, ETS sizes - Alert webhooks — configurable thresholds (error_rate, host_down, high_latency) with debounced dispatch
- CSV/JSON export — download any dashboard view as
.csvor.json - REST API — programmatic access to hosts, routes, events, and metrics via JSON endpoints
- Slow request tracing — automatic capture of request/response bodies for requests exceeding a latency threshold
- Alert Center — live alerts page with firing status, history, acknowledge, and snooze controls
- Alert History — GenServer-backed ETS storage for alert records with lifecycle management
- Native notifications — Slack, Discord, and Email notifiers with debounced dispatch
- Persistent storage — optional SQLite backend via swappable
Storage.Backendbehaviour (ETS remains default) - No database required — all data lives in ETS tables (in-memory) by default
Screenshots
| Outbound Overview | Host Detail | Timeline Inspector |
|---|---|---|
![]() | ![]() | ![]() |
Installation
Add monitorex to your mix.exs:
def deps do
[
{:monitorex, "~> 0.6.0"}
]
endThen run:
mix deps.get
Quick Start
1. Configure sources
In config/config.exs:
config :monitorex, :sources, [:tesla, :finch, :req, :phoenix]2. Mount the dashboard in your router
# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
use Phoenix.Router
import Monitorex.Router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :protect_from_forgery
end
scope "/monitoring" do
pipe_through :browser
http_dashboard []
end
end3. Start your server
mix phx.server
Visit /monitoring to see your dashboard.
Configuration
Sources
config :monitorex, :sources, [:tesla, :finch, :phoenix]Available sources: :tesla, :finch, :req, :phoenix. Only attach the sources you use.
⚠️ Req source requires
req_telemetry— Req 0.5.x removed built-in telemetry. Add{:req_telemetry, "~> 0.1"}to yourmix.exsdeps for Req events to fire.
Inbound path filtering
Only track requests under specific path prefixes:
config :monitorex, :inbound_path_prefixes, ["/api", "/graphql"]When not configured, all paths are tracked.
Authentication & Access Control
Implement the Monitorex.Resolver behaviour to control dashboard access:
defmodule MyApp.MonitorexResolver do
@behaviour Monitorex.Resolver
@impl true
def resolve_user(conn) do
# Return a map with user info from your session/auth system
case get_session(conn, :current_user) do
nil -> %{id: nil, name: "guest"}
user -> %{id: user.id, name: user.name}
end
end
@impl true
def resolve_access(%{id: nil}) do
# Redirect unauthenticated users to login
{:forbidden, "/login"}
end
def resolve_access(_user) do
:all
end
endConfigure it:
config :monitorex, :resolver, MyApp.MonitorexResolverIf no resolver is configured, a default resolver grants full access (:all).
Consumer Identification
Monitorex identifies inbound consumers by priority:
- Custom function — your own
consumer_fn:config :monitorex, :consumer_fn, &MyApp.extract_consumer/1 - Basic-auth username — decoded from
Authorization: Basic ... - API key header — value of
X-Api-Key(first 8 characters)
Deduplication
When both Tesla and Finch are used in the same application, the same HTTP request may fire events from both libraries. Enable dedup to prevent double-counting:
config :monitorex, :clients, [:tesla, :finch]Request/Response Detail Capture
Monitorex can capture HTTP headers and bodies for detailed inspection.
Header redaction
Sensitive header values are automatically redacted before storage:
config :monitorex, :redacted_headers, [
"authorization",
"cookie",
"set-cookie",
"x-api-key",
"x-auth-token"
]Body storage
Body capture is disabled by default to limit memory usage:
# Store request and/or response bodies on the Event struct
config :monitorex, :store_request_body, true
config :monitorex, :store_response_body, true
# Truncate bodies larger than N bytes (default: 10_000)
config :monitorex, :max_body_bytes, 10_000Memory Management
To prevent unbounded ETS growth in production, Monitorex caps aggregate tables and prunes stale entries:
# Maximum entries per aggregate table (hosts, endpoints, routes, consumers)
# When exceeded, oldest entries are dropped during cleanup.
config :monitorex, :max_endpoints, 2_000
# Recent event ring buffers (per direction)
config :monitorex, :max_recent, 500 # outbound
config :monitorex, :max_recent_inbound, 500 # inbound
# Stale entry TTL (aggregate tables)
config :monitorex, :endpoint_ttl, :timer.hours(1)Slow Request Tracing
Monitorex can automatically flag and retain detailed traces for slow requests. When a request exceeds the configured threshold, full request/response bodies are captured even if body storage is globally disabled — providing debugging data without the memory overhead of storing all bodies.
# Latency threshold in milliseconds (default: 2_000)
config :monitorex, :slow_request_threshold_ms, 2_000
# Maximum slow requests retained per direction (default: 200)
config :monitorex, :max_slow, 200Set :slow_request_threshold_ms to nil or 0 to disable slow request tracing entirely.
Slow events are stored in separate ETS tables (:monitorex_slow_outbound and :monitorex_slow_inbound) and exposed via Monitorex.Storage.list_slow_outbound/1 and list_slow_inbound/1 for custom dashboards or alerting integrations.
Monitor runtime memory usage:
Monitorex.memory_usage()
# => %{tables: %{monitorex_outbound_hosts: %{size: 42, memory_words: 1234}, ...},
# total_words: 46089, total_kb: 18.53}The health endpoint (GET /monitorex/health) also exposes current ETS table sizes and total memory under ets_table_sizes and total_ets_memory_words.
Storage Backend
By default, Monitorex stores all data in ETS tables (in-memory). You can optionally enable SQLite persistence so metrics survive BEAM restarts:
# Use ETS (default)
config :monitorex, :storage, Monitorex.Storage.ETS
# Use SQLite — requires :exqlite in your deps
config :monitorex, :storage, Monitorex.Storage.SQLite
config :monitorex, :sqlite_path, "/var/lib/monitorex/data.db"SQLite is compiled conditionally — if exqlite is not present, Monitorex falls back to ETS automatically. Add {:exqlite, "~> 0.29"} to your mix.exs to use it.
Alerts & Notifications
Configure alert rules and notification channels:
# Alert thresholds evaluated every cleanup cycle
config :monitorex, :alerts, [
%{name: :high_error_rate, condition: :error_rate, threshold: 0.05},
%{name: :host_down, condition: :host_down, threshold: 3},
%{name: :high_latency, condition: :high_latency, threshold: 1_000}
]
# Slack webhook
config :monitorex, :slack_webhook_url, "https://hooks.slack.com/services/..."
# Discord webhook
config :monitorex, :discord_webhook_url, "https://discord.com/api/webhooks/..."
# SMTP (requires :gen_smtp)
config :monitorex, :smtp,
relay: "smtp.example.com",
username: "alerts@example.com",
password: "secret",
from: "monitorex@example.com",
to: ["oncall@example.com"]Rules can also be added/removed at runtime via Monitorex.Alerts.add_rule/1 and remove_rule/1.
Pages
| Page | URL | Description |
|---|---|---|
| Outbound Overview | / | Summary cards + host table |
| Outbound Recent | /outbound_recent | Live feed with status filter |
| Host Detail | /host/:host | Per-endpoint breakdown + recent requests |
| Inbound Overview | /inbound | Route table + summary |
| Inbound Consumers | /inbound_consumers | Per-consumer stats |
| Inbound Recent | /inbound_recent | Live feed with filters |
| Timeline | /timeline | Split-pane event inspector with request/response detail |
| Route Detail | /route/:key | Consumer breakdown + recent requests |
| Alerts | /alerts | Alert summary, firing alerts, history table |
REST API
Monitorex ships a built-in JSON REST API for programmatic access to telemetry data. The API is auto-mounted at /api (configurable via the :api_path option in http_dashboard/1).
Endpoints
| Endpoint | Description |
|---|---|
GET /api/health | Health status (same as /monitorex/health) |
GET /api/hosts | List all hosts with aggregate stats |
GET /api/hosts/:host | Per-host detail with endpoint breakdown |
GET /api/routes | Inbound route aggregates |
GET /api/consumers | Consumer stats |
GET /api/events | Recent events with filters (see below) |
GET /api/events/:timestamp | Single event detail |
GET /api/metrics | Computed metrics (RPS, error rate, latency quantiles) |
All endpoints return a consistent JSON envelope:
{"ok": true, "data": ...}Errors return:
{"ok": false, "error": "message"}Query parameters
Events (GET /api/events):
| Param | Type | Default | Description |
|---|---|---|---|
direction | string | "outbound" | "outbound" or "inbound" |
limit | integer | 50 | Max results (max: 500) |
offset | integer | 0 | Pagination offset |
host | string | — | Filter by host (outbound only) |
method | string | — | Filter by HTTP method (GET, POST, etc.) |
status | integer | — | Filter by HTTP status code |
consumer | string | — | Filter by consumer (inbound only) |
route | string | — | Filter by route key (inbound only) |
since | ISO 8601 | — | Events after this timestamp |
Metrics (GET /api/metrics):
| Param | Type | Default | Description |
|---|---|---|---|
host | string | all | Filter to a specific host |
window | integer | 300 | Time window in seconds for RPS/error rate |
Pagination
Paginated endpoints (/api/events) return these response headers:
X-Total-Count— total matching eventsX-Page-Size— the limit parameter usedX-Page-Offset— the offset parameter usedX-Returned-Count— actual returned count
CORS
All endpoints include Access-Control-Allow-Origin: * and respond to OPTIONS preflight requests.
Examples
# List all hosts
curl http://localhost:4000/api/hosts
# Outbound events filtered by host and status
curl "http://localhost:4000/api/events?direction=outbound&host=api.example.com&status=500"
# Metrics with 5-minute window
curl "http://localhost:4000/api/metrics?window=300"
# Single event by timestamp
curl "http://localhost:4000/api/events/1779232233155167"
Disabling the API
Set :api_path to nil or false:
http_dashboard api_path: falseNote: The API is mounted inside the scope pipeline. For production use, consider mounting it in a separate scope without
:browserpipeline to avoid CSRF protection on non-GET methods:
scope "/monitoring" do pipe_through :browser http_dashboard api_path: false end scope "/monitoring/api" do forward "/", Monitorex.ApiPlug end
Asset Pipeline
Monitorex ships pre-built CSS and JS assets. To rebuild them from source:
mix assets.build
Source files are in assets/css/app.css and assets/js/app.js. The build uses Tailwind CSS v4 and esbuild.
Development
git clone https://github.com/GustavoZiaugra/monitorex.git
cd monitorex
mix deps.get
mix compile --warnings-as-errors
# Run tests
mix test
# Run demo server
mix run scripts/demo.exs
# Validate as Phoenix dependency
cd /tmp
mix phx.new demo_monitorex --no-ecto --no-mailer --no-dashboard --no-gettext
cd demo_monitorex
# add {:monitorex, path: "/path/to/monitorex"} to mix.exs
mix deps.get && mix compile
Docs
mix docs
Then open doc/index.html.
License
MIT

