備忘録です。
もうツールに操られるのは嫌なので、
rubyとmongoDBで生ログの解析ができるようにしようと思っています。
mongoDBを選んだのは、特に意味があるわけではないですけど、
大量のデータになったら、MySQLじゃきついんではないかなという
思い込みと、noSQL使ってみたかったというだけです。
まだまだプロトですが、
・ログをパースして、DBにぶち込むスクリプト(ruby_log.rb)
・簡易的に集計するスクリプト(count.rb)
だけ作りました。
パースの方は
・ApacheLogRegexを使って、ログをパース
・色々フィルターかけて、扱いやすい形に変換
・MongoDBにぶち込む
という流れです。
MongoDBについては、あえてDataMapperは使わないでやっています。
mongo_mapper使っちゃうと、MongoDBの良さが半減しそうな
感じがしたので、避けています。
どうせRailsとか使わないし。
(candyはちょっと使ってみたいと思ったんですけど、
うまく動作せず断念。。。)
リファクタリングすれば、もうちょっと綺麗に書けそうな気はします。
ほんとは設定系の内容は別ファイルにした方がよさそうですけどね。
集計の方はMap/Reduceを使ってみました。
いまいちMap/Reduceの使い方がよく分からなくて、
単純集計しか出来ていません。
あと、エンコード周りは面倒なので、後回しにしています。。。。
なので、検索キーワードはかなり微妙な集計結果になってるかもしれません。
もうちょっとMap/Reduceを調べて、複雑な集計をできるようにしようと思います。
ruby_log.rb
Encoding.default_external="UTF-8"
require 'mongo'
require 'apachelogregex'
require 'time'
#set the path of apache log file
LOGFILE = 'logfile.log'
class LogFilter
def main(obj)
if(pattern(obj))
res = self.file_type(obj)
end
return res
end
def file_type(obj)
val = Hash.new()
val['page'] = self.pages(obj['%r'])
val['referer'] = obj['%{Referer}i']
val['ip'] = obj['%h']
val['ua'] = obj['%{User-Agent}i']
val['ip_ua'] = obj['%h'] + "__" + obj['%{User-Agent}i']
val['time'] = self.p_time(obj['%t'])
search_kw = self.parse_kw(obj)
if(search_kw)
search_kw.each_pair do |x,y|
val['search'] = x.to_s
val['kw'] = y.to_s
end
else
val['search'] = "none"
val['kw'] = "none"
end
return val
end
def pattern(hsh)
#set the file type to be removed
file_paturns = [
/.gif/i,
/.png/i,
/.jpg/i,
/.swf/i,
/.css/i,
/.js/i,
/.ico/i,
/.mp3/i,
/.txt/i
]
#set the user agent to be removed
ua_fil = [
/bot/i,
/msn/i,
/ichiro/i,
/Hatena/i,
/Yahoo! Slurp/,
/findlinks/
]
file_paturns.each do |reg|
if(hsh['%r'].match(reg))
return false
break
end
end
ua_fil.each do |reg|
if(hsh['%{User-Agent}i'].match(reg))
return false
break
end
end
return true
end
def pages(str)
return str.gsub(/^GETs|^POSTs|^HEADs/,"").gsub(/sHTTP.+$/, "")
end
def p_time(str)
str = str.gsub("[","").gsub("]","")
mm = /^(d+)/([a-zA-Z]+)/(d+):([d:]+) ([+-]d+)/.match(str)
post_time = Time.parse("#{mm[1]} #{mm[2]} #{mm[3]} #{mm[4]} #{mm[5]}")
return post_time
end
def parse_kw(hsh)
#set the search engine list
list = {
"google" => ["q", /google.co.jp/search?/],
"google_com" => ["q", /google.com/search?/],
"yahoo" => ["p", /search.yahoo.co.jp/search/]
}
str = hsh["%{Referer}i"]
tmp = Hash.new
list.each_pair do |x,y|
if(str.match(y[1]))
reg = /#{y[0]}=[^&]*/
tmp[x] = (str.scan(reg)[0]).gsub!("#{y[0]}=","")
end
end
if(!tmp.empty?)
return tmp
else
return false
end
end
end
fil = LogFilter.new
con = Mongo::Connection.new
db = con.db('log')
logs = db.collection('Log')
# difinition of apache log format
format = '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"'
parser = ApacheLogRegex.new(format)
i = 0
File.foreach(LOGFILE) do |line|
begin
res = fil.main(parser.parse(line))
if(res!=nil)
#p res
logs.insert(res)
end
rescue ApacheLogRegex::ParseError => e
puts "Error parsing log file: " + e.message
end
end
puts "end file:#{LOGFILE}"
count.rb
Encoding.default_external="UTF-8"
require 'rubygems'
require 'mongo'
require 'uri'
class SingleMp
def initialize(str)
@str = str
@con = Mongo::Connection.new
@db = @con.db('log')
@logs = @db.collection('Log')
end
attr_accessor :str
def main
self.printMp(self.getMp)
end
def getMp
@map = "function() { emit(this.#{@str}, 1); }"
@reduce = "function (k, vals) {" +
"var sum = 0;" +
"for (var i in vals) {" +
"sum += vals[i];" +
"}" +
"return sum;" +
"}"
@res = @logs.map_reduce(@map, @reduce)
return @res
end
def printMp(obj)
obj.find().each do |x|
if(x['_id']!=nil)
print (@str + ":t" + URI.unescape(x['_id']) + "t=>t" + x['value'].to_s + "n")
end
end
end
end
mp_page = SingleMp.new('page')
mp_page.main
puts ("n---------------------nn")
mp_ua = SingleMp.new('ua')
mp_ua.main
puts ("n---------------------nn")
mp_ipua = SingleMp.new('ip_ua')
mp_ipua.main
puts ("n---------------------nn")
mp_ipua = SingleMp.new('referer')
mp_ipua.main
puts ("n---------------------nn")
mp_ipua = SingleMp.new('kw')
mp_ipua.main
puts ("n---------------------nn")
mp_ipua = SingleMp.new('search')
mp_ipua.main
*11/22 timeのパースがうまくいってなかったので、修正しました。
もっとスマートにパースできなかなー。
1 reply on “ruby + mongoDBでapacheログの解析システムを作る”
ちょうどやりたかったことを掲載いただいていて本当に助かります!
が、実行するとエラーになってしまいます。
ruby_log.rb:63:in `pattern’: undefined method `[]’ for nil:NilClass (NoMethodError)
from ruby_log.rb:62:in `each’
from ruby_log.rb:62:in `pattern’
from ruby_log.rb:15:in `main’
from ruby_log.rb:122
from ruby_log.rb:120:in `foreach’
from ruby_log.rb:120
もし差し支えなければRuby、gem list –localでの各バージョン結果などを教えていただくことは可能でしょうか?
ご検討いただけますと助かりますm(__)m