You're 6 months into production with Bugsnag. The CFO says "costs are too high, switch to Sentry."
The migration estimate? 3 months. Why? Because your entire codebase is littered with:
Bugsnag.notify(exception)
Bugsnag.leave_breadcrumb("User clicked checkout")
- Bugsnag-specific configuration
- Custom Bugsnag metadata patterns
This is vendor lock-in through API pollution.
# Your code never changes:
Lapsoss.capture_exception(e)
# Switch vendors in config, not code:
Lapsoss.configure do |config|
# Monday: Using Bugsnag
config.use_bugsnag(api_key: ENV['BUGSNAG_KEY'])
# Tuesday: Add Telebugs for comparison
config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
# Wednesday: Drop Bugsnag, keep Telebugs
# Just remove the line. Zero code changes.
end
- Ruby 3.3+
- Rails 7.2+
gem 'lapsoss'
# Capture exceptions
Lapsoss.capture_exception(e)
# Capture messages
Lapsoss.capture_message("Payment processed", level: :info)
# Add context
Lapsoss.with_scope(user_id: current_user.id) do
process_payment
end
# Add breadcrumbs
Lapsoss.add_breadcrumb("User clicked checkout", type: :navigation)
That's it. No 500-line examples needed.
Lapsoss integrates with Rails' native error reporting API introduced in Rails 7. No monkey-patching, no global error handlers:
# It just works with Rails.error:
Rails.error.handle(context: {user_id: current_user.id}) do
risky_operation
end
# Automatically captured by whatever service you configured
Option 1: Automatic Rails.error Integration (Recommended)
# config/initializers/lapsoss.rb
Lapsoss.configure do |config|
config.use_appsignal(push_api_key: ENV['APPSIGNAL_KEY'])
end
# That's it! All Rails errors are automatically captured
# Works with Rails.error.handle, Rails.error.record, Rails.error.report
# No code changes needed - just configure and go
Option 2: Add Controller Context (Optional)
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Lapsoss::RailsControllerContext
end
# Now all errors include controller/action context:
# { controller: "users", action: "show", controller_class: "UsersController" }
Option 3: Manual Error Reporting (Your Choice)
# In controllers, jobs, or anywhere you want explicit control
begin
process_payment
rescue => e
Lapsoss.capture_exception(e, user_id: current_user.id)
# Handle gracefully...
end
# Or use Rails.error directly with your configured services
Rails.error.report(e, context: { user_id: current_user.id })
Unlike other gems, Lapsoss never automatically captures all exceptions. You stay in control:
- β Rails.error integration only - Uses Rails' official API
- β Explicit error handling - You choose what to capture
- β No global hooks - Your app behavior never changes
- β Optional controller context - Include if you want it
This means your application behaves exactly the same with or without Lapsoss. No surprises, no changed behavior, no conflicts with other gems.
# Step 1: Add Lapsoss alongside your current setup
gem 'lapsoss'
gem 'bugsnag' # Keep your existing gem for now
# Step 2: Configure dual reporting
Lapsoss.configure do |config|
config.use_bugsnag(api_key: ENV['BUGSNAG_KEY'])
config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
end
# Step 3: Gradually replace Bugsnag calls
# Old: Bugsnag.notify(e)
# New: Lapsoss.capture_exception(e)
# Step 4: Remove bugsnag gem when ready
# Your app keeps running, now on Telebugs
Vendor SDKs monkey-patch your application:
- Sentry patches Net::HTTP, Redis, and 20+ other gems
- Bugsnag patches ActionController, ActiveJob, and more
- Each vendor races to patch the same methods
- Multiple SDKs = multiple layers of patches
- Your app behavior changes based on load order
Lapsoss doesn't patch anything:
- Pure Ruby implementation
- Uses Rails' error API
- Your app behavior remains unchanged
- No competing instrumentation
# Route EU data to EU servers, US data to US servers
config.use_sentry(name: :us, dsn: ENV['US_SENTRY_DSN'])
config.use_telebugs(name: :eu, dsn: ENV['EU_TELEBUGS_DSN'])
# Run both services, compare effectiveness
config.use_rollbar(name: :current, access_token: ENV['ROLLBAR_TOKEN'])
config.use_sentry(name: :candidate, dsn: ENV['SENTRY_DSN'])
# Multiple providers for redundancy
config.use_telebugs(name: :primary, dsn: ENV['PRIMARY_DSN'])
config.use_appsignal(name: :backup, push_api_key: ENV['APPSIGNAL_KEY'])
This is a Rails gem for Rails applications. We use ActiveSupport because:
- You already have it (you're using Rails)
- It provides the exact utilities we need
- It's better than reimplementing Rails patterns poorly
If you need pure Ruby error tracking, use the vendor SDKs directly for now.
- Teams that have been burned by vendor lock-in
- Apps that need regional data compliance (GDPR)
- Developers who value clean, maintainable code
- Rails applications that embrace change
- Pure Ruby libraries (use vendor SDKs)
- Teams happy with their current vendor forever
- Applications that need APM features
- Non-Rails applications
All adapters are pure Ruby implementations with no external SDK dependencies:
- Sentry - Full error tracking support
- Rollbar - Complete error tracking with grouping
- AppSignal - Error tracking and deploy markers
- Insight Hub (formerly Bugsnag) - Error tracking with breadcrumbs
- Telebugs - Sentry-compatible protocol (perfect for self-hosted alternatives)
# config/initializers/lapsoss.rb
Lapsoss.configure do |config|
config.use_telebu
8000
gs(dsn: ENV["TELEBUGS_DSN"])
end
Lapsoss.configure do |config|
# Named adapters for different purposes
config.use_sentry(name: :errors, dsn: ENV['SENTRY_DSN'])
config.use_rollbar(name: :business_events, access_token: ENV['ROLLBAR_TOKEN'])
config.use_logger(name: :local_backup) # Local file backup
end
# Telebugs, Glitchtip, or any Sentry-compatible service
Lapsoss.configure do |config|
config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
# Or use use_sentry with a custom endpoint
config.use_sentry(dsn: ENV['SELF_HOSTED_SENTRY_DSN'])
end
Lapsoss.configure do |config|
# Adapter setup
config.use_rollbar(access_token: ENV['ROLLBAR_TOKEN'])
# Data scrubbing (uses Rails filter_parameters automatically)
config.scrub_fields = %w[password credit_card ssn] # Or leave nil to use Rails defaults
# Performance
config.async = true # Send errors in background
# Sampling (see docs/sampling_strategies.md for advanced examples)
config.sample_rate = Rails.env.production? ? 0.25 : 1.0
# Transport settings
config.transport_timeout = 10 # seconds
config.transport_max_retries = 3
end
You decide what errors to track. Lapsoss doesn't make assumptions:
Lapsoss.configure do |config|
# Use the before_send callback for simple filtering
config.before_send = lambda do |event|
# Return nil to prevent sending
return nil if event.exception.is_a?(ActiveRecord::RecordNotFound)
event
end
# Or use the exclusion filter for more complex rules
config.exclusion_filter = Lapsoss::ExclusionFilter.new(
# Exclude specific exception types
excluded_exceptions: [
"ActionController::RoutingError", # Your choice
"ActiveRecord::RecordNotFound" # Your decision
],
# Exclude by pattern matching
excluded_patterns: [
/timeout/i, # If timeouts are expected in your app
/user not found/i # If these are normal in your workflow
],
# Exclude specific error messages
excluded_messages: [
"No route matches",
"Invalid authenticity token"
]
)
# Add custom exclusion logic
config.exclusion_filter.add_exclusion(:custom, lambda do |event|
# Your business logic here
event.context[:request]&.dig(:user_agent)&.match?(/bot/i)
end)
end
# Development/Test exclusions
if Rails.env.development?
config.exclusion_filter.add_exclusion(:exception, "RSpec::Expectations::ExpectationNotMetError")
config.exclusion_filter.add_exclusion(:exception, "Minitest::Assertion")
end
# User input errors (if you don't want to track them)
config.exclusion_filter.add_exclusion(:exception, "ActiveRecord::RecordInvalid")
config.exclusion_filter.add_exclusion(:exception, "ActionController::ParameterMissing")
# Bot traffic (if you want to exclude it)
config.exclusion_filter.add_exclusion(:custom, lambda do |event|
request = event.context[:request]
request && request[:user_agent]&.match?(/googlebot|bingbot/i)
end)
Your app, your rules. Lapsoss just provides the mechanism.
Lapsoss automatically integrates with Rails' parameter filtering:
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [:password, :token]
# Lapsoss automatically uses these filters - no additional configuration needed!
Control how errors are grouped:
config.fingerprint_callback = lambda do |event|
case event.exception&.class&.name
when "ActiveRecord::RecordNotFound"
"record-not-found" # Group all together
when "Stripe::CardError"
"payment-failure"
else
nil # Use default fingerprinting
end
end
Built-in retry logic with exponential backoff:
config.transport_max_retries = 3
config.transport_timeout = 10
config.transport_jitter = true # Prevent thundering herd
Want to see Lapsoss in action? Try this in your Rails console:
# Configure Lapsoss with the logger adapter for immediate visibility
Lapsoss.configure do |config|
config.use_logger(name: :console_test)
config.async = false # Synchronous for immediate output
config.debug = true # Verbose logging
end
# Create a class that demonstrates error handling
class Liberation
def self.liberate!
Rails.error.handle do
raise StandardError, "Freedom requires breaking chains!"
end
puts "β
Continued execution after error"
end
def self.revolt!
Rails.error.record do
raise RuntimeError, "Revolution cannot be stopped!"
end
puts "This won't print - error was re-raised"
end
end
# Test error capture (error is swallowed)
Liberation.liberate!
# You'll see the error logged but execution continues
# Test error recording (error is re-raised)
begin
Liberation.revolt!
rescue => e
puts "Caught re-raised error: #{e.message}"
end
# Manual error reporting with context
begin
1 / 0
rescue => e
Rails.error.report(e, context: { user_id: 42, action: "console_test" })
end
# Check what was captured
puts "\nπ Lapsoss captured all errors through Rails.error!"
You'll see all err 8000 ors logged to your console with full backtraces and context. This same integration works automatically for all Rails controllers, jobs, and mailers.
Lapsoss provides the same convenient error handling methods directly, perfect for background jobs, rake tasks, or standalone scripts:
# In your Sidekiq job, rake task, or any Ruby code
require 'lapsoss'
Lapsoss.configure do |config|
config.use_sentry(dsn: ENV['SENTRY_DSN'])
end
# Handle errors (swallow them)
result = Lapsoss.handle do
risky_operation
end
# Returns nil if error occurred, or the block's result
# Handle with fallback
user = Lapsoss.handle(fallback: User.anonymous) do
User.find(id)
end
# Record errors (re-raise them)
Lapsoss.record do
critical_operation # Error is captured then re-raised
end
# Report errors manually
begin
something_dangerous
rescue => e
Lapsoss.report(e, user_id: user.id, context: 'background_job')
# Continue processing...
end
# These methods mirror Rails.error exactly:
# - Lapsoss.handle β Rails.error.handle
# - Lapsoss.record β Rails.error.record
# - Lapsoss.report β Rails.error.report
This means your error handling code works the same way everywhere - in Rails controllers, background jobs, rake tasks, or standalone scripts.
class MyAdapter < Lapsoss::Adapters::Base
def capture(event)
# Send to your service
HttpClient.post("/errors", event.to_h)
end
end
Lapsoss::Registry.register(:my_service, MyAdapter)
For Sentry-compatible services, just extend the SentryAdapter:
class TelebugsAdapter < Lapsoss::Adapters::SentryAdapter
def initialize(name = :telebugs, settings = {})
super(name, settings)
end
private
def build_headers(public_key)
super(public_key).merge(
"X-Telebugs-Client" => "lapsoss/#{Lapsoss::VERSION}"
)
end
end
- Fork it
- Create your feature branch
- Add tests for your changes
- Submit a pull request
MIT License - Because good code should be free
Built for Rails developers who refuse to be locked in.