RubyでLogger作ってみた

RubyでLoggerっぽいものを作ってみました。実は、Rubyには標準ライブラリの中に「Loggerクラス」というものが有ります。じゃあ、要らないじゃん、ってことになるんですが、次の点でぼかぁ気に入らなかったのです。

  1. クラスなので、Newして使う
  2. インスタンスになってしまうと、複数のクラス内でLogを取るためには、グローバル変数に代入するしかない
  3. グローバル変数をクラス内で使うと、そのクラスがそのグローバル変数依存になってしまう
  4. グローバル変数は通常Mainで実行するファイルに書かれるため、それらのクラスがそのMainファイル前提になってしまい、汎用性が落ちる
  5. かといって非依存にするためには、各クラスにインスタンスを一々渡す必要が出る
  6. 気に入らない

Loggingの問題はよくAspect指向プログラミングの例題としても使われます。つまり複数のクラスで使用されることが前提な分けです。そこで、今回はModuleで作成しました。各クラスでRequireすればそれでOKということになります。

logger_aspect.rb Ver. 1.0

中身の構造は簡単です。例えるなら「私書箱」のようなものです。まず名前の付いたポストを用意します。そしてそれに、ただ文字列を投げ込みます。規定の量(デフォルトでは100行)を越えるとポストの中身を取り出し、Writerスレッドに渡します。Writerスレッドは、受け取ったLogを指定されたファイルに書き込みます。なお、既存ファイルは上書きされます。Rubyはネイティブスレッディングではありますが、GVL(Giant VM Lock)なので並行処理です。しかし、タイムシェアリング実行ではあるのでメインの実行が、IO Waitで邪魔されることもありません。

用意したAPIは次のような感じです。

setting([PATH], [LIMIT]): 設定を行います。

  • PATH = ログファイルを保存するフォルダ (デフォ値 "./")
  • LIMIT = ファイルに書き出すまでの蓄積行数 (デフォ値 100)

start() : ログ機能をオンにします。オンになっていなければ、その他のAPIは呼び出されても何もしません。

stop() : ログ機能を停止します

regist(POST, [PREFIX], [POSTFIX]) :ログを溜めるポストを作ります。

  • POST = 新しいポスト名
  • PREFIX = 保存するログファイルの先頭に付ける文字列 (デフォ値 "")
  • POSTFIX = 保存するログファイルの末尾に付ける文字列 (デフォ値 "")

post(POST, *MSG):ポストにメッセージを投入します。

  • POST = メッセージを投入するポスト名
  • *MSG = ポストに投入するメッセージです。Stringを可変長引数で受け取ります

flush_all()
flush(POST) : POSTの中身をファイルに書き出します。

  • POST = ファイルに書き出すポスト名

switch_all()
switch(POST): ログファイルを連番の新しいファイルにします。

  • POST = 対象となるポスト名

使い方はこんな感じ


使用例

2Dpoint.rb

class 2DPoint
  require "logger_aspect.rb"

  def initialize
    @x
    @y

    #LoggerAspectを使うための下準備
    LoggerAspect::regist(:point)
  end
  
  def set(x,y)
    @x = x
    @y = y

    LoggerAspect::post(:point, "GET POINT: #{@x}, #{@y}")
  end
end

main.rb

#メインの実行ファイル
$LOAD_PATH << "."
require "2Dpoint.rb"
def main
  require "logger_aspect.rb"
  
  LoggerAspect::setting("./logtest/")
  LoggerAspect::start()

  100.times{
    2p = 2DPoint.new
    2p.set(rand(50),rand(50))
  }
end
main()

こんな風に、2DPointクラスはmain.rbに非依存でありながらも、ログ機能を持つことができます。main.rbでLoggerAspect::start()しなければ、LoggerAspectは何もしません。

===追記
Module Aの中で標準Loggerのインスタンス作って、そのAをIncludeすればええやん、とか思ったが、まあいいや。w
ちゃんとスレッド書き出しもするようにしたし。