最近は物騒でして、危ないサイトにアクセスしていないか?って依頼が来ることがあります。そういうことでプロキシのログを見るんですけど、効率よく検索できないと難しいところです。
で、そのプロキシログなんですが、gzip圧縮された状態で100MB以上(展開したら5倍ぐらいになる)のファイルが毎日たまっていっています。こんなの全部展開していたらログサーバはすぐにいっぱいになってしまいます。
ということで、Rubyでgzipを展開しながら見ていけばいいと言うことでこんなメソッドを作りました。
require 'zlib'
def gzip_each_line(filename, &block)
Zlib::GzipReader.open(filename) do |gz|
gz.each_line &block
end
end
これで万事オッケーのはずってやってみたら、なぜか1行だけしか読み取ってくれなかったのです。もしかしてファイル壊れている?と思ってgunzipしたら、ちゃんと展開されました。で、gzipについて調べているととんでもない事実を発見しました。
複数の圧縮ファイルを連結することができる。 この場合、 gunzip はすべての圧縮ファイルを一度に伸長する。たとえば、
gzip -c file1 > foo.gz gzip -c file2 >> foo.gz
の後に
gunzip -c foo
とするのは、以下と同じである。
cat file1 file2
え、gzipって単純連結できるの?というより、このログファイルはgzipの単純連結になってるーーー
さすが青いやつ1です。ただのgzipの筈が無かった。ということで何度もGzipReaderに喰わしてあげればいいやと書き直してみました。
require 'zlib'
def gzip_each_line(filename, &block)
File.open(filename, "rb") do |io|
until io.eof?
Zlib::GzipReader.wrap(io) do |gz|
gz.each_line &block
gz.finish
end
end
end
end
これでうまくいくはずと思ってやったら、二つ目のgzipでgzipじゃないよってエラーになって終わっちゃいます。どうやら、GzipReaderではioをバッファ単位で取得しているらしく、GzipReaderを閉じても使われなかったバッファが戻ってこないようなのです。つまり、読み込まれなかった分をちゃんと数えてその分戻してやらないといけない・・・となって、こうなりました。
require 'zlib'
def gzip_each_line(filename, &block)
File.open(filename, "rb") do |io|
unused_size = 0
until io.eof?
Zlib::GzipReader.wrap(io) do |gz|
gz.each_line &block
unused_size = gz.unused ? gz.unused.size : 0
gz.finish
end
io.seek(-unused_size, IO::SEEK_CUR)
end
end
end
GzipReader#unused
は使われなかったバッファが入っています。そのサイズ分、毎回戻してやれば、次のgzipもうまく見に行けるって方法です。これでやっといろいろ検索ができるようになりました。
というかGzipReaderがはじめから連結されたgzipファイルも対応しておいてくれていれば良かったんだけどなー。なお、単に検索するならzcat | grep
とかでいいんじゃね?という指摘は受け付けておりません!
-
ザクとは違う青いやつではありません。シェアは高いですが、まぁ、いろいろありますね、あの製品は。 ↩