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.
Files changed (296) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +11 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +28 -0
  5. data/README.md +90 -0
  6. data/Rakefile +1 -0
  7. data/config.ru +11 -0
  8. data/lib/mongo_request_logger/adapters/base.rb +51 -0
  9. data/lib/mongo_request_logger/adapters/mongo.rb +89 -0
  10. data/lib/mongo_request_logger/adapters/moped.rb +94 -0
  11. data/lib/mongo_request_logger/backtrace_cleaner.rb +11 -0
  12. data/lib/mongo_request_logger/config.rb +109 -0
  13. data/lib/mongo_request_logger/ext/buffered_logger.rb +6 -0
  14. data/lib/mongo_request_logger/ext/logger.rb +6 -0
  15. data/lib/mongo_request_logger/log_message.rb +170 -0
  16. data/lib/mongo_request_logger/logged_job.rb +63 -0
  17. data/lib/mongo_request_logger/logger.rb +122 -0
  18. data/lib/mongo_request_logger/logger_extensions.rb +34 -0
  19. data/lib/mongo_request_logger/rack.rb +63 -0
  20. data/lib/mongo_request_logger/railtie.rb +57 -0
  21. data/lib/mongo_request_logger/search_terms.rb +59 -0
  22. data/lib/mongo_request_logger/version.rb +3 -0
  23. data/lib/mongo_request_logger/viewer.rb +212 -0
  24. data/lib/mongo_request_logger.rb +16 -0
  25. data/mongo_request_logger.gemspec +24 -0
  26. data/public/css/bootstrap-responsive.css +815 -0
  27. data/public/css/bootstrap-responsive.min.css +9 -0
  28. data/public/css/bootstrap.css +4983 -0
  29. data/public/css/bootstrap.min.css +9 -0
  30. data/public/img/flags/ad.png +0 -0
  31. data/public/img/flags/ae.png +0 -0
  32. data/public/img/flags/af.png +0 -0
  33. data/public/img/flags/ag.png +0 -0
  34. data/public/img/flags/ai.png +0 -0
  35. data/public/img/flags/al.png +0 -0
  36. data/public/img/flags/am.png +0 -0
  37. data/public/img/flags/an.png +0 -0
  38. data/public/img/flags/ao.png +0 -0
  39. data/public/img/flags/ar.png +0 -0
  40. data/public/img/flags/as.png +0 -0
  41. data/public/img/flags/at.png +0 -0
  42. data/public/img/flags/au.png +0 -0
  43. data/public/img/flags/aw.png +0 -0
  44. data/public/img/flags/ax.png +0 -0
  45. data/public/img/flags/az.png +0 -0
  46. data/public/img/flags/ba.png +0 -0
  47. data/public/img/flags/bb.png +0 -0
  48. data/public/img/flags/bd.png +0 -0
  49. data/public/img/flags/be.png +0 -0
  50. data/public/img/flags/bf.png +0 -0
  51. data/public/img/flags/bg.png +0 -0
  52. data/public/img/flags/bh.png +0 -0
  53. data/public/img/flags/bi.png +0 -0
  54. data/public/img/flags/bj.png +0 -0
  55. data/public/img/flags/bm.png +0 -0
  56. data/public/img/flags/bn.png +0 -0
  57. data/public/img/flags/bo.png +0 -0
  58. data/public/img/flags/br.png +0 -0
  59. data/public/img/flags/bs.png +0 -0
  60. data/public/img/flags/bt.png +0 -0
  61. data/public/img/flags/bv.png +0 -0
  62. data/public/img/flags/bw.png +0 -0
  63. data/public/img/flags/by.png +0 -0
  64. data/public/img/flags/bz.png +0 -0
  65. data/public/img/flags/ca.png +0 -0
  66. data/public/img/flags/catalonia.png +0 -0
  67. data/public/img/flags/cc.png +0 -0
  68. data/public/img/flags/cd.png +0 -0
  69. data/public/img/flags/cf.png +0 -0
  70. data/public/img/flags/cg.png +0 -0
  71. data/public/img/flags/ch.png +0 -0
  72. data/public/img/flags/ci.png +0 -0
  73. data/public/img/flags/ck.png +0 -0
  74. data/public/img/flags/cl.png +0 -0
  75. data/public/img/flags/cm.png +0 -0
  76. data/public/img/flags/cn.png +0 -0
  77. data/public/img/flags/co.png +0 -0
  78. data/public/img/flags/cr.png +0 -0
  79. data/public/img/flags/cs.png +0 -0
  80. data/public/img/flags/cu.png +0 -0
  81. data/public/img/flags/cv.png +0 -0
  82. data/public/img/flags/cx.png +0 -0
  83. data/public/img/flags/cy.png +0 -0
  84. data/public/img/flags/cz.png +0 -0
  85. data/public/img/flags/de.png +0 -0
  86. data/public/img/flags/dj.png +0 -0
  87. data/public/img/flags/dk.png +0 -0
  88. data/public/img/flags/dm.png +0 -0
  89. data/public/img/flags/do.png +0 -0
  90. data/public/img/flags/dz.png +0 -0
  91. data/public/img/flags/ec.png +0 -0
  92. data/public/img/flags/ee.png +0 -0
  93. data/public/img/flags/eg.png +0 -0
  94. data/public/img/flags/eh.png +0 -0
  95. data/public/img/flags/england.png +0 -0
  96. data/public/img/flags/er.png +0 -0
  97. data/public/img/flags/es.png +0 -0
  98. data/public/img/flags/et.png +0 -0
  99. data/public/img/flags/europeanunion.png +0 -0
  100. data/public/img/flags/fam.png +0 -0
  101. data/public/img/flags/fi.png +0 -0
  102. data/public/img/flags/fj.png +0 -0
  103. data/public/img/flags/fk.png +0 -0
  104. data/public/img/flags/fm.png +0 -0
  105. data/public/img/flags/fo.png +0 -0
  106. data/public/img/flags/fr.png +0 -0
  107. data/public/img/flags/ga.png +0 -0
  108. data/public/img/flags/gb.png +0 -0
  109. data/public/img/flags/gd.png +0 -0
  110. data/public/img/flags/ge.png +0 -0
  111. data/public/img/flags/gf.png +0 -0
  112. data/public/img/flags/gh.png +0 -0
  113. data/public/img/flags/gi.png +0 -0
  114. data/public/img/flags/gl.png +0 -0
  115. data/public/img/flags/gm.png +0 -0
  116. data/public/img/flags/gn.png +0 -0
  117. data/public/img/flags/gp.png +0 -0
  118. data/public/img/flags/gq.png +0 -0
  119. data/public/img/flags/gr.png +0 -0
  120. data/public/img/flags/gs.png +0 -0
  121. data/public/img/flags/gt.png +0 -0
  122. data/public/img/flags/gu.png +0 -0
  123. data/public/img/flags/gw.png +0 -0
  124. data/public/img/flags/gy.png +0 -0
  125. data/public/img/flags/hk.png +0 -0
  126. data/public/img/flags/hm.png +0 -0
  127. data/public/img/flags/hn.png +0 -0
  128. data/public/img/flags/hr.png +0 -0
  129. data/public/img/flags/ht.png +0 -0
  130. data/public/img/flags/hu.png +0 -0
  131. data/public/img/flags/id.png +0 -0
  132. data/public/img/flags/ie.png +0 -0
  133. data/public/img/flags/il.png +0 -0
  134. data/public/img/flags/in.png +0 -0
  135. data/public/img/flags/io.png +0 -0
  136. data/public/img/flags/iq.png +0 -0
  137. data/public/img/flags/ir.png +0 -0
  138. data/public/img/flags/is.png +0 -0
  139. data/public/img/flags/it.png +0 -0
  140. data/public/img/flags/jm.png +0 -0
  141. data/public/img/flags/jo.png +0 -0
  142. data/public/img/flags/jp.png +0 -0
  143. data/public/img/flags/ke.png +0 -0
  144. data/public/img/flags/kg.png +0 -0
  145. data/public/img/flags/kh.png +0 -0
  146. data/public/img/flags/ki.png +0 -0
  147. data/public/img/flags/km.png +0 -0
  148. data/public/img/flags/kn.png +0 -0
  149. data/public/img/flags/kp.png +0 -0
  150. data/public/img/flags/kr.png +0 -0
  151. data/public/img/flags/kw.png +0 -0
  152. data/public/img/flags/ky.png +0 -0
  153. data/public/img/flags/kz.png +0 -0
  154. data/public/img/flags/la.png +0 -0
  155. data/public/img/flags/lb.png +0 -0
  156. data/public/img/flags/lc.png +0 -0
  157. data/public/img/flags/li.png +0 -0
  158. data/public/img/flags/lk.png +0 -0
  159. data/public/img/flags/lr.png +0 -0
  160. data/public/img/flags/ls.png +0 -0
  161. data/public/img/flags/lt.png +0 -0
  162. data/public/img/flags/lu.png +0 -0
  163. data/public/img/flags/lv.png +0 -0
  164. data/public/img/flags/ly.png +0 -0
  165. data/public/img/flags/ma.png +0 -0
  166. data/public/img/flags/mc.png +0 -0
  167. data/public/img/flags/md.png +0 -0
  168. data/public/img/flags/me.png +0 -0
  169. data/public/img/flags/mg.png +0 -0
  170. data/public/img/flags/mh.png +0 -0
  171. data/public/img/flags/mk.png +0 -0
  172. data/public/img/flags/ml.png +0 -0
  173. data/public/img/flags/mm.png +0 -0
  174. data/public/img/flags/mn.png +0 -0
  175. data/public/img/flags/mo.png +0 -0
  176. data/public/img/flags/mp.png +0 -0
  177. data/public/img/flags/mq.png +0 -0
  178. data/public/img/flags/mr.png +0 -0
  179. data/public/img/flags/ms.png +0 -0
  180. data/public/img/flags/mt.png +0 -0
  181. data/public/img/flags/mu.png +0 -0
  182. data/public/img/flags/mv.png +0 -0
  183. data/public/img/flags/mw.png +0 -0
  184. data/public/img/flags/mx.png +0 -0
  185. data/public/img/flags/my.png +0 -0
  186. data/public/img/flags/mz.png +0 -0
  187. data/public/img/flags/na.png +0 -0
  188. data/public/img/flags/nc.png +0 -0
  189. data/public/img/flags/ne.png +0 -0
  190. data/public/img/flags/nf.png +0 -0
  191. data/public/img/flags/ng.png +0 -0
  192. data/public/img/flags/ni.png +0 -0
  193. data/public/img/flags/nl.png +0 -0
  194. data/public/img/flags/no.png +0 -0
  195. data/public/img/flags/np.png +0 -0
  196. data/public/img/flags/nr.png +0 -0
  197. data/public/img/flags/nu.png +0 -0
  198. data/public/img/flags/nz.png +0 -0
  199. data/public/img/flags/om.png +0 -0
  200. data/public/img/flags/pa.png +0 -0
  201. data/public/img/flags/pe.png +0 -0
  202. data/public/img/flags/pf.png +0 -0
  203. data/public/img/flags/pg.png +0 -0
  204. data/public/img/flags/ph.png +0 -0
  205. data/public/img/flags/pk.png +0 -0
  206. data/public/img/flags/pl.png +0 -0
  207. data/public/img/flags/pm.png +0 -0
  208. data/public/img/flags/pn.png +0 -0
  209. data/public/img/flags/pr.png +0 -0
  210. data/public/img/flags/ps.png +0 -0
  211. data/public/img/flags/pt.png +0 -0
  212. data/public/img/flags/pw.png +0 -0
  213. data/public/img/flags/py.png +0 -0
  214. data/public/img/flags/qa.png +0 -0
  215. data/public/img/flags/re.png +0 -0
  216. data/public/img/flags/ro.png +0 -0
  217. data/public/img/flags/rs.png +0 -0
  218. data/public/img/flags/ru.png +0 -0
  219. data/public/img/flags/rw.png +0 -0
  220. data/public/img/flags/sa.png +0 -0
  221. data/public/img/flags/sb.png +0 -0
  222. data/public/img/flags/sc.png +0 -0
  223. data/public/img/flags/scotland.png +0 -0
  224. data/public/img/flags/sd.png +0 -0
  225. data/public/img/flags/se.png +0 -0
  226. data/public/img/flags/sg.png +0 -0
  227. data/public/img/flags/sh.png +0 -0
  228. data/public/img/flags/si.png +0 -0
  229. data/public/img/flags/sj.png +0 -0
  230. data/public/img/flags/sk.png +0 -0
  231. data/public/img/flags/sl.png +0 -0
  232. data/public/img/flags/sm.png +0 -0
  233. data/public/img/flags/sn.png +0 -0
  234. data/public/img/flags/so.png +0 -0
  235. data/public/img/flags/sr.png +0 -0
  236. data/public/img/flags/st.png +0 -0
  237. data/public/img/flags/sv.png +0 -0
  238. data/public/img/flags/sy.png +0 -0
  239. data/public/img/flags/sz.png +0 -0
  240. data/public/img/flags/tc.png +0 -0
  241. data/public/img/flags/td.png +0 -0
  242. data/public/img/flags/tf.png +0 -0
  243. data/public/img/flags/tg.png +0 -0
  244. data/public/img/flags/th.png +0 -0
  245. data/public/img/flags/tj.png +0 -0
  246. data/public/img/flags/tk.png +0 -0
  247. data/public/img/flags/tl.png +0 -0
  248. data/public/img/flags/tm.png +0 -0
  249. data/public/img/flags/tn.png +0 -0
  250. data/public/img/flags/to.png +0 -0
  251. data/public/img/flags/tr.png +0 -0
  252. data/public/img/flags/tt.png +0 -0
  253. data/public/img/flags/tv.png +0 -0
  254. data/public/img/flags/tw.png +0 -0
  255. data/public/img/flags/tz.png +0 -0
  256. data/public/img/flags/ua.png +0 -0
  257. data/public/img/flags/ug.png +0 -0
  258. data/public/img/flags/uk.png +0 -0
  259. data/public/img/flags/um.png +0 -0
  260. data/public/img/flags/us.png +0 -0
  261. data/public/img/flags/uy.png +0 -0
  262. data/public/img/flags/uz.png +0 -0
  263. data/public/img/flags/va.png +0 -0
  264. data/public/img/flags/vc.png +0 -0
  265. data/public/img/flags/ve.png +0 -0
  266. data/public/img/flags/vg.png +0 -0
  267. data/public/img/flags/vi.png +0 -0
  268. data/public/img/flags/vn.png +0 -0
  269. data/public/img/flags/vu.png +0 -0
  270. data/public/img/flags/wales.png +0 -0
  271. data/public/img/flags/wf.png +0 -0
  272. data/public/img/flags/ws.png +0 -0
  273. data/public/img/flags/ye.png +0 -0
  274. data/public/img/flags/yt.png +0 -0
  275. data/public/img/flags/za.png +0 -0
  276. data/public/img/flags/zm.png +0 -0
  277. data/public/img/flags/zw.png +0 -0
  278. data/public/img/glyphicons-halflings-white.png +0 -0
  279. data/public/img/glyphicons-halflings.png +0 -0
  280. data/public/js/bootstrap.js +1825 -0
  281. data/public/js/bootstrap.min.js +6 -0
  282. data/public/js/jquery-1.7.2.min.js +4 -0
  283. data/public/js/logs.js +194 -0
  284. data/spec/mongo_logger_spec.rb +33 -0
  285. data/spec/moped_logger_spec.rb +39 -0
  286. data/spec/query_spec.rb +80 -0
  287. data/spec/railtie_spec.rb +21 -0
  288. data/spec/shared_examples.rb +111 -0
  289. data/spec/spec_helper.rb +17 -0
  290. data/spec/testapp/config/logger.yml +6 -0
  291. data/spec/testapp/log/.gitkeep +0 -0
  292. data/views/index.erb +0 -0
  293. data/views/layout.erb +61 -0
  294. data/views/log_page.erb +22 -0
  295. data/views/logs.erb +76 -0
  296. 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
@@ -0,0 +1,3 @@
1
+ module MongoRequestLogger
2
+ VERSION = '0.1.0'
3
+ end