Rails Default Logger

Rails標準のロギング機能。開発環境では詳細なログ出力、本番環境では必要最小限のログを自動出力。SQLクエリ、コントローラアクション、レンダリング時間等の情報を含む。設定なしで即座に利用可能。

ロギングRubyRailsActiveSupport標準ロガーフレームワーク

ライブラリ

Rails Default Logger

概要

Rails Default Loggerは、Ruby on Railsフレームワークに標準で組み込まれているロギングシステムです。ActiveSupport::TaggedLoggingをベースとし、Rails 7.1以降はActiveSupportBroadcastLoggerとして複数出力先への同時ログ配信機能を提供します。アプリケーション起動時から自動的に利用可能で、開発・本番環境での適切なログレベル管理、タグ付きログ、構造化ログ、SQLクエリ追跡など、Railsアプリケーション開発に特化した包括的なロギング機能を提供します。

詳細

Rails Default LoggerはRails 1.0から進化を続け、現在は業界最高水準のロギング機能を提供する成熟したシステムです。Rails.loggerとしてアクセス可能で、debug、info、warn、error、fatalの5段階ログレベルをサポートし、環境別の自動設定(開発:debug、本番:info)を行います。ActiveSupport::TaggedLoggingによりリクエスト毎のコンテキスト情報をタグとして付与でき、大規模Webアプリケーションでのログ追跡を効率化します。Rails 7.1のBroadcastLogger機能により、ファイル・標準出力・外部ログサービスへの同時出力も可能です。

主な特徴

  • 自動環境設定: 開発・テスト・本番環境での最適なログレベル自動設定
  • タグ付きログ: リクエストID、ユーザー情報等のコンテキスト自動付与
  • SQLクエリ追跡: データベースクエリとソースコード位置の自動ログ
  • 複数出力先対応: ファイル、標準出力、外部サービスへの同時配信
  • フォーマット制御: 環境別の出力形式とタイムスタンプ自動管理
  • パフォーマンス最適化: ブロック記法による遅延評価でオーバーヘッド削減

メリット・デメリット

メリット

  • Railsフレームワークとの完全統合で設定不要の即座利用
  • 環境別の自動最適化により開発から本番まで一貫したログ管理
  • ActiveRecord統合によるSQLクエリとソースコード追跡の自動化
  • タグ付きログによる大規模アプリケーションでの効率的ログ分析
  • BroadcastLogger機能による複数ログ出力先の柔軟な設定
  • Rails Guidesによる豊富なドキュメントとベストプラクティス

デメリット

  • Rails以外のフレームワークでは利用不可のフレームワーク依存
  • 高度な構造化ログや外部ログサービス統合には追加設定が必要
  • デフォルト設定では大容量ログファイルの自動ローテーション未対応
  • マイクロサービス環境での分散ログ収集には専用ツールが必要
  • JSON形式など特殊フォーマットには追加のフォーマッター設定が必要
  • Rails内部ログと独自ログの混在により、ログ量が増加しがち

参考ページ

書き方の例

基本セットアップ

# Rails.loggerは自動的に利用可能(設定不要)
# アプリケーション起動時に自動初期化される

# 基本的なログ出力
Rails.logger.debug "デバッグ情報: #{variable.inspect}"
Rails.logger.info "情報: 処理が完了しました"
Rails.logger.warn "警告: メモリ使用量が高くなっています"
Rails.logger.error "エラー: データベース接続に失敗"
Rails.logger.fatal "致命的: アプリケーションを停止します"

# 設定確認
puts Rails.logger.level        # 現在のログレベル
puts Rails.logger.class        # BroadcastLogger (Rails 7.1+)

基本的なログ出力

# コントローラでのログ出力
class UsersController < ApplicationController
  def index
    logger.info "ユーザー一覧取得開始"
    
    @users = User.all
    logger.debug "取得ユーザー数: #{@users.count}"
    
    # パフォーマンスの良いブロック記法
    logger.debug { "詳細なユーザー情報: #{@users.map(&:attributes).inspect}" }
    
    logger.info "ユーザー一覧取得完了"
  end
  
  def create
    logger.info "ユーザー作成開始: #{params[:user]}"
    
    @user = User.new(user_params)
    
    if @user.save
      logger.info "ユーザー作成成功: ID=#{@user.id}, 名前=#{@user.name}"
      redirect_to @user, notice: 'ユーザーが作成されました'
    else
      logger.warn "ユーザー作成失敗: #{@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_params.inspect}" }
    permitted_params
  end
end

# モデルでのログ出力
class User < ApplicationRecord
  after_create :log_user_creation
  before_destroy :log_user_deletion
  
  private
  
  def log_user_creation
    Rails.logger.info "新しいユーザーが作成されました: ID=#{id}, 名前=#{name}"
  end
  
  def log_user_deletion
    Rails.logger.warn "ユーザーが削除されます: ID=#{id}, 名前=#{name}"
  end
end

# ジョブでのログ出力
class EmailSendJob < ApplicationJob
  def perform(user_id, email_type)
    Rails.logger.info "メール送信ジョブ開始: ユーザーID=#{user_id}, タイプ=#{email_type}"
    
    user = User.find(user_id)
    Rails.logger.debug { "ユーザー情報: #{user.attributes.inspect}" }
    
    case email_type
    when 'welcome'
      UserMailer.welcome_email(user).deliver_now
      Rails.logger.info "ウェルカムメール送信完了: #{user.email}"
    when 'reminder'
      UserMailer.reminder_email(user).deliver_now
      Rails.logger.info "リマインダーメール送信完了: #{user.email}"
    else
      Rails.logger.error "不明なメールタイプ: #{email_type}"
      raise ArgumentError, "Unknown email type: #{email_type}"
    end
  rescue => e
    Rails.logger.error "メール送信ジョブでエラー: #{e.message}"
    Rails.logger.error "バックトレース: #{e.backtrace.first(5).join("\n")}"
    raise
  end
end

# サービスクラスでのログ出力
class PaymentService
  def self.process_payment(user, amount)
    Rails.logger.info "決済処理開始: ユーザー=#{user.id}, 金額=#{amount}"
    
    begin
      # 決済処理のシミュレーション
      if amount > 0
        Rails.logger.debug "決済金額検証: #{amount}円"
        
        # 外部API呼び出し等の処理
        Rails.logger.debug "外部決済API呼び出し中..."
        result = external_payment_api(user, amount)
        
        Rails.logger.info "決済処理成功: トランザクションID=#{result[:transaction_id]}"
        { success: true, transaction_id: result[:transaction_id] }
      else
        Rails.logger.warn "不正な決済金額: #{amount}"
        { success: false, error: "Invalid amount" }
      end
    rescue => e
      Rails.logger.error "決済処理エラー: #{e.message}"
      Rails.logger.error "ユーザー: #{user.id}, 金額: #{amount}"
      { success: false, error: e.message }
    end
  end
  
  private
  
  def self.external_payment_api(user, amount)
    # 外部API呼び出しのシミュレーション
    { transaction_id: SecureRandom.uuid }
  end
end

高度な設定(環境設定、ログレベル、出力先)

# config/application.rb での基本設定
module MyApplication
  class Application < Rails::Application
    # ログレベルの設定
    config.log_level = :info
    
    # ログファイルサイズの制限
    config.log_file_size = 100.megabytes
    
    # ログタグの設定
    config.log_tags = [:request_id, :subdomain]
    
    # SQLクエリコメントの有効化
    config.active_record.query_log_tags_enabled = true
    
    # 詳細なジョブログの有効化
    config.active_job.verbose_enqueue_logs = true
  end
end

# config/environments/development.rb
Rails.application.configure do
  # 開発環境ではすべてのログを出力
  config.log_level = :debug
  
  # SQLクエリの詳細ログ有効化
  config.active_record.verbose_query_logs = true
  
  # 外部ファイルへのログ出力追加
  config.logger = ActiveSupport::Logger.new("log/development_detailed.log")
  config.logger.formatter = config.log_formatter
end

# config/environments/production.rb
Rails.application.configure do
  # 本番環境では必要最小限のログ
  config.log_level = :info
  
  # 標準出力への出力(Docker/Kubernetes対応)
  if ENV["RAILS_LOG_TO_STDOUT"].present?
    config.logger = ActiveSupport::Logger.new(STDOUT)
    config.logger.formatter = config.log_formatter
  end
  
  # ログフォーマットのカスタマイズ
  config.log_formatter = proc do |severity, datetime, progname, msg|
    "#{datetime.iso8601} [#{severity}] #{progname}: #{msg}\n"
  end
end

# config/initializers/logging.rb - 高度なログ設定
Rails.application.configure do
  # カスタムログファイルの追加
  error_logger = ActiveSupport::Logger.new("log/errors.log")
  error_logger.level = Logger::ERROR
  
  # BroadcastLoggerによる複数出力先設定
  if Rails.logger.respond_to?(:broadcast_to)
    Rails.logger.broadcast_to(error_logger)
  end
  
  # ログローテーション設定
  if Rails.env.production?
    Rails.logger = ActiveSupport::Logger.new(
      "log/production.log",
      10,                    # 保持ファイル数
      50.megabytes          # ファイルサイズ上限
    )
  end
end

# アプリケーション固有のログタグ設定
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

# 構造化ログの実装例
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

# 使用例
StructuredLogger.log_event("user_registration", {
  user_id: user.id,
  email: user.email,
  referrer: request.referer
})

# パフォーマンス測定ログ
class PerformanceLogger
  def self.measure(operation_name)
    start_time = Time.current
    Rails.logger.debug "[PERF] #{operation_name} 開始"
    
    result = yield
    
    end_time = Time.current
    duration = ((end_time - start_time) * 1000).round(2)
    
    Rails.logger.info "[PERF] #{operation_name} 完了: #{duration}ms"
    result
  end
end

# 使用例
def expensive_operation
  PerformanceLogger.measure("データベースクエリ") do
    User.includes(:posts).where(active: true).limit(100)
  end
end

エラーハンドリングとログ

# 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 "予期しないエラーが発生: #{exception.class.name}"
    Rails.logger.error "メッセージ: #{exception.message}"
    Rails.logger.error "リクエストパス: #{request.path}"
    Rails.logger.error "パラメータ: #{params.inspect}"
    Rails.logger.error "バックトレース:"
    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 "リソースが見つかりません: #{exception.message}"
    Rails.logger.warn "リクエストパス: #{request.path}"
    Rails.logger.warn "パラメータ: #{params.inspect}"
    
    render json: { error: "Not found" }, status: 404
  end
end

# モデルでのバリデーションエラーログ
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 バリデーションエラー:"
      errors.full_messages.each do |message|
        Rails.logger.warn "  - #{message}"
      end
      Rails.logger.warn "  属性: #{attributes.inspect}"
    end
  end
end

# ジョブでのリトライエラーハンドリング
class ReliableJob < ApplicationJob
  retry_on StandardError, wait: :exponentially_longer, attempts: 5
  
  def perform(data)
    Rails.logger.info "ReliableJob 実行開始: #{data.inspect}"
    
    begin
      # 何らかの処理
      process_data(data)
      Rails.logger.info "ReliableJob 実行成功"
    rescue => e
      Rails.logger.error "ReliableJob でエラー (試行 #{executions}/#{self.class.max_attempts}): #{e.message}"
      Rails.logger.error "データ: #{data.inspect}"
      
      if executions >= self.class.max_attempts
        Rails.logger.fatal "ReliableJob 最大試行回数に達しました: #{data.inspect}"
        # 管理者への通知等
        AdminMailer.job_failure_notification(self.class.name, data, e).deliver_now
      end
      
      raise  # リトライを継続
    end
  end
  
  private
  
  def process_data(data)
    # 処理のシミュレーション
    raise "Random error" if rand < 0.3
    Rails.logger.debug "データ処理完了: #{data}"
  end
end

# カスタム例外処理クラス
class ErrorHandler
  def self.handle_and_log(error, context = {})
    error_id = SecureRandom.uuid
    
    Rails.logger.error "エラーID: #{error_id}"
    Rails.logger.error "エラークラス: #{error.class.name}"
    Rails.logger.error "エラーメッセージ: #{error.message}"
    Rails.logger.error "コンテキスト: #{context.inspect}"
    Rails.logger.error "発生時刻: #{Time.current.iso8601}"
    
    if error.respond_to?(:backtrace) && error.backtrace
      Rails.logger.error "バックトレース:"
      error.backtrace.first(15).each_with_index do |line, index|
        Rails.logger.error "  #{index + 1}: #{line}"
      end
    end
    
    # 外部エラートラッキングサービスへの送信(例:Sentry、Bugsnag)
    # ExternalErrorService.report(error, error_id: error_id, context: context)
    
    error_id
  end
end

# 使用例
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: "処理中にエラーが発生しました", 
    error_id: error_id 
  }, status: 500
end

実用例(本番運用対応)

# config/initializers/logging_enhancements.rb
# 本番環境での高度なログ設定

# 1. リクエスト毎のユニークIDをログに含める
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. SQLクエリの詳細ログ(開発環境のみ)
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. ジョブの詳細ログ設定
Rails.application.config.active_job.verbose_enqueue_logs = true

# 4. 外部ログサービス統合(例: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 "外部ログサービス送信エラー: #{e.message}"
        end
      end
    end
  end
end

# アプリケーション全体のログミドルウェア
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"
    
    # 遅いリクエストの警告
    if duration > 1000
      Rails.logger.warn "[SLOW_REQUEST] #{request.method} #{request.path} took #{duration}ms"
    end
    
    [status, headers, response]
  end
end

# ミドルウェアの追加
Rails.application.config.middleware.use LoggingMiddleware

# データベースクエリの監視
ActiveSupport::Notifications.subscribe 'sql.active_record' do |name, started, finished, unique_id, data|
  duration = ((finished - started) * 1000).round(2)
  
  if duration > 100  # 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  # 1秒以上のクエリはエラー
    Rails.logger.error "[VERY_SLOW_QUERY] #{duration}ms: #{data[:sql]}"
  end
end

# バックグラウンドジョブの監視
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  # 30秒以上のジョブを警告
    Rails.logger.warn "[SLOW_JOB] #{job.class.name} took #{duration}ms"
    Rails.logger.warn "[SLOW_JOB] Arguments: #{job.arguments.inspect}"
  end
end

# メモリ使用量の監視(開発環境)
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  # 500MB以上で警告
          Rails.logger.warn "[MEMORY] High memory usage: #{memory_usage}MB"
        end
        
        sleep 60  # 1分間隔
      end
    end
  end
end

# ヘルスチェック用ログ
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  # Redis未使用の場合はOK
    end
  rescue => e
    Rails.logger.error "[HEALTH_CHECK] Redis error: #{e.message}"
    false
  end
  
  def check_external_api
    # 外部API等の重要な依存サービスのチェック
    true
  rescue => e
    Rails.logger.error "[HEALTH_CHECK] External API error: #{e.message}"
    false
  end
end