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.

LoggingRubyRailsActiveSupportStandard LoggerFramework

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

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