restless-sdk
Capture your API traffic and send it to Restless.
One Rack middleware covers Rails, Sinatra, Hanami, Grape, Roda and anything else that speaks Rack. Ruby 2.6+. No dependencies.
Install
gem install restless-sdk
or in a Gemfile:
gem "restless-sdk"
Use
require "restless"
CLIENT = Restless.new(ENV["RESTLESS_KEY"])
CLIENT.setup do |request|
{
api_key: CLIENT.mask(request.header("Authorization")),
owner: {
# Permanent and immutable. A workspace id or database primary key,
# never an API key, email, or anything else that rotates: the
# dashboard pins a project's whole log history to this.
id: workspace_id_for(request),
# Runs once per owner id, then caches for an hour. Put the expensive
# lookup here, not in the fields above.
enrich: ->(owner_id) {
workspace = Workspace.find(owner_id)
{ label: workspace.name, email: workspace.admin_emails }
},
},
}
end
Then mount it.
# config.ru (Sinatra, Roda, Hanami, plain Rack)
use CLIENT.rack
run App
# config/application.rb (Rails)
config.middleware.insert_before 0, CLIENT.rack
CLIENT.rack returns a middleware factory: anything that calls .new(app)
on it works. Mount it as far out as you can, so it sees the real status
and body your app produced rather than an inner layer's.
What you get
- Lazy owner enrichment. The
enrichcallback runs on the first request from each owner id and then caches, so 100 requests from one workspace do not mean 100 database lookups. The cached value still rides on every upload, so no request lands in the dashboard as unauthenticated. - Safe by default.
Authorization,Cookie,password,token,ssnand friends are redacted before anything leaves your process. Bodies with nothing to redact are passed through byte for byte, so your payloads are never re-serialized on the way out. - Error triage. 4xx/5xx responses get
x-log-urlandx-debugheaders and adebugblock in the JSON body. If someone attaches a "next steps" message to an error in the dashboard, the SDK injects it asdebug.recovery- read synchronously from an in-process cache, never blocking the response on a network call. A 5xx that groups by its throw site also reports the key it would have grouped under otherwise, so a message you attached before that grouping existed keeps being injected. - Blocking. Return
{ block: true }from the setup callback to reject a request before your handler runs, or{ block: { status: 402, message: "Payment required" } }to pick the status.
The setup callback
It receives a small read-only request view and returns a hash:
| field | what |
|---|---|
api_key |
The masked end-user key. Always CLIENT.mask(raw_header) - never substitute "anonymous", whose last 4 characters would become the mask tail. |
owner[:id] |
The permanent, immutable workspace/tenant identifier. |
owner[:enrich] |
->(id) { ... } returning owner metadata (label, email, extras). The only channel for owner metadata; anything else inline on owner is dropped. |
block |
true, or { status:, message: }. |
| anything else | Carried through onto the log as-is. Keep it cheap - read straight off the request. Anything expensive belongs in enrich. |
The request view exposes header(name) (case-insensitive),
request_method, path, query_string, url, and the raw Rack env.
Route patterns
The middleware reads the matched route template from the framework so your handlers do not have to report it:
| framework | source | reported as |
|---|---|---|
| Sinatra | env["sinatra.route"] (GET /pets/:id) |
/pets/{id} |
| Rails | env["action_dispatch.route_uri_pattern"] |
/pets/{id} |
| anything | env["restless.route"], set by you |
as given |
The method prefix is stripped and :id-style params are rewritten to
{id}, so the same endpoint produces the same routePattern here as it
does in the Node, Python and Go SDKs. Override the whole thing with a
lambda:
use CLIENT.rack(route: ->(env) { env["my_router.pattern"] })
Never breaks your API
Observability must not take down a production request path. Upload failures,
callback exceptions, unserializable bodies and malformed input are all caught
and swallowed (surfaced only under DEBUG=restless). Uploads happen on a
background thread and are fire-and-forget. Streaming responses
(text/event-stream) and bodies over 1 MiB are never buffered.
An exception your app raises is captured with its stack - so the error fingerprint keys on the method that raised, not on the middleware - and then re-raised untouched, leaving your own error handling completely unaffected.
Environment variables
| variable | purpose |
|---|---|
RESTLESS_KEY |
Your project API key, if you do not pass one explicitly. |
RESTLESS_BASE_URL |
Override the ingest URL (self-hosted or staging). |
RESTLESS_ENV / RACK_ENV / RAILS_ENV |
production enables batching; anything else flushes every request. test disables uploads entirely. |
DEBUG=restless |
Print upload diagnostics to stderr. |
Conformance
This SDK implements version 1.0.0 of the Restless SDK Contract at level L2, and is verified against the shared cross-language conformance vectors and a differential fuzzer run against the reference implementation. See CONFORMANCE.md.
License
ISC