mongo_request_logger 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.travis.yml +11 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +28 -0
- data/README.md +90 -0
- data/Rakefile +1 -0
- data/config.ru +11 -0
- data/lib/mongo_request_logger/adapters/base.rb +51 -0
- data/lib/mongo_request_logger/adapters/mongo.rb +89 -0
- data/lib/mongo_request_logger/adapters/moped.rb +94 -0
- data/lib/mongo_request_logger/backtrace_cleaner.rb +11 -0
- data/lib/mongo_request_logger/config.rb +109 -0
- data/lib/mongo_request_logger/ext/buffered_logger.rb +6 -0
- data/lib/mongo_request_logger/ext/logger.rb +6 -0
- data/lib/mongo_request_logger/log_message.rb +170 -0
- data/lib/mongo_request_logger/logged_job.rb +63 -0
- data/lib/mongo_request_logger/logger.rb +122 -0
- data/lib/mongo_request_logger/logger_extensions.rb +34 -0
- data/lib/mongo_request_logger/rack.rb +63 -0
- data/lib/mongo_request_logger/railtie.rb +57 -0
- data/lib/mongo_request_logger/search_terms.rb +59 -0
- data/lib/mongo_request_logger/version.rb +3 -0
- data/lib/mongo_request_logger/viewer.rb +212 -0
- data/lib/mongo_request_logger.rb +16 -0
- data/mongo_request_logger.gemspec +24 -0
- data/public/css/bootstrap-responsive.css +815 -0
- data/public/css/bootstrap-responsive.min.css +9 -0
- data/public/css/bootstrap.css +4983 -0
- data/public/css/bootstrap.min.css +9 -0
- data/public/img/flags/ad.png +0 -0
- data/public/img/flags/ae.png +0 -0
- data/public/img/flags/af.png +0 -0
- data/public/img/flags/ag.png +0 -0
- data/public/img/flags/ai.png +0 -0
- data/public/img/flags/al.png +0 -0
- data/public/img/flags/am.png +0 -0
- data/public/img/flags/an.png +0 -0
- data/public/img/flags/ao.png +0 -0
- data/public/img/flags/ar.png +0 -0
- data/public/img/flags/as.png +0 -0
- data/public/img/flags/at.png +0 -0
- data/public/img/flags/au.png +0 -0
- data/public/img/flags/aw.png +0 -0
- data/public/img/flags/ax.png +0 -0
- data/public/img/flags/az.png +0 -0
- data/public/img/flags/ba.png +0 -0
- data/public/img/flags/bb.png +0 -0
- data/public/img/flags/bd.png +0 -0
- data/public/img/flags/be.png +0 -0
- data/public/img/flags/bf.png +0 -0
- data/public/img/flags/bg.png +0 -0
- data/public/img/flags/bh.png +0 -0
- data/public/img/flags/bi.png +0 -0
- data/public/img/flags/bj.png +0 -0
- data/public/img/flags/bm.png +0 -0
- data/public/img/flags/bn.png +0 -0
- data/public/img/flags/bo.png +0 -0
- data/public/img/flags/br.png +0 -0
- data/public/img/flags/bs.png +0 -0
- data/public/img/flags/bt.png +0 -0
- data/public/img/flags/bv.png +0 -0
- data/public/img/flags/bw.png +0 -0
- data/public/img/flags/by.png +0 -0
- data/public/img/flags/bz.png +0 -0
- data/public/img/flags/ca.png +0 -0
- data/public/img/flags/catalonia.png +0 -0
- data/public/img/flags/cc.png +0 -0
- data/public/img/flags/cd.png +0 -0
- data/public/img/flags/cf.png +0 -0
- data/public/img/flags/cg.png +0 -0
- data/public/img/flags/ch.png +0 -0
- data/public/img/flags/ci.png +0 -0
- data/public/img/flags/ck.png +0 -0
- data/public/img/flags/cl.png +0 -0
- data/public/img/flags/cm.png +0 -0
- data/public/img/flags/cn.png +0 -0
- data/public/img/flags/co.png +0 -0
- data/public/img/flags/cr.png +0 -0
- data/public/img/flags/cs.png +0 -0
- data/public/img/flags/cu.png +0 -0
- data/public/img/flags/cv.png +0 -0
- data/public/img/flags/cx.png +0 -0
- data/public/img/flags/cy.png +0 -0
- data/public/img/flags/cz.png +0 -0
- data/public/img/flags/de.png +0 -0
- data/public/img/flags/dj.png +0 -0
- data/public/img/flags/dk.png +0 -0
- data/public/img/flags/dm.png +0 -0
- data/public/img/flags/do.png +0 -0
- data/public/img/flags/dz.png +0 -0
- data/public/img/flags/ec.png +0 -0
- data/public/img/flags/ee.png +0 -0
- data/public/img/flags/eg.png +0 -0
- data/public/img/flags/eh.png +0 -0
- data/public/img/flags/england.png +0 -0
- data/public/img/flags/er.png +0 -0
- data/public/img/flags/es.png +0 -0
- data/public/img/flags/et.png +0 -0
- data/public/img/flags/europeanunion.png +0 -0
- data/public/img/flags/fam.png +0 -0
- data/public/img/flags/fi.png +0 -0
- data/public/img/flags/fj.png +0 -0
- data/public/img/flags/fk.png +0 -0
- data/public/img/flags/fm.png +0 -0
- data/public/img/flags/fo.png +0 -0
- data/public/img/flags/fr.png +0 -0
- data/public/img/flags/ga.png +0 -0
- data/public/img/flags/gb.png +0 -0
- data/public/img/flags/gd.png +0 -0
- data/public/img/flags/ge.png +0 -0
- data/public/img/flags/gf.png +0 -0
- data/public/img/flags/gh.png +0 -0
- data/public/img/flags/gi.png +0 -0
- data/public/img/flags/gl.png +0 -0
- data/public/img/flags/gm.png +0 -0
- data/public/img/flags/gn.png +0 -0
- data/public/img/flags/gp.png +0 -0
- data/public/img/flags/gq.png +0 -0
- data/public/img/flags/gr.png +0 -0
- data/public/img/flags/gs.png +0 -0
- data/public/img/flags/gt.png +0 -0
- data/public/img/flags/gu.png +0 -0
- data/public/img/flags/gw.png +0 -0
- data/public/img/flags/gy.png +0 -0
- data/public/img/flags/hk.png +0 -0
- data/public/img/flags/hm.png +0 -0
- data/public/img/flags/hn.png +0 -0
- data/public/img/flags/hr.png +0 -0
- data/public/img/flags/ht.png +0 -0
- data/public/img/flags/hu.png +0 -0
- data/public/img/flags/id.png +0 -0
- data/public/img/flags/ie.png +0 -0
- data/public/img/flags/il.png +0 -0
- data/public/img/flags/in.png +0 -0
- data/public/img/flags/io.png +0 -0
- data/public/img/flags/iq.png +0 -0
- data/public/img/flags/ir.png +0 -0
- data/public/img/flags/is.png +0 -0
- data/public/img/flags/it.png +0 -0
- data/public/img/flags/jm.png +0 -0
- data/public/img/flags/jo.png +0 -0
- data/public/img/flags/jp.png +0 -0
- data/public/img/flags/ke.png +0 -0
- data/public/img/flags/kg.png +0 -0
- data/public/img/flags/kh.png +0 -0
- data/public/img/flags/ki.png +0 -0
- data/public/img/flags/km.png +0 -0
- data/public/img/flags/kn.png +0 -0
- data/public/img/flags/kp.png +0 -0
- data/public/img/flags/kr.png +0 -0
- data/public/img/flags/kw.png +0 -0
- data/public/img/flags/ky.png +0 -0
- data/public/img/flags/kz.png +0 -0
- data/public/img/flags/la.png +0 -0
- data/public/img/flags/lb.png +0 -0
- data/public/img/flags/lc.png +0 -0
- data/public/img/flags/li.png +0 -0
- data/public/img/flags/lk.png +0 -0
- data/public/img/flags/lr.png +0 -0
- data/public/img/flags/ls.png +0 -0
- data/public/img/flags/lt.png +0 -0
- data/public/img/flags/lu.png +0 -0
- data/public/img/flags/lv.png +0 -0
- data/public/img/flags/ly.png +0 -0
- data/public/img/flags/ma.png +0 -0
- data/public/img/flags/mc.png +0 -0
- data/public/img/flags/md.png +0 -0
- data/public/img/flags/me.png +0 -0
- data/public/img/flags/mg.png +0 -0
- data/public/img/flags/mh.png +0 -0
- data/public/img/flags/mk.png +0 -0
- data/public/img/flags/ml.png +0 -0
- data/public/img/flags/mm.png +0 -0
- data/public/img/flags/mn.png +0 -0
- data/public/img/flags/mo.png +0 -0
- data/public/img/flags/mp.png +0 -0
- data/public/img/flags/mq.png +0 -0
- data/public/img/flags/mr.png +0 -0
- data/public/img/flags/ms.png +0 -0
- data/public/img/flags/mt.png +0 -0
- data/public/img/flags/mu.png +0 -0
- data/public/img/flags/mv.png +0 -0
- data/public/img/flags/mw.png +0 -0
- data/public/img/flags/mx.png +0 -0
- data/public/img/flags/my.png +0 -0
- data/public/img/flags/mz.png +0 -0
- data/public/img/flags/na.png +0 -0
- data/public/img/flags/nc.png +0 -0
- data/public/img/flags/ne.png +0 -0
- data/public/img/flags/nf.png +0 -0
- data/public/img/flags/ng.png +0 -0
- data/public/img/flags/ni.png +0 -0
- data/public/img/flags/nl.png +0 -0
- data/public/img/flags/no.png +0 -0
- data/public/img/flags/np.png +0 -0
- data/public/img/flags/nr.png +0 -0
- data/public/img/flags/nu.png +0 -0
- data/public/img/flags/nz.png +0 -0
- data/public/img/flags/om.png +0 -0
- data/public/img/flags/pa.png +0 -0
- data/public/img/flags/pe.png +0 -0
- data/public/img/flags/pf.png +0 -0
- data/public/img/flags/pg.png +0 -0
- data/public/img/flags/ph.png +0 -0
- data/public/img/flags/pk.png +0 -0
- data/public/img/flags/pl.png +0 -0
- data/public/img/flags/pm.png +0 -0
- data/public/img/flags/pn.png +0 -0
- data/public/img/flags/pr.png +0 -0
- data/public/img/flags/ps.png +0 -0
- data/public/img/flags/pt.png +0 -0
- data/public/img/flags/pw.png +0 -0
- data/public/img/flags/py.png +0 -0
- data/public/img/flags/qa.png +0 -0
- data/public/img/flags/re.png +0 -0
- data/public/img/flags/ro.png +0 -0
- data/public/img/flags/rs.png +0 -0
- data/public/img/flags/ru.png +0 -0
- data/public/img/flags/rw.png +0 -0
- data/public/img/flags/sa.png +0 -0
- data/public/img/flags/sb.png +0 -0
- data/public/img/flags/sc.png +0 -0
- data/public/img/flags/scotland.png +0 -0
- data/public/img/flags/sd.png +0 -0
- data/public/img/flags/se.png +0 -0
- data/public/img/flags/sg.png +0 -0
- data/public/img/flags/sh.png +0 -0
- data/public/img/flags/si.png +0 -0
- data/public/img/flags/sj.png +0 -0
- data/public/img/flags/sk.png +0 -0
- data/public/img/flags/sl.png +0 -0
- data/public/img/flags/sm.png +0 -0
- data/public/img/flags/sn.png +0 -0
- data/public/img/flags/so.png +0 -0
- data/public/img/flags/sr.png +0 -0
- data/public/img/flags/st.png +0 -0
- data/public/img/flags/sv.png +0 -0
- data/public/img/flags/sy.png +0 -0
- data/public/img/flags/sz.png +0 -0
- data/public/img/flags/tc.png +0 -0
- data/public/img/flags/td.png +0 -0
- data/public/img/flags/tf.png +0 -0
- data/public/img/flags/tg.png +0 -0
- data/public/img/flags/th.png +0 -0
- data/public/img/flags/tj.png +0 -0
- data/public/img/flags/tk.png +0 -0
- data/public/img/flags/tl.png +0 -0
- data/public/img/flags/tm.png +0 -0
- data/public/img/flags/tn.png +0 -0
- data/public/img/flags/to.png +0 -0
- data/public/img/flags/tr.png +0 -0
- data/public/img/flags/tt.png +0 -0
- data/public/img/flags/tv.png +0 -0
- data/public/img/flags/tw.png +0 -0
- data/public/img/flags/tz.png +0 -0
- data/public/img/flags/ua.png +0 -0
- data/public/img/flags/ug.png +0 -0
- data/public/img/flags/uk.png +0 -0
- data/public/img/flags/um.png +0 -0
- data/public/img/flags/us.png +0 -0
- data/public/img/flags/uy.png +0 -0
- data/public/img/flags/uz.png +0 -0
- data/public/img/flags/va.png +0 -0
- data/public/img/flags/vc.png +0 -0
- data/public/img/flags/ve.png +0 -0
- data/public/img/flags/vg.png +0 -0
- data/public/img/flags/vi.png +0 -0
- data/public/img/flags/vn.png +0 -0
- data/public/img/flags/vu.png +0 -0
- data/public/img/flags/wales.png +0 -0
- data/public/img/flags/wf.png +0 -0
- data/public/img/flags/ws.png +0 -0
- data/public/img/flags/ye.png +0 -0
- data/public/img/flags/yt.png +0 -0
- data/public/img/flags/za.png +0 -0
- data/public/img/flags/zm.png +0 -0
- data/public/img/flags/zw.png +0 -0
- data/public/img/glyphicons-halflings-white.png +0 -0
- data/public/img/glyphicons-halflings.png +0 -0
- data/public/js/bootstrap.js +1825 -0
- data/public/js/bootstrap.min.js +6 -0
- data/public/js/jquery-1.7.2.min.js +4 -0
- data/public/js/logs.js +194 -0
- data/spec/mongo_logger_spec.rb +33 -0
- data/spec/moped_logger_spec.rb +39 -0
- data/spec/query_spec.rb +80 -0
- data/spec/railtie_spec.rb +21 -0
- data/spec/shared_examples.rb +111 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/testapp/config/logger.yml +6 -0
- data/spec/testapp/log/.gitkeep +0 -0
- data/views/index.erb +0 -0
- data/views/layout.erb +61 -0
- data/views/log_page.erb +22 -0
- data/views/logs.erb +76 -0
- metadata +402 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rack/utils'
|
3
|
+
|
4
|
+
module MongoRequestLogger
|
5
|
+
class LogMessage
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize data={}
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](param)
|
13
|
+
@data[param.to_s]
|
14
|
+
end
|
15
|
+
|
16
|
+
def id
|
17
|
+
self[:_id].to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def referer
|
21
|
+
referer = self[:referer]
|
22
|
+
referer_data = {
|
23
|
+
'annotation' => ''
|
24
|
+
}
|
25
|
+
|
26
|
+
# parse google adwords HTTP_REFERERs for actual referer page on display network
|
27
|
+
if referer.nil? or referer.empty?
|
28
|
+
referer_data['url'] = ''
|
29
|
+
|
30
|
+
elsif referer.include? 'http://googleads.g.doubleclick.net'
|
31
|
+
uri = URI(referer)
|
32
|
+
params = ::Rack::Utils.parse_nested_query(uri.query)
|
33
|
+
referer_data['url'] = params['url']
|
34
|
+
referer_data['annotation'] = 'AdWords Display Network'
|
35
|
+
|
36
|
+
# parse google adwords SERP clicks for actual SERP page
|
37
|
+
elsif referer.include?('google') and referer.include?('aclk') and referer.include?('adurl')
|
38
|
+
uri = URI(referer)
|
39
|
+
params = ::Rack::Utils.parse_nested_query(uri.query)
|
40
|
+
referer_data['url'] = "http://#{uri.host}/?q=#{CGI::escape(params['q'])}"
|
41
|
+
referer_data['annotation'] = 'Adwords Google Search'
|
42
|
+
|
43
|
+
else
|
44
|
+
referer_data['url'] = referer
|
45
|
+
end
|
46
|
+
referer_data
|
47
|
+
end
|
48
|
+
|
49
|
+
def path
|
50
|
+
self[:path]
|
51
|
+
end
|
52
|
+
|
53
|
+
def runtime
|
54
|
+
"%.3f" % (self[:runtime] / 1000.0) if self[:runtime]
|
55
|
+
end
|
56
|
+
|
57
|
+
# One of:
|
58
|
+
# ''
|
59
|
+
# 'user_id'
|
60
|
+
# 'user_name'
|
61
|
+
# 'user_id - user_name'
|
62
|
+
def user
|
63
|
+
{
|
64
|
+
name: (self[:user_name] or ''),
|
65
|
+
email: (self[:user_email] or ''),
|
66
|
+
email_md5: Digest::MD5.hexdigest((self[:user_email] or ''))
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def user_id
|
71
|
+
self[:user_id]
|
72
|
+
end
|
73
|
+
|
74
|
+
def user_agent
|
75
|
+
self[:user_agent]
|
76
|
+
end
|
77
|
+
|
78
|
+
def time
|
79
|
+
self[:timestamp].getlocal.strftime("%y-%m-%d %H:%M:%S") if self[:timestamp]
|
80
|
+
end
|
81
|
+
|
82
|
+
def messages
|
83
|
+
self[:messages]
|
84
|
+
end
|
85
|
+
|
86
|
+
def response
|
87
|
+
response_code = self[:response]
|
88
|
+
if self[:exception]
|
89
|
+
response_description = self[:exception]['class']
|
90
|
+
else
|
91
|
+
response_class = Net::HTTPResponse::CODE_TO_OBJ[self[:response].to_s]
|
92
|
+
if response_class
|
93
|
+
response_description = response_class.name.split("::HTTP").last
|
94
|
+
else
|
95
|
+
response_description = 'OK'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
[response_code, response_description].select {|v| v != nil}.join(" - ")
|
100
|
+
end
|
101
|
+
|
102
|
+
def exception
|
103
|
+
if self[:exception]
|
104
|
+
if self[:exception]['code']
|
105
|
+
"#{self[:exception]['class']} - [#{self[:exception]['code']}] #{self[:exception]['message']}" if self[:exception]
|
106
|
+
else
|
107
|
+
"#{self[:exception]['class']} - #{self[:exception]['message']}" if self[:exception]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
def args
|
114
|
+
if self[:args]
|
115
|
+
self[:args].map { |arg| arg.to_s.truncate(50) }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def params
|
120
|
+
if self[:params].is_a? Array
|
121
|
+
result = []
|
122
|
+
self[:params].each do |key, value|
|
123
|
+
result << "#{key}: #{value.inspect.truncate(100)}"
|
124
|
+
end
|
125
|
+
result.join(", ")
|
126
|
+
elsif self[:params].is_a? String
|
127
|
+
self[:params]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def timestamp
|
132
|
+
(self[:timestamp].to_f * 1000).to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
def ip
|
136
|
+
self[:ip] || ''
|
137
|
+
end
|
138
|
+
|
139
|
+
def tags
|
140
|
+
(self[:tags] || []) - %w(rack)
|
141
|
+
end
|
142
|
+
|
143
|
+
def as_json(*)
|
144
|
+
{
|
145
|
+
id: id,
|
146
|
+
time: time,
|
147
|
+
path: path,
|
148
|
+
referer: referer,
|
149
|
+
tags: tags,
|
150
|
+
user: user,
|
151
|
+
ip: ip,
|
152
|
+
response: response,
|
153
|
+
user_agent: user_agent,
|
154
|
+
runtime: runtime,
|
155
|
+
timestamp: timestamp,
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
def extra exclude=[]
|
161
|
+
result = {}
|
162
|
+
@data.each do |key, value|
|
163
|
+
next if exclude.include? key.to_s
|
164
|
+
next if value.blank?
|
165
|
+
result[key.to_s.titlecase] = value
|
166
|
+
end
|
167
|
+
result
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'mongo_request_logger/logger'
|
2
|
+
require 'mongo_request_logger/rack'
|
3
|
+
|
4
|
+
module MongoRequestLogger
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# class MyJob
|
8
|
+
# extend MongoRequestLogger::LoggedJob
|
9
|
+
#
|
10
|
+
# def perform(args)
|
11
|
+
# ...
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
module LoggedJob
|
15
|
+
def self.logger= logger
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.logger
|
21
|
+
@logger ||= ::MongoRequestLogger::Rack.logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
LoggedJob.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def around_perform_log_job(*args, &block)
|
29
|
+
call_logged *args, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def call_logged *args, &block
|
35
|
+
unless logger.respond_to?(:log_request)
|
36
|
+
# No MongoDB logger, but still want to log any errors
|
37
|
+
begin
|
38
|
+
return block.call(*args)
|
39
|
+
rescue Exception => e
|
40
|
+
logger.error e.to_s
|
41
|
+
logger.error e.backtrace.join("\n")
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Note that @queue is the default queue for the job class, and may be different from the queue it was on.
|
47
|
+
# Therefore we don't log it anymore, to prevent confusion (until we find a way to determine that actual queue that
|
48
|
+
# the job was on).
|
49
|
+
|
50
|
+
options = {
|
51
|
+
:type => "job",
|
52
|
+
:job => self.name,
|
53
|
+
:args => args,
|
54
|
+
}
|
55
|
+
logger.log_request(options) do
|
56
|
+
logger.tag "resque"
|
57
|
+
short_name = self.name.split('::').last.underscore
|
58
|
+
logger.add_metadata path: "resque/#{short_name}"
|
59
|
+
block.call(*args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'active_support/buffered_logger'
|
2
|
+
require 'mongo_request_logger/ext/buffered_logger'
|
3
|
+
|
4
|
+
module MongoRequestLogger
|
5
|
+
class Logger < ActiveSupport::BufferedLogger
|
6
|
+
attr_reader :adapter
|
7
|
+
|
8
|
+
def initialize(adapter, file, level=DEBUG)
|
9
|
+
super(file, level)
|
10
|
+
@adapter = adapter
|
11
|
+
@mongo_record = {} # This is required as a workaround for logging in the console
|
12
|
+
create_indexes
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_indexes
|
16
|
+
adapter.create_index '_id'
|
17
|
+
adapter.create_index 'objects'
|
18
|
+
adapter.create_index 'tags'
|
19
|
+
adapter.create_index 'path'
|
20
|
+
adapter.create_index 'user_id'
|
21
|
+
adapter.create_index 'timestamp'
|
22
|
+
end
|
23
|
+
|
24
|
+
LEVELS = {
|
25
|
+
DEBUG => 'DEBUG',
|
26
|
+
INFO => 'INFO',
|
27
|
+
WARN => 'WARN',
|
28
|
+
ERROR => 'ERROR',
|
29
|
+
FATAL => 'FATAL',
|
30
|
+
UNKNOWN => 'UNKNOWN',
|
31
|
+
}
|
32
|
+
|
33
|
+
def level_to_s(level)
|
34
|
+
LEVELS[level]
|
35
|
+
end
|
36
|
+
|
37
|
+
def sanitize obj
|
38
|
+
if obj.is_a? Hash
|
39
|
+
result = {}
|
40
|
+
obj.each do |k, v|
|
41
|
+
# We replace all non-alphanumeric characters with underscores
|
42
|
+
result[k.to_s.gsub(/\W/, "_")] = sanitize(v)
|
43
|
+
end
|
44
|
+
result
|
45
|
+
elsif obj.is_a? Array or obj.is_a? Set
|
46
|
+
obj.map {|o| sanitize(o)}
|
47
|
+
elsif obj.is_a? Time or obj.is_a? String or obj.is_a? Integer
|
48
|
+
obj
|
49
|
+
else
|
50
|
+
obj.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_request(options={})
|
55
|
+
@mongo_record = options.merge({
|
56
|
+
:messages => [],
|
57
|
+
:timestamp => Time.now.utc,
|
58
|
+
:pid => Process.pid,
|
59
|
+
})
|
60
|
+
|
61
|
+
begin
|
62
|
+
start_time = Time.now
|
63
|
+
return yield
|
64
|
+
rescue Exception => e
|
65
|
+
# The backtrace is not useful metadata, and we want to write it to the text log as well.
|
66
|
+
exception e
|
67
|
+
@mongo_record[:exception] = {:class => e.class.to_s, :message => e.message}
|
68
|
+
raise
|
69
|
+
ensure
|
70
|
+
# Benchmark.measure doesn't handle exceptions well, so we measure the time ourselves.
|
71
|
+
end_time = Time.now
|
72
|
+
runtime = end_time.to_f - start_time.to_f
|
73
|
+
@mongo_record[:runtime] = (runtime.real * 1000).ceil
|
74
|
+
|
75
|
+
do_log @mongo_record
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_metadata(options={})
|
80
|
+
options.each_pair do |key, value|
|
81
|
+
@mongo_record[key] = value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_metadata_set key, *values
|
86
|
+
key = key.to_s
|
87
|
+
@mongo_record[key] ||= Set.new
|
88
|
+
@mongo_record[key] += values
|
89
|
+
end
|
90
|
+
|
91
|
+
def add(severity, message = nil, progname = nil, &block)
|
92
|
+
unless level > severity
|
93
|
+
time = Time.now.strftime "%H:%M:%S.%L"
|
94
|
+
prefix = "#{time} [#{level_to_s(severity)}] "
|
95
|
+
#if ActiveRecord::Base.colorize_logging
|
96
|
+
# # remove colorization done by rails and just save the actual message
|
97
|
+
# @mongo_record[:messages] << prefix + message.gsub(/(\e(\[([\d;]*[mz]?))?)?/, '').strip rescue nil
|
98
|
+
#else
|
99
|
+
@mongo_record[:messages] ||= []
|
100
|
+
@mongo_record[:messages] << prefix + message
|
101
|
+
#end
|
102
|
+
end
|
103
|
+
|
104
|
+
# To do normal file logging as well, add a call to super here
|
105
|
+
# We only log ERROR and above to the text file logs.
|
106
|
+
if severity >= ERROR
|
107
|
+
super
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def do_log(record)
|
113
|
+
begin
|
114
|
+
adapter.insert_log_record(sanitize(record))
|
115
|
+
rescue => e
|
116
|
+
# Note that this will not result in recursion, as we do not commit the log again here. It will be written to the
|
117
|
+
# text log.
|
118
|
+
exception e, "Unable to log to MongoDB"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'mongo_request_logger/backtrace_cleaner'
|
2
|
+
|
3
|
+
# Some additional logging methods. These are as extensions to the standard logger classes instead of our logger, so
|
4
|
+
# that an app can easily switch between the structured logger and a standard file-based logger.
|
5
|
+
module MongoRequestLogger
|
6
|
+
module LoggerExtensions
|
7
|
+
def add_metadata_set(key, *values)
|
8
|
+
info "#{key}: #{values.inspect}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_metadata(options={})
|
12
|
+
info options.inspect
|
13
|
+
end
|
14
|
+
|
15
|
+
def tag *tags
|
16
|
+
add_metadata_set :tags, *tags
|
17
|
+
end
|
18
|
+
|
19
|
+
def exception(exception, message=nil)
|
20
|
+
if message
|
21
|
+
start = "#{message}: "
|
22
|
+
else
|
23
|
+
start = ""
|
24
|
+
end
|
25
|
+
|
26
|
+
cleaned_backtrace = MongoRequestLogger::BacktraceCleaner.clean(exception.backtrace || [])
|
27
|
+
|
28
|
+
log_message = "#{start}#{exception.class} #{exception.message}:\n " +
|
29
|
+
cleaned_backtrace.join("\n ")
|
30
|
+
|
31
|
+
error log_message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
4
|
+
|
5
|
+
|
6
|
+
module MongoRequestLogger
|
7
|
+
class Rack
|
8
|
+
cattr_accessor :logger
|
9
|
+
cattr_accessor :ignore_prefixes
|
10
|
+
|
11
|
+
def self.ignore_prefix prefix
|
12
|
+
self.ignore_prefixes ||= []
|
13
|
+
self.ignore_prefixes << prefix
|
14
|
+
end
|
15
|
+
|
16
|
+
self.ignore_prefix '/log' # TODO: dynamically detect this?
|
17
|
+
|
18
|
+
def initialize(app, options = {})
|
19
|
+
@app = app
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
self.class.logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
#HACK: Don't log the log viewer itself
|
29
|
+
self.class.ignore_prefixes.each do |prefix|
|
30
|
+
if env["PATH_INFO"].to_s.start_with? prefix
|
31
|
+
return @app.call(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# TODO: filter parameters
|
35
|
+
# KLUDGE: this will cause parameter parsing to happen twice: once here, once later on in Rails.
|
36
|
+
# Or maybe it's automatically cached in env?
|
37
|
+
logger.log_request do
|
38
|
+
begin
|
39
|
+
request = ::Rack::Request.new(env)
|
40
|
+
options = {
|
41
|
+
path: request.path_info,
|
42
|
+
host: request.host_with_port,
|
43
|
+
user_agent: request.user_agent,
|
44
|
+
ip: request.ip,
|
45
|
+
referer: request.referrer,
|
46
|
+
query_string: request.query_string,
|
47
|
+
request_method: request.request_method,
|
48
|
+
content_type: request.content_type,
|
49
|
+
params: request.params
|
50
|
+
}
|
51
|
+
logger.add_metadata options
|
52
|
+
logger.add_metadata_set :tags, "rack"
|
53
|
+
status, headers, body = @app.call(env)
|
54
|
+
logger.add_metadata response: status.to_i
|
55
|
+
[status, headers, body]
|
56
|
+
rescue Exception => e
|
57
|
+
logger.add_metadata response: 500
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
if (defined? Rails) && Rails.version =~ /^3\./
|
2
|
+
require 'mongo_request_logger'
|
3
|
+
require 'mongo_request_logger/config'
|
4
|
+
require 'mongo_request_logger/rack'
|
5
|
+
require 'mongo_request_logger/viewer'
|
6
|
+
|
7
|
+
require 'rails'
|
8
|
+
|
9
|
+
begin
|
10
|
+
# We prefer moped ...
|
11
|
+
require 'moped'
|
12
|
+
rescue LoadError
|
13
|
+
# ... but fallback to mongo
|
14
|
+
require 'mongo'
|
15
|
+
end
|
16
|
+
|
17
|
+
module MongoRequestLogger
|
18
|
+
class Railtie < ::Rails::Railtie
|
19
|
+
class << self
|
20
|
+
def setup(app)
|
21
|
+
|
22
|
+
global_config = MongoRequestLogger::Config.new
|
23
|
+
global_config.load_file(File.join(Rails.root, "config/logger.yml"))
|
24
|
+
log_config = global_config.namespaced(Rails.env)
|
25
|
+
return unless log_config && log_config['database']
|
26
|
+
|
27
|
+
if defined? Moped
|
28
|
+
adapter = MongoRequestLogger::Adapters::Moped.new(log_config)
|
29
|
+
else
|
30
|
+
adapter = MongoRequestLogger::Adapters::Mongo.new(log_config)
|
31
|
+
end
|
32
|
+
Rails.logger = MongoRequestLogger::Logger.new adapter, Rails.root.join("log/#{Rails.env}.log")
|
33
|
+
|
34
|
+
MongoRequestLogger::Rack.logger = Rails.logger
|
35
|
+
MongoRequestLogger::Rack.ignore_prefixes << '/assets'
|
36
|
+
|
37
|
+
MongoRequestLogger::Viewer.adapter = adapter
|
38
|
+
|
39
|
+
app.config.middleware.insert_after ActionDispatch::DebugExceptions, MongoRequestLogger::Rack
|
40
|
+
|
41
|
+
if defined?(PhusionPassenger)
|
42
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
43
|
+
if forked
|
44
|
+
adapter.reconnect
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
initializer "setup logger" do |app|
|
53
|
+
MongoRequestLogger::Railtie.setup(app)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Used the following blog post as a starting point, then completely rewrote the parser:
|
2
|
+
# http://proccli.com/2011/12/advanced-search-query-parsing-ruby/
|
3
|
+
# https://gist.github.com/1477730#file_search_terms.rb
|
4
|
+
|
5
|
+
module MongoRequestLogger
|
6
|
+
class SearchTerms
|
7
|
+
attr_reader :query, :parts, :search, :path, :search_regex
|
8
|
+
|
9
|
+
# query:: this is what you want tokenized
|
10
|
+
# split:: if you'd like to split values on "," then pass true
|
11
|
+
def initialize(query)
|
12
|
+
@query = query
|
13
|
+
@parts = {}
|
14
|
+
@search = nil
|
15
|
+
@search_regex = nil
|
16
|
+
parse_query
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
@parts[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
SCANNER = /(?:(\w+):(\S+)) # key:value
|
26
|
+
|(?:"(.+)") # string in quotes
|
27
|
+
|(^\S+) # a path
|
28
|
+
|(?:\/(.+?)\/(?:\s|$)) # regular expression
|
29
|
+
|(\S+)/x # a word
|
30
|
+
def parse_query
|
31
|
+
search_parts = []
|
32
|
+
search_regex = nil
|
33
|
+
# We break up the query into a sequence of the following possible components:
|
34
|
+
# key:value
|
35
|
+
# "a string wrapped in quotes"
|
36
|
+
# /a/path (at the beginning of the string only)
|
37
|
+
# /a regular expression/
|
38
|
+
# word
|
39
|
+
groups = @query.scan(SCANNER)
|
40
|
+
groups.map do |key, value, search, path, regex, word|
|
41
|
+
if key
|
42
|
+
@parts[key.downcase] = value
|
43
|
+
elsif search
|
44
|
+
search_parts << search
|
45
|
+
elsif regex
|
46
|
+
@search_regex = regex
|
47
|
+
elsif path
|
48
|
+
@path = path
|
49
|
+
elsif word
|
50
|
+
search_parts << word
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
unless @search_regex
|
55
|
+
@search = search_parts.join(' ')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|