1.プログラム プログラム名: 君ならどう書く? (テキスト処理(フィルタ)) 発表者名: Perl: 小山浩之(Tokyo/Shibuya Perl Mongers) PHP: 重松修 Python: 浦郷圭介(日本Pythonユーザ会) Ruby: たけうちひとし 記録者名: 吉田和弘 2.時間 開始時刻: 16:00 終了時刻: 16:30 (質疑応答: 16:26 〜 16:30) 3.発表の概要、論点 テキスト処理プログラムを各言語で実装する。 お題: Apacheのログを解析して、とあるIPアドレスがWebサーバを訪問してから出て行くまでの時間(滞在時間)を求めよ。 4.発表の流れ、内容 ■仕様 Apacheログ仕様: ApacheのCustomLogでデフォルト状態でcombinedと定義されているもの。 滞在時間の定義: 「直前5分間に同一IPからアクセスなし」状態となってから「直後5分間に同一IPからアクセスなし」状態となるまで。 そのほかの仕様: クライアントが指定されているかチェックすること。 ■Perl UNIX的ログ解析 ◆UNIX的ってなによ? プログラムを組み合わせて作るというUNIXの考え方は、仕事でも意識的に使っている。 Apacheログの形式は解析に適さない。 ◆どう実装する? パイプを使う。 パイプを使うとプログラミングしなくていい(自分で考えたコードを書かなくていい)。 ログ解析に要求される機能を個別のプログラムとして実装し、パイプで結合して使用する。 機能ごとに完結したプログラムなので、プログラム単位の再利用が可能で保守・拡張も容易。 ◆3つのフィルタ parser: ログから時刻とアクセス元を抽出する。 sorter: parserの出力をもとに滞在期間を求める。 reporter: sorterの出力を元に、解析結果を出力する。 ◆使い方 grepが必要。 トータルのアクセス時間と平均のアクセス時間を出力。 作成した3つのプログラムとは別に、アクセス元を限定するためにgrepコマンドを使用する。 % cat access_log | parser | grep '^192\.168\.188\.195' | sorter 300 | reporter Total: 14841[sec] Access: 257 Mean: 57.75[sec] ◆この実装のメリット 保守しやすい。 実績のあるプログラムを作りたい、かつ既存のモジュールも使いたい(という要求があるが、パイプでつなげると解決できる)。 より安全な再利用。 モジュールやクラスの再利用は結局新たにコードを書く必要があり、バグを入れ込む危険がある。 サーバの保守管理など重要なデータを操作する場合には、実績のあるプログラムをそのまま再利用したほうが安全。 ■Python Rubyのモニタが写らないトラブルがあり、Pythonに振る。 環境: Python-2.2以上 使用モジュール  time  時間や時間の書式のフォーマットを処理するためのモジュール   ApacheのCombinedアクセスログを解析し、IPアドレスごとの滞在時間と、全体の平均時間を求めます。 ハックが入って仕様がちょっと変わってしまっているがご了承を。 クライアントが指定されているかチェックするという仕様なのでチェックを入れている。 line17: ログのパーシング line4, 15-16: strftime strptime 時刻の書式を扱う関数。 from time import strptime, strftime line18-31 make_accessdata{1,2,3} 関数を切り替えることができる。好きなものを使える。 (発表者注) 1は、最も古い書き方 2は、リスト内包表記によるループからリストを簡潔に作る書き方 3は、ジェネレータを利用した疑似的なリスト作成法 という具合になってます。 1,2は意味的には同じですが、3はリストを返しませんので、sort()もできません。 ジェネレータの利用例として挙げてみたものです。 1: 4 行書くのは面倒だよね。 (発表者注) 古い書法だと確かにちょっと面倒です。それを解消するのが2になってます。 2: ワンライナーでも書ける。 2.0 からの機能、内包表記を使った。Pythonぽくない。 (発表者注) 最近のPythonでは内包表記が推奨されてるようです。 3: ジェネレータを使った。 analyze()関数が肝。 (手元のバージョンでは)第二引数で見たいIPアドレスを指定できる。 (発表者注) 最初に、引数解析を最初の部分(一番下)でやってることをざっと説明しています。 ■Ruby 解析対象のログの大きさは1MB ◆処理の流れ 使い方: $ ruby analog.rb [ip_addr] [logfile, ...] ip_addr = ARGV.shift access_log = AccessLog.new(ARGF) access_log.ip_addr = ip_addr report(access_log.sort) AccessLogクラスを作った。sort結果をreportメソッドに渡して出力。 ◆正規表現によるログの解析 (正規表現については具体的な説明なし) ◆アクセス日時の抽出(イテレータ) eachメソッド eachメソッドは明示的には使っていないが、sortで使われる。 class AccessLog include Enumerable ... include Enumerable により、collect, find, sortなどが追加される。 アクセス日時をソートするなどの機能が自動的に付加される。 (レポート出力のデモ) (Rubyでは、)やっつけ仕事でも、ARGF、文字列操作、正規表現、を適用できる(ので、これらを使って作ってください)。 ■PHP 環境: PHP-4.3.3RC2  --enable-mbregex(Ruby互換正規表現) IPアドレスごとの配列に格納する。 全部格納してから加工。表示する。 line 4: ファイル名にURLを指定できるので、外にあるデータでも読める。 line18: fgetsファミリ:fgetcsvなど亜種がある。(その場で使える関数がある場合は楽できる) (発表者注) fgetss, fgetcsv の亜種があります。 fgetss は行を取得後, html tag を取り除きます。 fgetcsv は行を取得後, CSVとして各項目ごとに切り出し配列にしてくれます。 line17-26 情報の抽出 *正規表現パターンマッチング *sscan()関数によるスキャン *指定文字列、正規表現パターンによる分割 ◆PHP配列の特徴 *配列は単なるハッシュ *キーによるランダムアクセス *途中に突っ込んでも(insertしてもindexが)ずれない。他言語から入るとはまるだろう。 *foreach構文 *スタック的操作も可能 ◆PHPのまとめ *手続き型 *便利な関数 *言語仕様がよく変わる。凝ったことはしない。楽して書くようにしないと、あらっと思うことが出てくる。 5.質疑応答・議論の流れ、内容 ■Perlでの実装について 質問者: 小飼弾さん データ量が多いとパイプはつらくないか。 回答者:  いやそれはパイプじゃなくても同じことだろう。 結論: パイプの問題とは言えない。 補足1: 一時ファイルを使うsortコマンドの方がデータ量に対して安全です。 (回答者注) 言語内蔵のsort関数を利用すると(多くの実装では)オンメモリでsortしてしまう。 sortコマンドは一次ファイルを利用するので、データ量が多い場合にはより安全。 補足2: grepを先にすればいいのでは。 ■Rubyでの実装について 質問者: 高林さん 月の名前を12個書くのは面倒です。time.rbのParseDateを使えばできるのでは。 あと、net/httpを使ってるところはopen-uriのほうが楽かと思います。 回答者  できるはず。 補足: time.rbのParseDateではApacheのログの時刻フォーマットは扱えない。 6.発表の総括 結論なし 課題なし しいて言えば、 *データ量が多い場合の効率的な実装 *Apacheの時刻フォーマットの扱い方 を各言語でまとめれば有意義だろう。 7.所感 各発表者の持ち時間が短すぎる。スライドを飛ばすなどしていたように、発表者は踏み込んだ説明ができなかったように思う。 テキスト処理のお題ということで、ログの加工という、サーバ管理者に大切な題材を選んだことは評価できる。 印刷されたコードとプレゼンに使われたコードと実際に動かしているコードに違いがあったようで、ロガーは混乱した。実際に使えるコードをお土産にしたかった。 8.特記事項 反応をみる余裕がなかった。^^; モニタ接続の事前確認が必要だ。 $Id: p3-2.log,v 1.1 2004/03/24 04:00:37 ryuchi Exp $