lens-rails

Zero-config APM integration for Lens. Drop this gem into any Rails app and logs, metrics, and request traces start flowing to your Lens instance automatically — no OpenTelemetry SDK, no protobuf, no additional processes.

Installation

Add to your Gemfile:

gem 'lens-rails'

Set two environment variables (e.g. in .env or your secrets manager):

LENS_URL=https://lens.example.com
LENS_APP_TOKEN=your-app-token

That's it. No initializer required.

What gets collected automatically

Signal Source Detail
Logs Rails.logger Every log line with severity, body, controller, action, and request ID
Metrics Rack middleware Request count, error count, mean/p50/p95 duration — flushed every 30 s
HTTP requests process_action.action_controller Controller, action, method, path, status, duration, SQL summary, operation waterfall
Background jobs perform.active_job Job class, queue, duration, SQL summary, operation waterfall

Each HTTP request and job record includes a flat operation waterfall: one entry per SQL query (name + SQL text + duration + offset from request start) and per view render.

Configuration

To override defaults, add an initializer:

# config/initializers/lens.rb
Lens::Rails.configure do |config|
  config.service_name = "my-app"        # defaults to Rails app module name
  config.url          = "https://..."   # overrides LENS_URL
  config.app_token    = "tok_..."       # overrides LENS_APP_TOKEN

  # Only record SQL queries that took at least this long (ms). Default: 0.0 (all queries).
  config.sql_threshold_ms = 5.0

  # Drop SQL queries whose text matches any of these patterns.
  # Useful for suppressing framework noise from the waterfall.
  config.sql_ignore = [
    /solid_queue/i,
    /\ASELECT.*schema_migrations/i,
  ]

  # Minimum severity to export. Default: Logger::INFO (drops DEBUG-level SQL noise).
  # Set to Logger::WARN to export only warnings and above.
  config.min_log_severity = Logger::INFO

  # Ring-buffer size for log records (drop-oldest when full). Default: 10_000.
  config.max_log_buffer = 10_000

  # HTTP timeout for each exporter flush call (seconds). Default: 2.
  config.exporter_open_timeout = 2
  config.exporter_read_timeout = 2

  # Maximum time to wait for a final flush on process shutdown (seconds). Default: 5.
  config.shutdown_timeout = 5
end

How it works

The gem wires up three components via a Railtie:

  • LogExporter — broadcasts into Rails.logger and batches records to POST /v1/logs every 5 s.
  • MetricsExporter — Rack middleware that tracks request durations via reservoir sampling and flushes to POST /v1/metrics every 30 s.
  • RequestsExporter — subscribes to ActiveSupport::Notifications (process_action, sql.active_record, render_template, perform.active_job) and flushes request and job records to POST /v1/requests every 30 s.

All payloads are gzip-compressed JSON. Background flush threads restart automatically after a Puma or Unicorn fork.

SolidQueue dispatcher jobs are filtered out of the job waterfall automatically.

Requirements

  • Ruby >= 3.1
  • Rails >= 7.1