Rails Default Logger
Standard logging functionality in Rails. Automatically outputs detailed logs in development environment and minimal necessary logs in production. Includes information such as SQL queries, controller actions, and rendering times. Immediately available without configuration.
Library
Rails Default Logger
Overview
Rails Default Logger is the logging system built into the Ruby on Rails framework as standard. Based on ActiveSupport::TaggedLogging, and from Rails 7.1 onwards it provides simultaneous log broadcasting functionality to multiple output destinations as ActiveSupportBroadcastLogger. Automatically available from application startup, it provides comprehensive logging features specialized for Rails application development including appropriate log level management for development and production environments, tagged logging, structured logging, and SQL query tracking.
Details
Rails Default Logger has evolved since Rails 1.0 and currently provides mature logging system with industry-leading logging capabilities. Accessible as Rails.logger, it supports 5-level logging (debug, info, warn, error, fatal) and performs automatic environment-based configuration (development: debug, production: info). ActiveSupport::TaggedLogging allows attaching contextual information per request as tags, streamlining log tracking in large-scale web applications. Rails 7.1's BroadcastLogger functionality enables simultaneous output to files, standard output, and external log services.
Key Features
- Automatic Environment Configuration: Optimal log level auto-configuration for development, test, and production environments
- Tagged Logging: Automatic attachment of contextual information like request ID and user information
- SQL Query Tracking: Automatic logging of database queries and source code locations
- Multiple Output Destinations: Simultaneous broadcasting to files, standard output, and external services
- Format Control: Automatic management of environment-specific output formats and timestamps
- Performance Optimization: Overhead reduction through block notation lazy evaluation
Pros and Cons
Pros
- Complete integration with Rails framework for immediate use without configuration
- Consistent log management from development to production through automatic environment optimization
- Automated SQL query and source code tracking through ActiveRecord integration
- Efficient log analysis in large-scale applications through tagged logging
- Flexible configuration of multiple log output destinations via BroadcastLogger functionality
- Rich documentation and best practices through Rails Guides
Cons
- Framework dependency making it unusable outside Rails frameworks
- Advanced structured logging and external log service integration requires additional configuration
- Default configuration lacks automatic rotation for large log files
- Distributed log collection in microservice environments requires dedicated tools
- Special formats like JSON require additional formatter configuration
- Log volume tends to increase due to mixing Rails internal logs with custom logs
Reference Pages
- Rails Guides - Debugging Rails Applications
- Rails API Documentation - ActiveSupport::Logger
- Rails API Documentation - TaggedLogging
Code Examples
Basic Setup
# Rails.logger is automatically available (no configuration needed)
# Automatically initialized at application startup
# Basic log output
Rails.logger.debug "Debug info: #{variable.inspect}"
Rails.logger.info "Info: Processing completed"
Rails.logger.warn "Warning: High memory usage detected"
Rails.logger.error "Error: Database connection failed"
Rails.logger.fatal "Fatal: Shutting down application"
# Configuration verification
puts Rails.logger.level # Current log level
puts Rails.logger.class # BroadcastLogger (Rails 7.1+)
Basic Log Output
# Log output in controllers
class UsersController < ApplicationController
def index
logger.info "User list retrieval started"
@users = User.all
logger.debug "Retrieved user count: #{@users.count}"
# Performance-optimized block notation
logger.debug { "Detailed user info: #{@users.map(&:attributes).inspect}" }
logger.info "User list retrieval completed"
end
def create
logger.info "User creation started: #{params[:user]}"
@user = User.new(user_params)
if @user.save
logger.info "User creation successful: ID=#{@user.id}, Name=#{@user.name}"
redirect_to @user, notice: 'User was created successfully'
else
logger.warn "User creation failed: #{@user.errors.full_messages.join(', ')}"
render :new
end
end
private
def user_params
permitted_params = params.require(:user).permit(:name, :email, :age)
logger.debug { "Permitted parameters: #{permitted_params.inspect}" }
permitted_params
end
end
# Log output in models
class User < ApplicationRecord
after_create :log_user_creation
before_destroy :log_user_deletion
private
def log_user_creation
Rails.logger.info "New user created: ID=#{id}, Name=#{name}"
end
def log_user_deletion
Rails.logger.warn "User will be deleted: ID=#{id}, Name=#{name}"
end
end
# Log output in jobs
class EmailSendJob < ApplicationJob
def perform(user_id, email_type)
Rails.logger.info "Email send job started: UserID=#{user_id}, Type=#{email_type}"
user = User.find(user_id)
Rails.logger.debug { "User info: #{user.attributes.inspect}" }
case email_type
when 'welcome'
UserMailer.welcome_email(user).deliver_now
Rails.logger.info "Welcome email sent: #{user.email}"
when 'reminder'
UserMailer.reminder_email(user).deliver_now
Rails.logger.info "Reminder email sent: #{user.email}"
else
Rails.logger.error "Unknown email type: #{email_type}"
raise ArgumentError, "Unknown email type: #{email_type}"
end
rescue => e
Rails.logger.error "Error in email send job: #{e.message}"
Rails.logger.error "Backtrace: #{e.backtrace.first(5).join("\n")}"
raise
end
end
# Log output in service classes
class PaymentService
def self.process_payment(user, amount)
Rails.logger.info "Payment processing started: User=#{user.id}, Amount=#{amount}"
begin
# Payment processing simulation
if amount > 0
Rails.logger.debug "Payment amount validation: #{amount}"
# External API call processing
Rails.logger.debug "Calling external payment API..."
result = external_payment_api(user, amount)
Rails.logger.info "Payment processing successful: TransactionID=#{result[:transaction_id]}"
{ success: true, transaction_id: result[:transaction_id] }
else
Rails.logger.warn "Invalid payment amount: #{amount}"
{ success: false, error: "Invalid amount" }
end
rescue => e
Rails.logger.error "Payment processing error: #{e.message}"
Rails.logger.error "User: #{user.id}, Amount: #{amount}"
{ success: false, error: e.message }
end
end
private
def self.external_payment_api(user, amount)
# External API call simulation
{ transaction_id: SecureRandom.uuid }
end
end
Advanced Configuration (Environment Settings, Log Levels, Output Destinations)
# Basic configuration in config/application.rb
module MyApplication
class Application < Rails::Application
# Log level configuration
config.log_level = :info
# Log file size limit
config.log_file_size = 100.megabytes
# Log tags configuration
config.log_tags = [:request_id, :subdomain]
# Enable SQL query comments
config.active_record.query_log_tags_enabled = true
# Enable detailed job logs
config.active_job.verbose_enqueue_logs = true
end
end
# config/environments/development.rb
Rails.application.configure do
# Output all logs in development environment
config.log_level = :debug
# Enable detailed SQL query logs
config.active_record.verbose_query_logs = true
# Add log output to external file
config.logger = ActiveSupport::Logger.new("log/development_detailed.log")
config.logger.formatter = config.log_formatter
end
# config/environments/production.rb
Rails.application.configure do
# Minimal logs in production environment
config.log_level = :info
# Output to standard output (Docker/Kubernetes support)
if ENV["RAILS_LOG_TO_STDOUT"].present?
config.logger = ActiveSupport::Logger.new(STDOUT)
config.logger.formatter = config.log_formatter
end
# Custom log format
config.log_formatter = proc do |severity, datetime, progname, msg|
"#{datetime.iso8601} [#{severity}] #{progname}: #{msg}\n"
end
end
# config/initializers/logging.rb - Advanced log configuration
Rails.application.configure do
# Add custom log file
error_logger = ActiveSupport::Logger.new("log/errors.log")
error_logger.level = Logger::ERROR
# Multiple output destination configuration with BroadcastLogger
if Rails.logger.respond_to?(:broadcast_to)
Rails.logger.broadcast_to(error_logger)
end
# Log rotation configuration
if Rails.env.production?
Rails.logger = ActiveSupport::Logger.new(
"log/production.log",
10, # Number of files to keep
50.megabytes # File size limit
)
end
end
# Application-specific log tag configuration
Rails.application.configure do
config.log_tags = [
:request_id,
-> request { request.env["HTTP_USER_AGENT"]&.split&.first },
-> request { "User:#{request.session[:user_id]}" if request.session[:user_id] }
]
end
# Structured logging implementation example
class StructuredLogger
def self.log_event(event_name, data = {})
log_data = {
timestamp: Time.current.iso8601,
event: event_name,
data: data,
request_id: Current.request_id,
user_id: Current.user&.id
}
Rails.logger.info "[STRUCTURED] #{log_data.to_json}"
end
end
# Usage example
StructuredLogger.log_event("user_registration", {
user_id: user.id,
email: user.email,
referrer: request.referer
})
# Performance measurement logging
class PerformanceLogger
def self.measure(operation_name)
start_time = Time.current
Rails.logger.debug "[PERF] #{operation_name} started"
result = yield
end_time = Time.current
duration = ((end_time - start_time) * 1000).round(2)
Rails.logger.info "[PERF] #{operation_name} completed: #{duration}ms"
result
end
end
# Usage example
def expensive_operation
PerformanceLogger.measure("Database query") do
User.includes(:posts).where(active: true).limit(100)
end
end
Error Handling and Logging
# Global error handling in application_controller.rb
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_standard_error
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
private
def handle_standard_error(exception)
Rails.logger.error "Unexpected error occurred: #{exception.class.name}"
Rails.logger.error "Message: #{exception.message}"
Rails.logger.error "Request path: #{request.path}"
Rails.logger.error "Parameters: #{params.inspect}"
Rails.logger.error "Backtrace:"
exception.backtrace.first(10).each do |line|
Rails.logger.error " #{line}"
end
if Rails.env.production?
render json: { error: "Internal server error" }, status: 500
else
raise exception
end
end
def handle_not_found(exception)
Rails.logger.warn "Resource not found: #{exception.message}"
Rails.logger.warn "Request path: #{request.path}"
Rails.logger.warn "Parameters: #{params.inspect}"
render json: { error: "Not found" }, status: 404
end
end
# Validation error logging in models
class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
validates :name, presence: true, length: { minimum: 2 }
after_validation :log_validation_errors
private
def log_validation_errors
if errors.any?
Rails.logger.warn "User validation errors:"
errors.full_messages.each do |message|
Rails.logger.warn " - #{message}"
end
Rails.logger.warn " Attributes: #{attributes.inspect}"
end
end
end
# Retry error handling in jobs
class ReliableJob < ApplicationJob
retry_on StandardError, wait: :exponentially_longer, attempts: 5
def perform(data)
Rails.logger.info "ReliableJob execution started: #{data.inspect}"
begin
# Some processing
process_data(data)
Rails.logger.info "ReliableJob execution successful"
rescue => e
Rails.logger.error "Error in ReliableJob (attempt #{executions}/#{self.class.max_attempts}): #{e.message}"
Rails.logger.error "Data: #{data.inspect}"
if executions >= self.class.max_attempts
Rails.logger.fatal "ReliableJob reached maximum attempts: #{data.inspect}"
# Admin notification etc.
AdminMailer.job_failure_notification(self.class.name, data, e).deliver_now
end
raise # Continue retry
end
end
private
def process_data(data)
# Processing simulation
raise "Random error" if rand < 0.3
Rails.logger.debug "Data processing completed: #{data}"
end
end
# Custom exception handling class
class ErrorHandler
def self.handle_and_log(error, context = {})
error_id = SecureRandom.uuid
Rails.logger.error "Error ID: #{error_id}"
Rails.logger.error "Error class: #{error.class.name}"
Rails.logger.error "Error message: #{error.message}"
Rails.logger.error "Context: #{context.inspect}"
Rails.logger.error "Occurrence time: #{Time.current.iso8601}"
if error.respond_to?(:backtrace) && error.backtrace
Rails.logger.error "Backtrace:"
error.backtrace.first(15).each_with_index do |line, index|
Rails.logger.error " #{index + 1}: #{line}"
end
end
# Send to external error tracking service (e.g., Sentry, Bugsnag)
# ExternalErrorService.report(error, error_id: error_id, context: context)
error_id
end
end
# Usage example
begin
dangerous_operation()
rescue => e
error_id = ErrorHandler.handle_and_log(e, {
user_id: current_user&.id,
request_path: request.path,
params: params.to_unsafe_h
})
render json: {
error: "An error occurred during processing",
error_id: error_id
}, status: 500
end
Practical Examples (Production Operations)
# config/initializers/logging_enhancements.rb
# Advanced log configuration for production environment
# 1. Include unique ID per request in logs
Rails.application.config.log_tags = [
:request_id,
-> request {
user_id = request.session[:user_id] || request.headers['X-User-ID']
"User:#{user_id}" if user_id
},
-> request { "IP:#{request.remote_ip}" }
]
# 2. Detailed SQL query logs (development environment only)
if Rails.env.development?
Rails.application.config.active_record.query_log_tags_enabled = true
Rails.application.config.active_record.query_log_tags = [
:application,
:controller,
:action,
:job,
{
current_user: -> { Current.user&.id },
request_id: -> { Current.request_id }
}
]
end
# 3. Detailed job log configuration
Rails.application.config.active_job.verbose_enqueue_logs = true
# 4. External log service integration (e.g., Datadog, Splunk)
if Rails.env.production? && ENV['EXTERNAL_LOG_ENDPOINT']
require 'net/http'
require 'json'
class ExternalLogService
def self.send_log(level, message, context = {})
log_data = {
timestamp: Time.current.iso8601,
level: level,
message: message,
application: 'my-rails-app',
environment: Rails.env,
hostname: Socket.gethostname,
**context
}
Thread.new do
begin
uri = URI(ENV['EXTERNAL_LOG_ENDPOINT'])
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{ENV['LOG_API_TOKEN']}"
request.body = log_data.to_json
http.request(request)
rescue => e
Rails.logger.error "External log service send error: #{e.message}"
end
end
end
end
end
# Application-wide logging middleware
class LoggingMiddleware
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new(env)
start_time = Time.current
Rails.logger.info "[REQUEST] #{request.method} #{request.path}"
Rails.logger.info "[REQUEST] User-Agent: #{request.user_agent}"
Rails.logger.info "[REQUEST] Params: #{request.params.except('controller', 'action').inspect}"
status, headers, response = @app.call(env)
end_time = Time.current
duration = ((end_time - start_time) * 1000).round(2)
Rails.logger.info "[RESPONSE] Status: #{status}, Duration: #{duration}ms"
# Slow request warning
if duration > 1000
Rails.logger.warn "[SLOW_REQUEST] #{request.method} #{request.path} took #{duration}ms"
end
[status, headers, response]
end
end
# Add middleware
Rails.application.config.middleware.use LoggingMiddleware
# Database query monitoring
ActiveSupport::Notifications.subscribe 'sql.active_record' do |name, started, finished, unique_id, data|
duration = ((finished - started) * 1000).round(2)
if duration > 100 # Warn for queries over 100ms
Rails.logger.warn "[SLOW_QUERY] #{duration}ms: #{data[:sql]}"
Rails.logger.warn "[SLOW_QUERY] Binds: #{data[:binds].inspect}" if data[:binds]
end
if duration > 1000 # Error for queries over 1 second
Rails.logger.error "[VERY_SLOW_QUERY] #{duration}ms: #{data[:sql]}"
end
end
# Background job monitoring
ActiveSupport::Notifications.subscribe 'perform.active_job' do |name, started, finished, unique_id, data|
duration = ((finished - started) * 1000).round(2)
job = data[:job]
Rails.logger.info "[JOB] #{job.class.name} completed in #{duration}ms"
if duration > 30000 # Warn for jobs over 30 seconds
Rails.logger.warn "[SLOW_JOB] #{job.class.name} took #{duration}ms"
Rails.logger.warn "[SLOW_JOB] Arguments: #{job.arguments.inspect}"
end
end
# Memory usage monitoring (development environment)
if Rails.env.development?
Rails.application.config.after_initialize do
Thread.new do
loop do
memory_usage = `ps -o rss= -p #{Process.pid}`.to_i / 1024 # MB
Rails.logger.debug "[MEMORY] Current usage: #{memory_usage}MB"
if memory_usage > 500 # Warn for usage over 500MB
Rails.logger.warn "[MEMORY] High memory usage: #{memory_usage}MB"
end
sleep 60 # 1-minute interval
end
end
end
end
# Health check logging
class HealthCheckController < ApplicationController
def index
Rails.logger.info "[HEALTH_CHECK] Application status check"
checks = {
database: check_database,
redis: check_redis,
external_api: check_external_api
}
all_healthy = checks.values.all?
Rails.logger.info "[HEALTH_CHECK] Results: #{checks.inspect}"
if all_healthy
render json: { status: 'healthy', checks: checks }
else
Rails.logger.error "[HEALTH_CHECK] Some services are unhealthy"
render json: { status: 'unhealthy', checks: checks }, status: 503
end
end
private
def check_database
ActiveRecord::Base.connection.execute('SELECT 1')
true
rescue => e
Rails.logger.error "[HEALTH_CHECK] Database error: #{e.message}"
false
end
def check_redis
if defined?(Redis)
Redis.new.ping == 'PONG'
else
true # OK if Redis not used
end
rescue => e
Rails.logger.error "[HEALTH_CHECK] Redis error: #{e.message}"
false
end
def check_external_api
# Check important dependent services like external APIs
true
rescue => e
Rails.logger.error "[HEALTH_CHECK] External API error: #{e.message}"
false
end
end