RubyでLoggerっぽいものを作ってみました。実は、Rubyには標準ライブラリの中に「Loggerクラス」というものが有ります。じゃあ、要らないじゃん、ってことになるんですが、次の点でぼかぁ気に入らなかったのです。
- クラスなので、Newして使う
- インスタンスになってしまうと、複数のクラス内でLogを取るためには、グローバル変数に代入するしかない
- グローバル変数をクラス内で使うと、そのクラスがそのグローバル変数依存になってしまう
- グローバル変数は通常Mainで実行するファイルに書かれるため、それらのクラスがそのMainファイル前提になってしまい、汎用性が落ちる
- かといって非依存にするためには、各クラスにインスタンスを一々渡す必要が出る
- 気に入らない
Loggingの問題はよくAspect指向プログラミングの例題としても使われます。つまり複数のクラスで使用されることが前提な分けです。そこで、今回はModuleで作成しました。各クラスでRequireすればそれでOKということになります。
中身の構造は簡単です。例えるなら「私書箱」のようなものです。まず名前の付いたポストを用意します。そしてそれに、ただ文字列を投げ込みます。規定の量(デフォルトでは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
ちゃんとスレッド書き出しもするようにしたし。