CakePHPのログファイルを、ローテートするバッチを作った。
Cakeのログクラスってシンプルすぎて、どうなのかな? 変数とかをログに送ると改行されてログとっちゃうんだけど普通か? すごく扱いづらいので、ソースを書き換えた。
ソース読んでてびっくりしたんだけど Objectクラスっていうかなり下のレイヤーのクラスに logっていうメソッドがあって、そこでCakeLogクラスを使っているんだけど 設計おかしくないか?オブジェクトクラスにログなんてメソッドが本当に必要? 必要な時だけシングルトンで取得して使えばいいんじゃねーの?
$logger->info('message');
こんな感じで。
ともあれ、最近PHPを書く事が多くて、AttackingPHP曰く、壊れたオブジェクト機構でもなんとかできるようになってきた。 その代わり他の言語で、頭が腐ってきてる。害悪だなぁ。
–
そんなこんなで、すでに取ってしまったログ(しかも変な改行もはいってる)を奇麗に整形しつつ、ローテートする必要があったので作ろうと思った。
最初はシェルスクリプトにしようと思ったが、わからなすぎてやめた。
次にsedとかsortとかgrepを使ってなんとかしようと思った。 どうしても複数行の正規表現が難しくてやめた。 http://www.kabipan.com/computer/sed/index.html が参考になった。 簡単な検索置換なら覚えたから、良しとしよう。
次にperl。すぐ諦める。
結局Rubyで書いた。 こういったテキストファイル操作なんてもんは、オブジェクト指向しない方が早いだろう事はわかってるから、Rubyじゃなくてもいいと思うけど、Rubyしか書けない。
無駄に対話式にしてみたりした。 出来上がって、結構満足の行くものになったが ソースを後から読んでも絶対にわからんってくらい、気持ちの悪いソースになった。
書いている時は気持ちいいんだけどねぇ。。
ファイルロックの機構とか入れてないんだけど 入れた方がいいのかな? 昔Perlでカウンタとか、そういう類いのあれで 良くファイルロックとか使ったような。
def number_with_delimiter(number, delimiter=",", separator=".")
begin
parts = number.to_s.split('.')
parts[0].gsub!(/(¥d)(?=(¥d¥d¥d)+(?!¥d))/, "¥¥1#{delimiter}")
parts.join separator
rescue
number
end
end
def confirm( mes )
#exit unless block_given?
puts "#{mes} (yes/no)"
ARGF.each do |input|
input.chop!
if input =~ /^no$/i
elsif input =~ /^yes$/i
yield input if block_given?
break
else
puts "input text [yes] or [no] and enter."
end
end
end
# welcome message
puts "Welcome to LogRotator for CakePHP logfiles."
confirm('Are you ok?')
targets = Dir.glob("*.log").select{|f|f=~/^([a-z]+)¥.log/}
if targets.empty?
puts "log files are not found in current directory."
exit
end
datelogs = {}
# read and flatten lines
targets.each do |filename|
fullfilename = "#{Dir::pwd}/#{filename}"
start_time = Time.now
puts %Q(Open "#{fullfilename}")
puts " => #{number_with_delimiter(File::size(filename))}KB"
lines = []
File.open(filename).each do |line|
if line =~ /^¥d{4}-¥d{2}-¥d{2}/
lines << line.chop
( datelogs[$&.gsub(/-/,'')] ||= Array.new ) << lines.last
else
lines.last << line.chop.gsub(/¥s{2,}/,'') unless lines.last.nil?
end
end
puts %Q( => #{number_with_delimiter(lines.size)} lines in "#{fullfilename}".)
puts %Q! => read time: #{Time.now-start_time}sec!
puts ""
lines = nil
end
puts datelogs.keys.sort.map{|e|" => will create #{e}.log"}
puts ""
confirm("create #{datelogs.size}files. Are you ok?")
datelogs.each do |key,item|
filename = "#{key}.log"
start_date = Time.now
begin
file = File.new(filename,"w")
file.puts(item.sort.join("¥n"))
file.close
rescue=>e
puts e
else
puts " => write #{filename} [#{number_with_delimiter(File.size(filename))}KB] (#{Time.now-start_date}sec)"
end
end
targets.each do |filename|
newname = filename + Time.now.strftime("%Y%m%d")
begin
File::rename( filename, newname )
rescue=>e
puts e
else
puts "rename #{filename} to #{newname}."
end
end
puts "CakePHP logfiles were rotated."