Rails
Sentiero ships as two gems. The core sentiero gem is framework-agnostic. The sentiero-rails gem adds an ActiveRecord store, a view helper, and an install generator.
Installation
Add to your Gemfile:
gem "sentiero-rails"
Run the install generator:
rails generate sentiero:install
rails db:migrate
The generator creates two files:
| File | Purpose |
|---|---|
db/migrate/*_create_sentiero_tables.rb |
Creates five tables: sentiero_sessions, sentiero_events, sentiero_problems, sentiero_occurrences, and sentiero_server_events |
config/initializers/sentiero.rb |
Configuration with basic_auth enabled and other options as commented examples |
Dashboard Basic Auth is enabled by default. The generated initializer includes an active config.basic_auth block:
config.basic_auth = { user: "admin", password: ENV["SENTIERO_DASHBOARD_PASSWORD"] }
The generator also prints a randomly generated password and the corresponding export command:
SENTIERO_DASHBOARD_PASSWORD=<generated>
export SENTIERO_DASHBOARD_PASSWORD=<generated>
Set this environment variable before starting your server, or the dashboard will raise Sentiero::Error when accessed (fail closed; a blank password is never silently accepted). To disable Basic Auth entirely, comment out the config.basic_auth block in the initializer and configure auth_callback or route-level auth instead.
Database Tables
The migration creates five tables: sentiero_sessions and sentiero_events hold recording sessions and their rrweb events; sentiero_problems and sentiero_occurrences hold grouped server-side errors and their individual occurrences; sentiero_server_events holds server-side structured events (logs, audit entries). See ActiveRecord Store for the column-by-column schema and indexes.
ActiveRecord Store
When sentiero-rails is loaded, the engine automatically sets Sentiero::Rails::Store.new as the default store if you haven’t configured one explicitly. You don’t need to set config.store unless you want to use Redis or Memory instead; see Storage Backends for the alternatives.
The AR store uses insert_all for bulk event writes and wraps each save_events call in a transaction.
LRU Eviction
Two limits from Sentiero.configuration are enforced inside each save_events transaction:
| Config | Behavior |
|---|---|
max_sessions |
When exceeded, oldest sessions (by updated_at) are deleted. The session being written to is protected. |
max_events_per_session |
When exceeded, oldest events (by timestamp) within the session are deleted. |
Both default to nil (unlimited). Set them in your initializer for production.
Cleanup
For time-based cleanup, use the built-in rake task loaded by the engine. Set config.retention_period (in seconds) in your initializer, then schedule:
rake sentiero:purge # deletes sessions older than retention_period (irreversible)
For GDPR erasure of specific sessions or a time range:
rake sentiero:erase SESSION_IDS=id1,id2
rake sentiero:erase SINCE=2024-01-01 UNTIL=2024-06-01
If you need manual AR-level cleanup as an alternative (e.g., in a Sidekiq cron job that runs its own queries), you can delete directly:
Sentiero::Rails::Event.where("created_at < ?", 30.days.ago).delete_all
Sentiero::Rails::Session.left_joins(:events)
.where(sentiero_events: { id: nil }).delete_all
Two Configuration Objects
Sentiero has two separate configuration namespaces. Do not confuse them.
Sentiero.configure – core configuration. Controls the store, CORS, auth, privacy, recorder options, and resource limits. This is the same object used by all frameworks.
Sentiero::Rails.configure – Rails-specific. Two options:
Sentiero::Rails.configure do |config|
config.events_url = "/sentiero/events" # default
config.reporter_middleware = true # default; set false to opt out
end
events_url is only used by the sentiero_script_tag view helper to set the default endpoint URL. If you pass events_url: directly to the helper, this config is ignored.
reporter_middleware controls whether the engine auto-inserts Sentiero::Reporter::Middleware into the Rails middleware stack. Defaults to true; the middleware only activates if the reporter is also configured and active. Set to false to manage the middleware manually.
View Helper
The engine injects sentiero_script_tag into all views via ActionView. Add it to your layout before </body>:
<!-- app/views/layouts/application.html.erb -->
<body>
<%= yield %>
<%= sentiero_script_tag %>
</body>
This renders the recorder’s two script tags (a config JSON block and the loader). The events_url defaults to Sentiero::Rails.configuration.events_url ("/sentiero/events"). See The Recorder for what the tags contain.
To override:
<%= sentiero_script_tag(events_url: "/custom/events/path") %>
Routing
Mount both Rack apps in config/routes.rb. The events endpoint must be public (it receives browser-generated recording data). The dashboard should be protected.
# config/routes.rb
mount Sentiero::Web::EventsApp.new => "/sentiero/events"
# Option A: mount openly, protect via basic_auth or auth_callback in the initializer
mount Sentiero::Web::DashboardApp.new => "/sentiero"
# Option B: protect via a Devise route constraint (see warning below)
# authenticate :user, ->(u) { u.admin? } do
# mount Sentiero::Web::DashboardApp.new => "/sentiero"
# end
The events endpoint path must match the events_url used by the script tag helper (default: "/sentiero/events"). The dashboard must be mounted at a path that is a prefix of the events path minus /events (default: "/sentiero").
Why the mount path matters for assets
The recorder JS is served from /sentiero/assets/recorder-<hash>.js. The script tag derives that URL from the events URL minus /events, and DashboardApp serves /assets/* itself, before its auth check, so the recorder stays public even when the dashboard is protected by auth_callback or basic_auth.
Warning: do not wrap the dashboard mount in a route constraint. A Rails
authenticate/route constraint (Option B above) gates everything under the mount path, including/sentiero/assets/*. Anonymous visitors then get a 401/redirect onrecorder.jsand recording silently breaks for everyone except logged-in admins. Two safe ways to require a login redirect instead:
- Use
config.basic_authorconfig.auth_callback(assets short-circuit before the callback runs), or- Mount the public assets endpoint outside the constraint:
# Serve recorder.js publicly, gate only the dashboard UI mount Sentiero::Web::AssetsApp.new => "/sentiero/assets" authenticate :user, ->(u) { u.admin? } do mount Sentiero::Web::DashboardApp.new => "/sentiero" end
Authentication
The generator enables basic_auth by default (see Installation). For session-based auth (Devise, Warden), set auth_callback in Sentiero.configure instead:
Sentiero.configure do |config|
config.auth_callback = ->(env) { env["warden"]&.authenticated? && env["warden"].user.admin? }
end
Route-level auth (a Devise authenticate block) can redirect to a login page, but it gates /sentiero/assets/* and breaks recording for anonymous visitors unless you mount Sentiero::Web::AssetsApp publicly outside the constraint. See the warning under Routing.
The events endpoint is intentionally unauthenticated; protect it with CORS and rate limiting. See Authentication for the full guide (Devise examples, the 403 limitation, CSRF, and the production checklist).