Current section
Files
Jump to
Current section
Files
metrixwire
README.md
README.md
# metrixwire
Zero-config APM SDK for **Elixir** — Phoenix, Plug & Ecto. In Phoenix it's fully automatic: add the dependency and set `METRIXWIRE_KEY`. Every request, database query and outbound HTTP call is instrumented automatically. There is no manual span API. Non-blocking: if the MetrixWire endpoint is down, your app keeps running normally.
## Installation
```elixir
# mix.exs
def deps do
[
{:metrixwire, "~> 0.1", hex: :metrixwire}
]
end
```
```bash
mix deps.get
```
## Usage
### Phoenix — fully automatic
Add the dep, set the key, done. The SDK ships its own OTP application that starts on boot, brings up the transport, and attaches every `:telemetry` handler — no code changes, no middleware to wire up:
```bash
export METRIXWIRE_KEY=mw_...
```
Or configure it in `config/runtime.exs`:
```elixir
config :metrixwire, api_key: System.get_env("METRIXWIRE_KEY")
```
Phoenix emits `[:phoenix, :endpoint, :start | :stop]` and `[:phoenix, :router_dispatch, :*]` telemetry, which the SDK hooks to open one trace per request, refine the route to the matched pattern (`GET /users/:id`), and capture the HTTP status and any exception.
### Bare Plug — one line
Non-Phoenix Plug apps add a single plug to their pipeline (analogous to a Rack middleware). Everything else — Ecto queries, outbound HTTP — is still captured automatically:
```elixir
plug MetrixWire.Plug
```
## How the automatic tracing works
The whole Elixir ecosystem emits `:telemetry` events, so **one handler set** covers every framework. The SDK attaches them at boot via `:telemetry.attach_many/4`. A Phoenix/Plug request runs in a single process, and Ecto/Finch telemetry is emitted in the **caller** process, so the active trace — held in the process dictionary — correctly correlates every span to its request.
| Source | Traced automatically | How |
|---|---|---|
| **Phoenix** | ✅ | `[:phoenix, :endpoint, :*]` opens/closes the trace; `[:phoenix, :router_dispatch, :*]` refines the route + captures exceptions. |
| **bare Plug** | ✅ | `plug MetrixWire.Plug` — one trace per request, closed on `before_send`. |
| **Ecto** | ✅ | `[<repo>, :query]` → `db_query` span (SQL as description, `total_time` converted to ms, `meta.rowCount` from `num_rows`). Repos are auto-discovered. |
| **Finch** | ✅ | `[:finch, :request, :stop]` → `http_call` span with `meta.statusCode`. |
| **Tesla** | ✅ | `[:tesla, :request, :stop]` → `http_call` span with `meta.statusCode`. |
Each handler is guarded and never throws into the emitting process. Schema/transaction-control SQL (`BEGIN`, `COMMIT`, `SET`, …) is filtered out.
## Configuration
All options live under `config :metrixwire, ...` and fall back to environment variables:
```elixir
config :metrixwire,
api_key: "mw_...", # required (falls back to METRIXWIRE_KEY)
endpoint: "http://localhost:3000/ingest", # default (a base URL is accepted; /ingest is appended)
flush_interval_ms: 5000, # how often batches are sent
enabled: true, # set false to disable (or METRIXWIRE_ENABLED=false)
timeout_ms: 3000, # send timeout (short, non-blocking)
max_batch: 20, # flush immediately once this many are queued
capture_source: true # capture the file:line a span originated from
```
Environment variables: `METRIXWIRE_KEY`, `METRIXWIRE_ENDPOINT`, `METRIXWIRE_ENABLED`. With no API key the SDK boots in **disabled mode** (logs a warning, attaches nothing) instead of raising.
## Non-blocking behavior
- Traces are batched in a `GenServer` and flushed off the request path on a timer + once `max_batch` is reached. The HTTP send runs in a detached `Task`, so a slow endpoint never backs up the mailbox.
- Delivery uses the built-in `:httpc` / `:inets` — **no external HTTP client dependency**. The only runtime dependency is `:telemetry`.
- **All** transport errors are swallowed — instrumentation never throws into or blocks your app.
- A final flush runs on application shutdown; you can also call `MetrixWire.flush/0` before a short-lived process exits.
## Error boundary (escape hatch)
For frameworks the SDK can't hook automatically, attach an exception to the active trace — this is **not** a manual span API; requests/queries are still captured automatically:
```elixir
try do
do_work()
rescue
e ->
MetrixWire.capture_exception(e, __STACKTRACE__)
reraise e, __STACKTRACE__
end
```