log_sense 1.6.1 → 1.7.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.
@@ -1,313 +0,0 @@
1
- require "sqlite3"
2
-
3
- module LogSense
4
- #
5
- # parse a Rails log file and return a SQLite3 DB
6
- #
7
- class RailsLogParser
8
- #
9
- # Tell users which format I can parse
10
- #
11
- def provide
12
- [:rails]
13
- end
14
-
15
- def parse(streams, options = {})
16
- db = SQLite3::Database.new ":memory:"
17
-
18
- db.execute <<-EOS
19
- CREATE TABLE IF NOT EXISTS Event(
20
- id INTEGER PRIMARY KEY AUTOINCREMENT,
21
- exit_status TEXT,
22
- started_at TEXT,
23
- ended_at TEXT,
24
- log_id TEXT,
25
- ip TEXT,
26
- unique_visitor TEXT,
27
- url TEXT,
28
- controller TEXT,
29
- html_verb TEXT,
30
- status INTEGER,
31
- duration_total_ms FLOAT,
32
- duration_views_ms FLOAT,
33
- duration_ar_ms FLOAT,
34
- allocations INTEGER,
35
- comment TEXT,
36
- source_file TEXT,
37
- line_number INTEGER
38
- )
39
- EOS
40
-
41
- ins = db.prepare <<-EOS
42
- insert into Event(
43
- exit_status,
44
- started_at,
45
- ended_at,
46
- log_id,
47
- ip,
48
- unique_visitor,
49
- url,
50
- controller,
51
- html_verb,
52
- status,
53
- duration_total_ms,
54
- duration_views_ms,
55
- duration_ar_ms,
56
- allocations,
57
- comment,
58
- source_file,
59
- line_number
60
- )
61
- values (#{Array.new(17, "?").join(", ")})
62
- EOS
63
-
64
- db.execute <<-EOS
65
- CREATE TABLE IF NOT EXISTS Error(
66
- id INTEGER PRIMARY KEY AUTOINCREMENT,
67
- log_id TEXT,
68
- context TEXT,
69
- description TEXT,
70
- filename TEXT,
71
- line_number INTEGER
72
- )
73
- EOS
74
-
75
- ins_error = db.prepare <<-EOS
76
- insert into Error(
77
- log_id,
78
- context,
79
- description,
80
- filename,
81
- line_number
82
- )
83
- values (?, ?, ?, ?, ?)
84
- EOS
85
-
86
- # requests in the log might be interleaved.
87
- #
88
- # We use the 'pending' variable to progressively store data
89
- # about requests till they are completed; whey they are
90
- # complete, we enter the entry in the DB and remove it from the
91
- # hash
92
- pending = {}
93
-
94
- # Log lines are either one of:
95
- #
96
- # LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Started VERB "URL" for IP at TIMESTAMP
97
- # LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Processing by CONTROLLER as FORMAT
98
- # LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Parameters: JSON
99
- # LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Rendered VIEW within LAYOUT (Duration: DURATION | Allocations: ALLOCATIONS)
100
- # LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Completed STATUS STATUS_STRING in DURATION (Views: DURATION | ActiveRecord: DURATION | Allocations: NUMBER)
101
- #
102
- # and they appears in the order shown above: started, processing, ...
103
- #
104
- # Different requests might be interleaved, of course
105
- #
106
- streams.each do |stream|
107
- stream.readlines.each_with_index do |line, line_number|
108
- filename = stream == $stdin ? "stdin" : stream.path
109
-
110
- # I and F for completed requests, [ is for error messages
111
- next if line[0] != 'I' and line[0] != 'F' and line[0] != '['
112
-
113
- data = match_and_process_error line
114
- if data
115
- ins_error.execute(data[:log_id],
116
- data[:context],
117
- data[:description],
118
- filename,
119
- line_number)
120
- next
121
- end
122
-
123
- data = match_and_process_start line
124
- if data
125
- id = data[:log_id]
126
- pending[id] = data.merge(pending[id] || {})
127
- next
128
- end
129
-
130
- data = match_and_process_processing_by line
131
- if data
132
- id = data[:log_id]
133
- pending[id] = data.merge(pending[id] || {})
134
- next
135
- end
136
-
137
- data = match_and_process_fatal line
138
- if data
139
- id = data[:log_id]
140
- # it might as well be that the first event started before
141
- # the log. With this, we make sure we add only events whose
142
- # start was logged and parsed
143
- if pending[id]
144
- event = data.merge(pending[id] || {})
145
-
146
- ins.execute(
147
- event[:exit_status],
148
- event[:started_at],
149
- event[:ended_at],
150
- event[:log_id],
151
- event[:ip],
152
- unique_visitor_id(event),
153
- event[:url],
154
- event[:controller],
155
- event[:html_verb],
156
- event[:status],
157
- event[:duration_total_ms],
158
- event[:duration_views_ms],
159
- event[:duration_ar_ms],
160
- event[:allocations],
161
- event[:comment],
162
- filename,
163
- line_number
164
- )
165
-
166
- pending.delete(id)
167
- end
168
- end
169
-
170
- data = self.match_and_process_completed line
171
- if data
172
- id = data[:log_id]
173
-
174
- # it might as well be that the first event started before
175
- # the log. With this, we make sure we add only events whose
176
- # start was logged and parsed
177
- if pending[id]
178
- event = data.merge (pending[id] || {})
179
-
180
- ins.execute(
181
- event[:exit_status],
182
- event[:started_at],
183
- event[:ended_at],
184
- event[:log_id],
185
- event[:ip],
186
- unique_visitor_id(event),
187
- event[:url],
188
- event[:controller],
189
- event[:html_verb],
190
- event[:status],
191
- event[:duration_total_ms],
192
- event[:duration_views_ms],
193
- event[:duration_ar_ms],
194
- event[:allocations],
195
- event[:comment],
196
- filename,
197
- line_number
198
- )
199
-
200
- pending.delete(id)
201
- end
202
- end
203
- end
204
- end
205
-
206
- db
207
- end
208
-
209
- TIMESTAMP = /(?<timestamp>[^ ]+)/
210
- ID = /(?<id>[a-z0-9-]+)/
211
- VERB = /(?<verb>GET|POST|PATCH|PUT|DELETE)/
212
- URL = /(?<url>[^"]+)/
213
- IP = /(?<ip>[0-9.]+)/
214
- STATUS = /(?<status>[0-9]+)/
215
- STATUS_IN_WORDS = /(OK|Unauthorized|Found|Internal Server Error|Bad Request|Method Not Allowed|Request Timeout|Not Implemented|Bad Gateway|Service Unavailable)/
216
- MSECS = /[0-9.]+/
217
-
218
- # Error Messages
219
- # [584cffcc-f1fd-4b5c-bb8b-b89621bd4921] ActionController::RoutingError (No route matches [GET] "/assets/foundation-icons.svg"):
220
- # [fd8df8b5-83c9-48b5-a056-e5026e31bd5e] ActionView::Template::Error (undefined method `all_my_ancestor' for nil:NilClass):
221
- # [d17ed55c-f5f1-442a-a9d6-3035ab91adf0] ActionView::Template::Error (undefined method `volunteer_for' for #<DonationsController:0x007f4864c564b8>
222
- EXCEPTION = /[A-Za-z_0-9:]+(Error)?/
223
- ERROR_REGEXP = /^\[#{ID}\] (?<context>#{EXCEPTION}) \((?<description>(#{EXCEPTION})?.*)\):/
224
-
225
- def match_and_process_error line
226
- matchdata = ERROR_REGEXP.match line
227
- if matchdata
228
- {
229
- log_id: matchdata[:id],
230
- context: matchdata[:context],
231
- description: matchdata[:description]
232
- }
233
- end
234
- end
235
-
236
- # I, [2021-10-19T08:16:34.343858 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Started GET "/grow/people/471" for 217.77.80.35 at 2021-10-19 08:16:34 +0000
237
- STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{ID}\] Started #{VERB} "#{URL}" for #{IP} at/
238
-
239
- def match_and_process_start line
240
- matchdata = STARTED_REGEXP.match line
241
- if matchdata
242
- {
243
- started_at: matchdata[:timestamp],
244
- log_id: matchdata[:id],
245
- html_verb: matchdata[:verb],
246
- url: matchdata[:url],
247
- ip: matchdata[:ip]
248
- }
249
- end
250
- end
251
-
252
- # TODO: Add regexps for the performance data (Views ...). We have three cases (view, active records, allocations), (views, active records), (active records, allocations)
253
- # I, [2021-10-19T08:16:34.712331 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Completed 200 OK in 367ms (Views: 216.7ms | ActiveRecord: 141.3ms | Allocations: 168792)
254
- # I, [2021-12-09T16:53:52.657727 #2735058] INFO -- : [0064e403-9eb2-439d-8fe1-a334c86f5532] Completed 200 OK in 13ms (Views: 11.1ms | ActiveRecord: 1.2ms)
255
- # I, [2021-12-06T14:28:19.736545 #2804090] INFO -- : [34091cb5-3e7b-4042-aaf8-6c6510d3f14c] Completed 500 Internal Server Error in 66ms (ActiveRecord: 8.0ms | Allocations: 24885)
256
- COMPLETED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{ID}\] Completed #{STATUS} #{STATUS_IN_WORDS} in (?<total>#{MSECS})ms \((Views: (?<views>#{MSECS})ms \| )?ActiveRecord: (?<arec>#{MSECS})ms( \| Allocations: (?<alloc>[0-9]+))?\)/
257
-
258
- def match_and_process_completed(line)
259
- matchdata = (COMPLETED_REGEXP.match line)
260
- # exit_status = matchdata[:status].to_i == 500 ? "E" : "I"
261
- if matchdata
262
- {
263
- exit_status: "I",
264
- ended_at: matchdata[:timestamp],
265
- log_id: matchdata[:id],
266
- status: matchdata[:status],
267
- duration_total_ms: matchdata[:total],
268
- duration_views_ms: matchdata[:views],
269
- duration_ar_ms: matchdata[:arec],
270
- allocations: matchdata[:alloc],
271
- comment: ""
272
- }
273
- end
274
- end
275
-
276
- # I, [2021-10-19T08:16:34.345162 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Processing by PeopleController#show as HTML
277
- PROCESSING_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{ID}\] Processing by (?<controller>[^ ]+) as/
278
-
279
- def match_and_process_processing_by line
280
- matchdata = PROCESSING_REGEXP.match line
281
- if matchdata
282
- {
283
- log_id: matchdata[:id],
284
- controller: matchdata[:controller]
285
- }
286
- end
287
- end
288
-
289
- # F, [2021-12-04T00:34:05.838973 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728]
290
- # F, [2021-12-04T00:34:05.839157 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728] ActionController::RoutingError (No route matches [GET] "/wp/wp-includes/wlwmanifest.xml"):
291
- # F, [2021-12-04T00:34:05.839209 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728]
292
- # F, [2021-12-04T00:34:05.839269 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728] actionpack (5.2.4.4) lib/action_dispatch/middleware/debug_exceptions.rb:65:in `call'
293
- FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{ID}\] (?<comment>.*)$/
294
-
295
- def match_and_process_fatal(line)
296
- matchdata = FATAL_REGEXP.match line
297
- if matchdata
298
- {
299
- exit_status: "F",
300
- log_id: matchdata[:id],
301
- comment: matchdata[:comment]
302
- }
303
- end
304
- end
305
-
306
- # generate a unique visitor id from an event
307
- def unique_visitor_id(event)
308
- date = event[:started_at] || event[:ended_at] || "1970-01-01"
309
- "#{DateTime.parse(date).strftime("%Y-%m-%d")} #{event[:ip]}"
310
- end
311
- end
312
- end
313
-