rails-logging
Skill2 starsChanged 8 days agoMITView on GitHub
Use when setting up a Rails app's logging or observability, debugging noisy logs, or adding structured logging: Lograge, tagged request context, parameter filtering, ActiveSupport::Notifications, Sentry or another error tracker, health checks, log levels.
- Reads credentials
In this repository24 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- rails-activestorageSkill
- rails-apiSkill
- rails-auditSkill
- rails-clean-test-outputSkill
- rails-controllersSkill
- rails-coreSkill
- rails-database-performanceSkill
- rails-i18nSkill
- rails-jobsSkill
- rails-mailersSkill
- rails-migrationsSkill
- rails-modelsSkill
- rails-multi-tenancySkill
- rails-performanceSkill
- rails-philosophySkill
- rails-project-setupSkill
- rails-securitySkill
- rails-stimulusSkill
- rails-styleSkill
- rails-testingSkill
- rails-turboSkill
- rails-upgradeSkill
- rails-viewcomponentsSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 32
Add user context from the controller:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rails Logging and Observability
1. Lograge — structured one-line request logs
The default Rails request log is multi-line and noisy. Lograge collapses it to one structured JSON line per request, suitable for log aggregators (Datadog, Loki, Splunk).
# Gemfile
gem "lograge"
# config/initializers/lograge.rb
Rails.application.configure do
config.lograge.enabled = true
config.lograge.formatter = Lograge::Formatters::Json.new
config.lograge.custom_options = lambda do |event|
{
request_id: event.payload[:headers]["X-Request-Id"],
user_id: event.payload[:user_id],
account_id: event.payload[:account_id],
}.compact
end
endAdd user context from the controller:
class ApplicationController < ActionController::Base
def append_info_to_payload(payload)
super
payload[:user_id] = current_user&.id
payload[:account_id] = current_account&.id
end
end2. Tagged logging
Wrap every log line in a request with identifiers so you can grep a full request's story:
# config/application.rb
config.log_tags = [:request_id]
# For custom tags (Rails 7.1+)
config.log_tags = [
:request_id,
->(req) { req.session[:account_id] },
]In application code, use Rails.logger.tagged for sub-operations:
Rails.logger.tagged("BatchImport", batch.id) do
batch.rows.each { |row| import_row(row) }
endUse Lograge OR tagged logging, not both — Lograge replaces the multi-line format that
log_tags prefixes onto.
3. Log levels
| Level | Use for |
debug | Detailed internals, only useful when actively debugging |
info | Normal operations worth knowing happened |
warn | Something unexpected that didn't cause a failure |
error | A failure that was handled (rescued and recovered) |
fatal | Unrecoverable failure; the process is about to exit |
Rails.logger.info "Charge succeeded for order #{order.id}"
Rails.logger.warn "Payment gateway slow response: #{duration}ms"
Rails.logger.error "Stripe webhook signature mismatch — possible replay"Never use puts, p, or pp in application code. They bypass the logger entirely —
no tags, no level, no structured output. Use Rails.logger.debug instead. See
rails-toolkit:rails-clean-test-output for finding and removing stray puts calls.
4. Filtering sensitive parameters
Rails logs request parameters by default. Ensure secrets never appear in logs:
# config/application.rb
config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate,
:otp, :ssn, :credit_card, :cvv, :card_number,
# Add app-specific sensitive fields:
:stripe_payload, :webhook_body,
]For custom structured payloads logged manually, filter in append_info_to_payload:
def append_info_to_payload(payload)
super
payload[:api_key] = "[FILTERED]" if params[:api_key].present?
end5. ActiveSupport::Notifications — custom instrumentation
Instrument domain events so monitoring can track business metrics alongside infrastructure metrics:
# Emit an event from anywhere in the app
ActiveSupport::Notifications.instrument("order.charged", order_id: order.id, amount: amount)
# Subscribe in an initializer
ActiveSupport::Notifications.subscribe("order.charged") do |name, start, finish, _id, payload|
duration_ms = ((finish - start) * 1000).round
Rails.logger.info({ event: name, duration_ms: duration_ms }.merge(payload).to_json)
StatsD.histogram("order.charge.duration_ms", duration_ms)
endFor long-lived subscribers (always on), use subscribe; for one-shot debugging, use
subscribed (automatically removes the listener after the block exits).
6. Error tracking (Sentry / Honeybadger)
# Gemfile
gem "sentry-ruby"
gem "sentry-rails"
# config/initializers/sentry.rb
Sentry.init do |config|
config.dsn = Rails.application.credentials.sentry_dsn!
config.environment = Rails.env
config.before_send = lambda do |event, hint|
event.user = { id: Current.user&.id, email: Current.user&.email }
event
end
config.traces_sample_rate = Rails.env.production? ? 0.1 : 0
config.profiles_sample_rate = 0.1 # requires traces_sample_rate > 0
endManually capture handled exceptions with context:
begin
ExternalService.call(payload)
rescue ExternalService::RateLimitError => e
Sentry.capture_exception(e, extra: { payload_id: payload.id, retry_after: e.retry_after })
raise
endDon't rescue and swallow exceptions without re-raising or capturing. Silent rescues turn real bugs into mysterious data corruption.
7. Health check endpoint
Uptime monitors and load balancer health checks need a lightweight endpoint that validates the app's critical dependencies:
# config/routes.rb
get "/health", to: "health#show"
# app/controllers/health_controller.rb
class HealthController < ActionController::Base
allow_unauthenticated_access # Rails 8 auth generator
def show
checks = {
database: database_ok?,
cache: cache_ok?,
}
status = checks.values.all? ? :ok : :service_unavailable
render json: checks.merge(status: status), status: status
end
private
def database_ok?
ActiveRecord::Base.connection.execute("SELECT 1")
true
rescue StandardError
false
end
def cache_ok?
Rails.cache.write("health_check", "ok", expires_in: 5.seconds)
Rails.cache.read("health_check") == "ok"
rescue StandardError
false
end
endReturn 503 when a critical dependency is down — load balancers interpret any non-2xx as
unhealthy. Exclude the health endpoint from authentication and request logging (it fires
every 30 seconds and clutters logs):
# config/initializers/lograge.rb
config.lograge.ignore_actions = ["HealthController#show"]8. Silencing noisy logs
Silence selectively rather than raising the log level globally:
# Silence Active Job logs in development
ActiveJob::Base.logger = Logger.new(nil)
# Silence a noisy third-party gem
SemanticLogger[Devise].level = :error
# Silence a specific code path inline
Rails.logger.silence { SomeNoisyService.call }9. Log rotation
In production, configure rotation to prevent unbounded disk growth:
# config/environments/production.rb
config.logger = ActiveSupport::Logger.new(
Rails.root.join("log", "#{Rails.env}.log"),
5, # keep 5 rotated files
100.megabytes
)
config.log_level = :infoOr delegate to the OS: config.logger = Logger.new(STDOUT) and pipe to
journald/logrotate/your cloud provider's log collector.
Quick reference
| Pattern | Purpose |
lograge gem + JSON formatter | Structured one-line request logs for aggregators |
append_info_to_payload | Attach user/account context to every request log |
config.log_tags = [:request_id] | Correlate all lines within a request |
Rails.logger.tagged(...) | Correlate lines within a sub-operation |
config.filter_parameters | Keep secrets out of logs |
AS::Notifications.instrument | Emit and subscribe to custom domain events |
Sentry.capture_exception | Send handled errors with context to Sentry |
GET /health returning 503 | Load balancer / uptime monitoring |
Logger.new(nil) | Silence a specific noisy logger |
config.lograge.ignore_actions | Exclude health/ping endpoints from request logs |
Composing with other skills
- See rails-toolkit:rails-performance for ETags, fragment caching, and CDN headers — observability and caching are complementary layers of a production-ready app.
- See rails-toolkit:rails-clean-test-output for removing stray
puts/p/ppcalls that bypass the logger in test output. - See rails-toolkit:rails-security for credential storage (
credentials.sentry_dsn!) and filtering sensitive data at the framework level.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

