Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

Rubyでのログ出力入門:LoggerとLogrageの使い方

ロギングは、アプリケーション開発において最も重要なタスクの一つです。ログは以下のような場面で活用されます。

  • アプリ内部で何が起きているかを把握したいとき
  • アプリケーションを監視したいとき
  • 特定のデータに関するメトリクスを収集したいとき

新しいプログラミング言語を学ぶ際、最初に選ばれるのはその言語がネイティブに備えているロギング機構でしょう。標準機能は通常、扱いやすく、ドキュメントも充実しており、コミュニティでも広く使われています。

ただし、ログデータの内容や扱い方は、企業の方針、ビジネスの性質、アプリケーションの種類によって大きく異なります。そのため、自分やチームが選択したロギングソリューションが運用全体にどのような影響を与えるかを理解することが非常に重要です。

本記事では、RubyおよびRailsで利用できるロギングの選択肢を、標準ライブラリから人気のサードパーティ製フレームワークまで幅広く紹介します。それでは始めましょう!

Rubyの標準機能によるロギング

Rubyには、ロギングを扱うための標準機能として「コンソールへの出力コマンド(主にコマンドライン用途向け)」と「Loggerクラス」の2つが用意されています。

それぞれ詳しく見ていきましょう。

コンソールへの出力

Rubyでテキストをコンソールに出力する一般的な方法は4つあります。putsprintpppです。

putsメソッドは、渡した値を出力した後に改行を加えます。

2.7.0 :001 > puts "Hey, I'm a log!"
Hey, I'm a log!
 => nil

printメソッドはputsと似ていますが、引数を常にto_sメソッドで文字列に変換してから出力する点が異なります。

pppはどちらも渡されたオブジェクトを変換せずにそのまま出力します。違いは、後者のppが出力を見やすいように整形(インデント)してくれる点です。

アプリケーションログの出力

サーバー上で動作するアプリケーションの場合、コンソールへ直接出力するのは現実的ではありません。

そこで登場するのが、はるかに柔軟なLoggerクラスです。

「debug」レベルのログを出力する例は次のとおりです。

require "logger"
logger = Logger.new(STDOUT)
logger.debug("I'm a debug log")

必要に応じて、STDOUTへの出力ではなく、ファイルへの保存やログ収集ツールへの送信などにも設定変更できます。

ちなみに「debug」というのはログレベルのことです。ログレベルとは、「このログメッセージは特定の種類のイベントに関連している」とシステムに伝えるための仕組みです。標準ではFatal、Error、Warn、Info、Debug、Unknownの6つのログレベルが用意されています。

logger.debug("I'm a debug log")
logger.info("I'm an info log")
logger.warn("I'm a warn log")
logger.error("I'm an error log: error message")
logger.fatal("I'm a fatal log")

ログ出力結果を見ると、各行にログレベル、プロセスID、タイムスタンプが付加されていることがわかります。

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

さらに便利な機能として、Loggerオブジェクトに特定のログレベルを設定できます。

logger.level = Logger::WARN

こう設定すると、Loggerはwarn以上の重要度を持つログのみを処理します。これは本番環境で特に有用です。サイズやセキュリティ上の理由から、debugログを保存したくないケースは多いためです。

この設定を行うと、出力は次のようになります。

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

ログのカスタマイズ

ログをカスタマイズしたい場合も、選択肢は豊富にあります。たとえば日時の表示形式を変更するには、デフォルトのフォーマットを上書きします。

logger.datetime_format = "%Y-%m-%d %H:%M"

出力例は以下のとおりです。

W, [2020-07-28 10:50#87786]  WARN -- : I'm a warn log

日時フォーマットは標準仕様に従っているため、変更の際は必ず公式ドキュメントを参照してください。

しかし場合によっては、ログの形式を完全に変更したいこともあるでしょう。そんなときに役立つのがFormatterクラスです。開発者はこのクラスを使って、Loggerがログ出力に使用するデフォルトのテンプレートを上書きできます。

たとえば、アプリが中央集権型のログ分析ツールを利用していて、すべてのログをname=value形式で出力する必要があるとします。その場合の新しいフォーマッタのコードは次のようになります。

logger.formatter = proc do |severity, datetime, progname, msg|
    date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
    "date=[#{date_format}] severity=#{severity.ljust(5)} pid=##{Process.pid} message='#{msg}'\n"
end

ここではprocを使ってすべてのログを横取りし、デフォルトの出力形式を望ましい形式に変更しています。その結果、出力は次のようになります。

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

わずか数行のコードで、アプリのすべてのログをツールでインデックス可能な形式にできました。

JSON形式でのログ出力

Formatterの力を借りれば、ログを好きな出力形式にカスタマイズするのは簡単です。たとえば、JSONモジュールを使ってログをJSON形式で出力することもできます。

require "json"
logger.formatter = proc do |severity, datetime, progname, msg|
    date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
    JSON.dump(date: "#{date_format}", severity:"#{severity.ljust(5)}", pid:"##{Process.pid}", message: msg) + "\n"
end

出力されるログは次のとおりです。

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

ファイルへのログ出力

前述のとおり、Loggerのコンストラクタは第1引数として、ログの保存先を受け取ります。

ログをファイルに保存したい場合は、単純にファイル名を指定するだけです。

require 'logger'
logger = Logger.new('my_logs.log')

これにより、このコードを含むRubyファイルと同じフォルダ内にmy_logs.logという新しいファイルが作成されます。

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

さらに、このクラスは保持ポリシーなど、さまざまなカスタムオプションもサポートしています。以下の例をご覧ください。

# 現在の週のデータを保持する。
Logger.new('my_weekly_logs.log', 'weekly')
# 今日と過去2ヶ月分のデータを保持する。
Logger.new('my_latest_2_months_logs.log', 2, 'monthly')
# ファイルサイズが26GBを超えたらログを初期化する。
Logger.new('my_custom_logs.log', 0, 20 * 1024 * 1024 * 1024)

第2引数として、Loggerはログを保持する頻度、または削除するタイミングの定義を受け付けます。

1つ目のオブジェクトでは、ログは1週間だけ保持されます。2つ目は2ヶ月間の範囲で保存し、3つ目は第3引数に基づいてファイルサイズのみを判定します(たとえば20GBに達したら破棄されます)。

Ruby on Railsでのロギング

これまでに紹介したLoggerの利点のほとんどは、Railsアプリでもそのまま利用できます。Rails版のLoggerは、Rubyのネイティブクラスをシンプルに拡張したものです。

加えて、Railsには開発者がログを複数のロガーにブロードキャストできる便利な機能があります。複数のロギングライブラリを併用したい場合も、Railsのloggerだけで対応可能です。

custom_logger = Logger.new(STDOUT)
Rails.logger.extend(ActiveSupport::Logger.broadcast(custom_logger))

これは、ログの送信先について柔軟な判断が必要な場合にも役立ちます。アプリの一部の機能では、別の場所や別の形式でログを出力したいことがあるからです。

サードパーティ製ロギングフレームワークの選択

Rubyの標準ロガーが求める要件を満たさない場合でも、選択肢は十分にあります。

サードパーティ製のロギングフレームワークには多くの選択肢がありますが、中でも最も人気があるのがLogrageです。早速見ていきましょう。

Lograge入門

LogrageはRailsアプリ向けに作られたフレームワークです。素のRubyで開発している場合は、適切な選択ではないかもしれません。

インストールは、次のgemを追加するだけです。

bundle add lograge

続いて、config/initializers/lograge.rbに新しい設定ファイルを作成し、以下のコードを記述します。

Rails.application.configure do
    config.lograge.enabled = true
    config.lograge.custom_options = lambda do |event|
        { time: event.time }
    end
end

この設定によりLogrageが有効になり、フレームワークにとってのイベントであるログが出力される時刻が定義されます。

Railsアプリを起動して最初のエンドポイントにアクセスすると、Logrageがリクエストの概要を出力します。Lograge導入前後のログを比較すると、それぞれ下図のような違いがあります。

Rubyでのログ出力をマスターする:LoggerとLogrageの使い方徹底解説

繰り返しになりますが、Logrageは銀の弾丸ではありません。むしろ非常に意見の強い(opinionated)フレームワークです。そのため、おそらく標準のLoggerや、好みの他のフレームワークと併用することになるでしょう。

まとめ

ここまで読んでいただくと、ロギングがプロジェクトにとって極めて重要でありながら、意外と軽視されがちな要素だということが明確になったのではないでしょうか。ネイティブかどうかにかかわらず、使用するツールを深く理解することは、より良い成果につながります。

新しいフレームワークを採用する際は、必ず十分なテストを行いましょう。プラットフォームによっては、優れた標準機能が最初からバンドルされていることもあります。そうでない場合は、既存のものと統合できる他のフレームワークが利用可能かどうかを見極めてください。

それでは、良いロギングライフを!

  1. TCMallocを使ってRubyのメモリ割り当てをプロファイリングする方法

    Rubyでは、メモリの割り当てはどのように行われているのでしょうか? Rubyは「ページ(page)」と呼ばれる塊単位でメモリを確保し、新しく生成されたオブジェクトはこのページの中に格納されていきます。 しかし、 ページがいっぱいになると、さらなるメモリが必要になります。 そのときRubyは、malloc関数を使ってオペレーティングシステム(OS)に追加のメモリを要求します。 このmalloc関数はOS標準で提供されているものですが、代替となる実装に差し替えることも可能です。 その代表的な実装のひとつが、Google製のTCMallocです。 TCMallocは「gperftools(Goog

  2. Rubyでパーサーを自作する方法!StringScannerを使った実装手順を徹底解説

    パース(構文解析)とは、文字列の集まりから意味を読み取り、プログラムが扱える形のデータへと変換する技術です。正規表現でも文字列の解析は可能ですが、すべての場面に適しているわけではありません。 たとえば、正規表現でHTMLを解析するのはあまり良い方法ではないというのは、プログラミング界隈ではよく知られた話です。 Rubyにはnokogiriという強力なライブラリがあり、HTMLの解析はこれに任せられます。しかし、自分でパーサーを一から作ってみると、文字列処理や構文解析の仕組みについて多くのことを学べます。それでは早速始めていきましょう! Rubyでのパースの基本:StringScannerクラス