2008-02-29

Rswatch なんてものを書いてみました

※ 実際には3/1なのにあえて4年に一度の肉の日にぶつけて書くメソッド :)

Swatch あるじゃないですか。兄さん、時計じゃないっすよ。そんなベタなボケは要らないっすよ。ログ監視ツールっすよ。

[SWATCH: The Simple WATCHer of Logfiles][1]

でまぁこれは皆さんご存知の通り Perl で書かれてるわけっすよ。

昔からあるツールで非常に有名なわけですけど、例によって Perl なので(?)依存モジュールがいくつかあって、まぁ CPAN で入れればいいのは分かってるけど、CPAN モジュールってシステム管理的には超邪魔くさくね?といつも思ってる自分としては OS 全体のパッケージ管理システムに入ってないものは入れたくないわけっすよ。でも監視はしたいんす。ずるずる先延ばしにしてきたんすけど、決心して

Ruby で似たようなもの書けばいいじゃん

ということで書きました。

[http://svn.coderepos.org/share/lang/ruby/misc/rswatch.rb][2]

やっと動くものを CodeRepos にうpできたよ! 車輪の再発明上等!

で、ドキュメントもサンプルも何もないんでここに下書きを書いておきます。

必要なもの

  • Ruby と Ruby の知識
  • File::Tail モジュール [http://file-tail.rubyforge.org/][3]
    • 結局依存モジュールはあるわけですけど、pure Ruby 一個なのでご勘弁を。
  • 監視したいログに関する設定を Ruby か YAML で書く
  • 監視に利用するルールとアラート対象のログを見つけたときのアクションを Ruby で書く

意外にハードルが高くなってしまいました。これはひとえに

中身がまだ何もない

からです。

起動方法

Usage:
  rswatch.rb [options]

Options:
  --dry-run      -n  parse config, but no action
  --lib-file     -l  LIBFILE
  --config-file  -f  CONFIGFILE
  --config-yaml  -y  YAMLFILE
  --daemon       -d  daemon mode
  --debug        -D
  --help         -h
  --version      -v

設定を -f で Ruby の文法のファイルか、-y で YAML のファイルで与えます。-l で与えるライブラリがキモなんですがこれは後で触れます。

動作する Ruby のバージョンと File::Tail の置き方

Ruby 1.6.8 と 1.8.6 で動作確認しています。これを書いたいちばんの理由は Swatch はパッケージに入ってないけど Ruby 1.6 なら入ってるよ、という環境でどうにか監視したかったからなので、1.6 で動かない書き方は不許可、という枷を自分に課しています。そうです、あえての GetoptLong です。

File::Tail は gem でインストールできますが、アーカイブを取ってきて $LOAD_PATH の中で file/tail の階層になる場所に置いておけば、gem のない環境でも大丈夫です。一応 rswatch.rb そのものの置かれているディレクトリにもパスを通すようにしてあるので、

rswatch.rb
file/
     tail.rb

という形で置いてくれれば動きます。

設定の書き方

こういう構造の Hash を与えます。

# -*- ruby -*-

{
  # recipe name
  'simpleecho' => {
#    'disabled'   => true,
    'watchfiles' => [ '/var/log/apache22/httpd-access.log',
                      '/var/log/apache22/httpd-error.log',
                      '/var/log/messages',
                    ],
    'rules'      => 'nonzero?',
    'backline'   => 1,
    'action'     => 'echo',
    'max_repeat' => 3,
#    'ignore_first' => false,
  },
  RECIPENAME => {
  }
}

監視対象群のひとかたまりをレシピと呼びます。レシピには何をしたいのか分かりやすい、適当な名前をつけてやってください。中身は

disabledこのレシピを無効とするかどうか
watchfiles監視対象ファイル群
rules監視ルールを1つ以上。複数ある場合はANDでチェックします。
backline一度に参照できるログの行数。デフォルトは 1
actionルールが成り立った場合に実行するメソッド
max_repeat上の action を何回まで実行するか。デフォルトは無制限。
ignore_first起動後最初にルールにマッチするログを見つけてもそれを無視するかどうか

ライブラリを用意してください

今のところ rule も action もお飾りのものしかありません。申し訳ありませんが

# -*- ruby -*-

module Rswatch
  class Rule < RuleBase
    #
    # [Param] Array backlog
    #
    def example( backlog )
    end
  end

  class Action < ActionBase
    #
    # [Param] String logname
    # [Param] Array  backlog
    #
    def example( logname, backlog )
    end
  end
end

こんな Ruby のファイルを用意して、この中で自由に書いてください。

rules, action に指定できる文字列はここで定義されたメソッド名になります。

で、起動時の -l オプションでこのファイルを指定してやります。backlog は backline で指定した行数分のログの中身が Array で渡ってきます。//m =~ backlog.join で一刀両断にするもよし、好きに料理してください。

残念ながらログファイルの解析に使える道具は Ruby 標準のものしかありません。Apache のログをサクっと解析したいとか syslog を手早く解析したいとかそういうことをしたい人は自分でなんとかしてください。

悩んでいるところ

ignore_first の値

ignore_first の値は変かもです。例えばトラブル復旧後に再起動したとかいう場合、最後のログがルールにマッチするものになっていることがあります。すると復旧後すぐ警告が発生する可能性があります。今のところデフォルトでこの値を true にしてあるので回避できますが、これは false にしておいて監視対象のログの種類によってユーザーにスイッチしてもらった方がいいような気もしています。

テスト段階ではログのほとんどがルールにマッチしちゃう状態になっていたので true にしていましたが、今考えると false が正しい気もします。ご意見ある方ツッコミください。

action の与え方

action の与え方は二つの点で悩んでいます。

  1. 複数の action を与えられるようにすべきか?
  2. いっそ Ruby のブロックを与えられるようにしようか?

特に二番目の点で悩んでいます。と言うのも、個人的に swatch では awk 風の書式の中で決まった処理しか書けないのが不便だなと感じているからです。exec や pipe を使って外部のプログラムは呼べますが、やはり不自由な感じは否めません。だったら Ruby でそのまま書けた方がいんじゃね? 設定ファイルで action に Ruby のブロックをそのまま書ければいいじゃんとも思うのですが、そうなると逆によくある処理を名前だけで呼び出すという方法と同居させにくくなりますし、YAML で設定を書くのが難しくなります。[^1]

あ、String だったら Ruby コード、Symbol だったら action名、という分け方はありかも。確か Ruby の Syck は Symbol も解釈できたはず。

[Rubyist Magazine - プログラマーのための YAML 入門 (中級編)][4]

  • データ型について
    • 真偽値と解釈されるデータは、"true/false", "yes/no" のほかに、"on/off" があります。また大文字でも CamelCase(先頭だけが大文字)でも認識されます
    • YAMLの仕様にはないですが、Syck では ":foo" のような「コロン+単語」の組み合わせは Ruby の Symbol と解釈されます。
    </blockquote> やっぱそうだ。 Symbol って便利なんだなぁ。問題はブロックを渡された本体側でそれをどう取り扱ったらいいのかまだ何も考えていないところか。 ## 目標 ゆくゆくはそうですね、ライブラリを目的別にチョイスしてサクッと監視できるようにしたいですよね。そのために本体から rule も action も切り離したんだから。ほんとによく使う基本のものだけ Base の方に取り込んで行く方向にしたいです。 あとあれですね。テストキット欲しいですよね。自分の書いた rule や action が正しく機能するかどうか簡単にテストできるようにしたい。どうすればいいかな。適当なログを吐くコードを書けばいいのか。むーん。 ## 謝辞と愚痴 今回初めて Thread と fork を使いました。今までデータの加工しかしてなかったんだなーと痛感。以下の情報が参考になりました。 * * [Rubyリファレンスマニュアル - Rubyリファレンスマニュアル][5] * [Twitter / Mizoguchi Coji: @wtnabe 把握。格納する変数がコード上は同じスコ...][6] * [CHNの中の人たちブログ - Rubyでデーモンプロセスを作る方法][7] 最初「スレッドはメモリを共有する」という言葉の意味が分からず悩みました。プロセスの場合は完全に分離してしまうがスレッドの場合は同じプロセス内なので flock みたいに行儀よく扱えば別々のスレッドで同じ変数を「扱うこともできるよ」という意味だと気づくまで時間が掛かりました。今回は共有したいものは何もないので「気にすることなかったのか!」と気づいてガックリ。しかしそのあとメインスレッドじゃないところのエラーがすぐに見つからないという事態に気づくまで妙なハマり方をすることに。 fork と Process.setsid の簡単なサンプルが見つからず、見当違いなコードを書いて悩むことしばし。それぞれの存在は知っていてもどこに何を書けばいいのかが短く的確に書かれているサンプルのなさに腹が立つと同時に、自分が Unix のプロセスの概念がよく分かっていないことがバレバレに。tty の detach ってそういう意味かーと初めて知った。[^2] 最終的にはスレッドも fork も明るいところでクックブックを読んだらだいぶ理解が進みました。電気を消してネットだけに頼っていたらハマった。 [1]: http://swatch.sourceforge.net/ [2]: http://svn.coderepos.org/share/lang/ruby/misc/rswatch.rb [3]: http://file-tail.rubyforge.org/ [4]: http://jp.rubyist.net/magazine/?0010-YAML [5]: http://www.ruby-lang.org/ja/man/html/index.html [6]: http://twitter.com/coji/statuses/753942912 [7]: http://chn.co.jp/blog/20080107.html#p01 [^1]: YAML にこだわる意味はないという考え方もあると思いますが、Ruby コードを書くのが目的ではなく監視が目的であるという場合は、やはり設定ファイルが生の Ruby コードであるというだけで十分敬遠されると思うので、できれば生の Ruby コードでしか設定が書けないという事態は避けたいです。というか生の Ruby コードで設定を書けるようにしてあるのは 1.6 でも動くようにするためです。 [^2]: 今回 stdin, stdout, stderr はあえてつぶしてありません。

About

例によって個人のなんちゃらです