logeater 0.1.1 → 0.2.1
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.
- checksums.yaml +4 -4
- data/bin/logeater +2 -36
- data/db/migrate/20150207183757_change_requests_params_to_jsonb.rb +9 -0
- data/db/migrate/20150224021844_change_requests_path_to_text.rb +9 -0
- data/db/migrate/20150602022241_add_user_id_and_tester_bar_to_requests.rb +6 -0
- data/db/schema.rb +27 -22
- data/lib/logeater/cli.rb +81 -0
- data/lib/logeater/params_parser.rb +7 -2
- data/lib/logeater/parser.rb +54 -68
- data/lib/logeater/reader.rb +39 -9
- data/lib/logeater/request.rb +1 -6
- data/lib/logeater/version.rb +1 -1
- data/lib/logeater.rb +18 -3
- data/logeater.gemspec +6 -6
- data/test/integration/logeater_test.rb +2 -2
- data/test/test_helper.rb +5 -3
- data/test/unit/params_parser_test.rb +5 -0
- data/test/unit/parser_test.rb +61 -1
- metadata +44 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4976cf2269f67a86ae95b9f6a182a527e15757c6
|
4
|
+
data.tar.gz: 07888882f6e46fbff69fea38a46672efcb766caa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 930d7fc93409a85005d38a8529cf9f4357922894458da1a9fb02760d3d7ebebefe79d1069d165e37148500a59e0ad02be8364fc29b86a72069dd6dd985996dea
|
7
|
+
data.tar.gz: 47774aa8db964b8405673f750d7c666b98fdf4952b683ad3b1c9f37e7939b897388b01d9aaed489965445dd56a2d546d8bf1effc906c9367e22ab18ba5efec62
|
data/bin/logeater
CHANGED
@@ -1,39 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require "logeater"
|
3
|
+
require "logeater/cli"
|
4
4
|
|
5
|
-
|
6
|
-
unless app
|
7
|
-
puts "USAGE: logeater <app> <files...>"
|
8
|
-
exit
|
9
|
-
end
|
10
|
-
|
11
|
-
files = ARGV
|
12
|
-
if files.empty?
|
13
|
-
puts "USAGE: logeater <app> <files...>"
|
14
|
-
exit
|
15
|
-
end
|
16
|
-
|
17
|
-
started_all = Time.now
|
18
|
-
files.each_with_index do |file, i|
|
19
|
-
reader = Logeater::Reader.new(app, file, progress: true)
|
20
|
-
reader.remove_existing_entries!
|
21
|
-
|
22
|
-
started_count = Logeater::Request.count
|
23
|
-
started_at = Time.now
|
24
|
-
reader.import
|
25
|
-
finished_at = Time.now
|
26
|
-
finished_count = Logeater::Request.count
|
27
|
-
|
28
|
-
puts " > \e[34mImported \e[1m%d\e[0;34m requests in \e[1m%.2f\e[0;34m seconds (%d of %d)\e[0m\n\n" % [
|
29
|
-
finished_count - started_count,
|
30
|
-
finished_at - started_at,
|
31
|
-
i + 1,
|
32
|
-
files.length ]
|
33
|
-
end
|
34
|
-
|
35
|
-
finished_all = Time.now
|
36
|
-
seconds = finished_all - started_all
|
37
|
-
minutes = (seconds / 60).to_i
|
38
|
-
seconds -= (minutes * 60)
|
39
|
-
puts "Total time %d minutes, %.2f seconds" % [minutes, seconds]
|
5
|
+
Logeater::CLI.start(ARGV)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class ChangeRequestsParamsToJsonb < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
execute "alter table requests alter column params type jsonb using params::jsonb"
|
4
|
+
end
|
5
|
+
|
6
|
+
def down
|
7
|
+
execute "alter table requests alter column params type json using params::json"
|
8
|
+
end
|
9
|
+
end
|
data/db/schema.rb
CHANGED
@@ -9,35 +9,40 @@
|
|
9
9
|
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
10
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
11
|
#
|
12
|
-
# It's strongly recommended
|
12
|
+
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(:
|
14
|
+
ActiveRecord::Schema.define(version: 20150602022241) do
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
t.string "
|
16
|
+
# These are extensions that must be enabled in order to support this database
|
17
|
+
enable_extension "plpgsql"
|
18
|
+
|
19
|
+
create_table "requests", force: :cascade do |t|
|
20
|
+
t.string "app", limit: 255, null: false
|
21
|
+
t.string "logfile", limit: 255, null: false
|
22
|
+
t.string "uuid", limit: 255, null: false
|
23
|
+
t.string "subdomain", limit: 255
|
21
24
|
t.datetime "started_at"
|
22
25
|
t.datetime "completed_at"
|
23
26
|
t.integer "duration"
|
24
|
-
t.string "http_method"
|
25
|
-
t.
|
26
|
-
t.
|
27
|
-
t.string "controller"
|
28
|
-
t.string "action"
|
29
|
-
t.string "remote_ip"
|
30
|
-
t.string "format"
|
27
|
+
t.string "http_method", limit: 255
|
28
|
+
t.text "path"
|
29
|
+
t.jsonb "params"
|
30
|
+
t.string "controller", limit: 255
|
31
|
+
t.string "action", limit: 255
|
32
|
+
t.string "remote_ip", limit: 255
|
33
|
+
t.string "format", limit: 255
|
31
34
|
t.integer "http_status"
|
32
|
-
t.string "http_response"
|
33
|
-
t.datetime "created_at",
|
34
|
-
t.datetime "updated_at",
|
35
|
+
t.string "http_response", limit: 255
|
36
|
+
t.datetime "created_at", null: false
|
37
|
+
t.datetime "updated_at", null: false
|
38
|
+
t.integer "user_id"
|
39
|
+
t.boolean "tester_bar"
|
35
40
|
end
|
36
41
|
|
37
|
-
add_index "requests", ["app"], :
|
38
|
-
add_index "requests", ["controller", "action"], :
|
39
|
-
add_index "requests", ["http_status"], :
|
40
|
-
add_index "requests", ["logfile"], :
|
41
|
-
add_index "requests", ["uuid"], :
|
42
|
+
add_index "requests", ["app"], name: "index_requests_on_app", using: :btree
|
43
|
+
add_index "requests", ["controller", "action"], name: "index_requests_on_controller_and_action", using: :btree
|
44
|
+
add_index "requests", ["http_status"], name: "index_requests_on_http_status", using: :btree
|
45
|
+
add_index "requests", ["logfile"], name: "index_requests_on_logfile", using: :btree
|
46
|
+
add_index "requests", ["uuid"], name: "index_requests_on_uuid", unique: true, using: :btree
|
42
47
|
|
43
48
|
end
|
data/lib/logeater/cli.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "logeater"
|
3
|
+
|
4
|
+
module Logeater
|
5
|
+
class CLI < Thor
|
6
|
+
|
7
|
+
desc "parse APP FILES", "parses files and writes their output to stdout"
|
8
|
+
def parse(app, *files)
|
9
|
+
if files.empty?
|
10
|
+
$stderr.puts "No files to parse"
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
started_all = Time.now
|
15
|
+
files.each_with_index do |file, i|
|
16
|
+
reader = Logeater::Reader.new(app, file, progress: true)
|
17
|
+
|
18
|
+
started_at = Time.now
|
19
|
+
requests = reader.parse
|
20
|
+
finished_at = Time.now
|
21
|
+
|
22
|
+
$stderr.puts " > \e[34mParsed \e[1m%d\e[0;34m requests in \e[1m%.2f\e[0;34m seconds (%d of %d)\e[0m\n\n" % [
|
23
|
+
requests,
|
24
|
+
finished_at - started_at,
|
25
|
+
i + 1,
|
26
|
+
files.length ]
|
27
|
+
end
|
28
|
+
|
29
|
+
finished_all = Time.now
|
30
|
+
seconds = finished_all - started_all
|
31
|
+
minutes = (seconds / 60).to_i
|
32
|
+
seconds -= (minutes * 60)
|
33
|
+
$stderr.puts "Total time %d minutes, %.2f seconds" % [minutes, seconds]
|
34
|
+
end
|
35
|
+
|
36
|
+
option :force, type: :boolean, desc: "Imports all specified files, overwiting previous imports if necessary"
|
37
|
+
option :progress, type: :boolean, desc: "Renders a progress bar while importing"
|
38
|
+
option :verbose, type: :boolean
|
39
|
+
desc "import APP FILES", "imports files"
|
40
|
+
def import(app, *files)
|
41
|
+
unless options[:force]
|
42
|
+
imported_files = Logeater::Request.where(app: app).pluck("DISTINCT logfile")
|
43
|
+
files.reject! { |file| imported_files.member? File.basename(files[0]) }
|
44
|
+
end
|
45
|
+
|
46
|
+
if files.empty?
|
47
|
+
$stderr.puts "No files to import"
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
started_all = Time.now
|
52
|
+
files.each_with_index do |file, i|
|
53
|
+
$stderr.puts " > \e[34mImporting \e[1m#{File.basename(file)}\e[0;34m (%d of %d)\e[0m\n\n" % [
|
54
|
+
i + 1,
|
55
|
+
files.length ]
|
56
|
+
|
57
|
+
reader = Logeater::Reader.new(app, file, options.slice(:progress, :verbose))
|
58
|
+
reader.remove_existing_entries!
|
59
|
+
|
60
|
+
started_count = Logeater::Request.count
|
61
|
+
started_at = Time.now
|
62
|
+
reader.import
|
63
|
+
finished_at = Time.now
|
64
|
+
finished_count = Logeater::Request.count
|
65
|
+
|
66
|
+
$stderr.puts " > \e[34mImported \e[1m%d\e[0;34m requests in \e[1m%.2f\e[0;34m seconds (%d of %d)\e[0m\n\n" % [
|
67
|
+
finished_count - started_count,
|
68
|
+
finished_at - started_at,
|
69
|
+
i + 1,
|
70
|
+
files.length ]
|
71
|
+
end
|
72
|
+
|
73
|
+
finished_all = Time.now
|
74
|
+
seconds = finished_all - started_all
|
75
|
+
minutes = (seconds / 60).to_i
|
76
|
+
seconds -= (minutes * 60)
|
77
|
+
$stderr.puts "Total time %d minutes, %.2f seconds" % [minutes, seconds]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -40,10 +40,15 @@ module Logeater
|
|
40
40
|
|
41
41
|
# [:@int, "10", [1, 14]]
|
42
42
|
when :@int then sexp[1].to_i
|
43
|
-
|
43
|
+
|
44
44
|
# [:@float, "10.56", [1, 14]]
|
45
45
|
when :@float then sexp[1].to_f
|
46
46
|
|
47
|
+
# [:unary, :-@, [:@float, \\\"173.41\\\", [1, 17285]]]
|
48
|
+
when :unary then
|
49
|
+
return -identify(sexp[2]) if sexp[1] == :-@
|
50
|
+
raise Parser::ParserNotImplemented, "Unknown unary operator: #{sexp[1].inspect}"
|
51
|
+
|
47
52
|
# [:var_ref, [:@kw, "true", [1, 12]]]
|
48
53
|
when :var_ref then
|
49
54
|
return true if sexp[1][1] == "true"
|
@@ -54,7 +59,7 @@ module Logeater
|
|
54
59
|
# [:array, [[:@int, "1", [1, 9]], [:@int, "4", [1, 12]]]]
|
55
60
|
# [:array, nil]
|
56
61
|
when :array then sexp[1] ? sexp[1].map { |sexp| identify(sexp) } : []
|
57
|
-
|
62
|
+
|
58
63
|
# [:hash,
|
59
64
|
# [:assoclist_from_args,
|
60
65
|
# [[:assoc_new,
|
data/lib/logeater/parser.rb
CHANGED
@@ -14,20 +14,10 @@ module Logeater
|
|
14
14
|
(?<message>.*)
|
15
15
|
$/x.freeze
|
16
16
|
|
17
|
-
TIMESTAMP_MATCHER = /
|
18
|
-
(?<year>\d\d\d\d)\-
|
19
|
-
(?<month>\d\d)\-
|
20
|
-
(?<day>\d\d)T
|
21
|
-
(?<hours>\d\d):
|
22
|
-
(?<minutes>\d\d):
|
23
|
-
(?<seconds>\d\d(?:\.\d+))
|
24
|
-
/x.freeze
|
25
|
-
|
26
|
-
HTTP_VERBS = %w{DELETE GET HEAD OPTIONS PATCH POST PUT}.freeze
|
27
|
-
|
28
17
|
REQUEST_LINE_MATCHER = /^
|
29
18
|
\[(?<subdomain>[^\]]+)\]\s
|
30
19
|
\[(?<uuid>[\w\-]{36})\]\s+
|
20
|
+
(?:\[(?:guest|user\.(?<user_id>\d+)(?<tester_bar>:cph)?)\]\s+)?
|
31
21
|
(?<message>.*)
|
32
22
|
$/x.freeze
|
33
23
|
|
@@ -53,7 +43,7 @@ module Logeater
|
|
53
43
|
REQUEST_COMPLETED_MATCHER = /^
|
54
44
|
Completed\s
|
55
45
|
(?<http_status>\d\d\d)\s
|
56
|
-
(?<http_response>.*)\
|
46
|
+
(?:(?<http_response>.*)\s)?in\s
|
57
47
|
(?<duration>[\d\.]+)(?<units>ms)\b
|
58
48
|
/x.freeze # optional: (Views: 0.1ms | ActiveRecord: 50.0ms)
|
59
49
|
|
@@ -61,20 +51,13 @@ module Logeater
|
|
61
51
|
match = line.match LINE_MATCHER
|
62
52
|
raise UnmatchedLine.new(line) unless match
|
63
53
|
|
64
|
-
timestamp = match["timestamp"]
|
65
|
-
time = timestamp.match TIMESTAMP_MATCHER
|
66
|
-
raise MalformedTimestamp.new(timestamp) unless time
|
67
|
-
time = Time.new(*time.captures[0...-1], BigDecimal.new(time["seconds"]))
|
68
|
-
|
69
|
-
message = match["message"]
|
70
|
-
|
71
54
|
result = {
|
72
55
|
type: :generic,
|
73
|
-
timestamp:
|
56
|
+
timestamp: match["timestamp"],
|
74
57
|
log_level: match["log_level"],
|
75
|
-
message: message
|
76
|
-
|
77
|
-
|
58
|
+
message: match["message"]
|
59
|
+
}.merge(
|
60
|
+
parse_message(match["message"]))
|
78
61
|
end
|
79
62
|
|
80
63
|
def parse_message(message)
|
@@ -82,71 +65,58 @@ module Logeater
|
|
82
65
|
return {} unless match
|
83
66
|
|
84
67
|
message = match["message"]
|
85
|
-
type = identify_request_line_type(message)
|
86
68
|
|
87
69
|
{ subdomain: match["subdomain"],
|
88
70
|
uuid: match["uuid"],
|
89
|
-
type:
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
return :request_started if message =~ /^Started (#{HTTP_VERBS.join("|")})/
|
96
|
-
return :request_controller if message.start_with? "Processing by "
|
97
|
-
return :request_params if message.start_with? "Parameters: "
|
98
|
-
return :request_completed if message =~ /^Completed \d\d\d/
|
99
|
-
:request_line
|
71
|
+
type: :request_line,
|
72
|
+
user_id: match["user_id"] && match["user_id"].to_i,
|
73
|
+
tester_bar: !!match["tester_bar"],
|
74
|
+
message: message
|
75
|
+
}.merge(
|
76
|
+
parse_message_extra(message))
|
100
77
|
end
|
101
78
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
79
|
+
def parse_message_extra(message)
|
80
|
+
match = message.match(REQUEST_STARTED_MATCHER)
|
81
|
+
return parse_request_started_message(match) if match
|
82
|
+
|
83
|
+
match = message.match(REQUEST_CONTROLLER_MATCHER)
|
84
|
+
return parse_request_controller_message(match) if match
|
85
|
+
|
86
|
+
match = message.match(REQUEST_PARAMETERS_MATCHER)
|
87
|
+
return parse_request_params_message(match) if match
|
88
|
+
|
89
|
+
match = message.match(REQUEST_COMPLETED_MATCHER)
|
90
|
+
return parse_request_completed_message(match) if match
|
91
|
+
|
112
92
|
{}
|
113
93
|
end
|
114
94
|
|
115
|
-
def parse_request_started_message(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
{ http_method: match["http_method"],
|
121
|
-
path: uri.path,
|
95
|
+
def parse_request_started_message(match)
|
96
|
+
{ type: :request_started,
|
97
|
+
http_method: match["http_method"],
|
98
|
+
path: parsed_uri[match["path"]],
|
122
99
|
remote_ip: match["remote_ip"] }
|
123
100
|
end
|
124
101
|
|
125
|
-
def parse_request_controller_message(
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
{ controller: match["controller"].underscore.gsub(/_controller$/, ""),
|
102
|
+
def parse_request_controller_message(match)
|
103
|
+
{ type: :request_controller,
|
104
|
+
controller: normalized_controller_name[match["controller"]],
|
130
105
|
action: match["action"],
|
131
106
|
format: match["format"] }
|
132
107
|
end
|
133
108
|
|
134
|
-
def parse_request_params_message(
|
135
|
-
|
136
|
-
|
137
|
-
params = ParamsParser.new(match["params"])
|
138
|
-
|
139
|
-
{ params: params.parse! }
|
109
|
+
def parse_request_params_message(match)
|
110
|
+
{ type: :request_params,
|
111
|
+
params: ParamsParser.new(match["params"]).parse! }
|
140
112
|
rescue Logeater::Parser::MalformedParameters
|
141
113
|
log "Unable to parse parameters: #{match["params"].inspect}"
|
142
114
|
{ params: match["params"] }
|
143
115
|
end
|
144
116
|
|
145
|
-
def parse_request_completed_message(
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
{ http_status: match["http_status"].to_i,
|
117
|
+
def parse_request_completed_message(match)
|
118
|
+
{ type: :request_completed,
|
119
|
+
http_status: match["http_status"].to_i,
|
150
120
|
http_response: match["http_response"],
|
151
121
|
duration: match["duration"].to_i }
|
152
122
|
end
|
@@ -157,5 +127,21 @@ module Logeater
|
|
157
127
|
$stderr.puts "\e[33m#{statement}\e[0m"
|
158
128
|
end
|
159
129
|
|
130
|
+
|
131
|
+
|
132
|
+
def initialize
|
133
|
+
@normalized_controller_name = Hash.new do |hash, controller_name|
|
134
|
+
hash[controller_name] = controller_name.underscore.gsub(/_controller$/, "")
|
135
|
+
end
|
136
|
+
|
137
|
+
@parsed_uri = Hash.new do |hash, uri|
|
138
|
+
hash[uri] = Addressable::URI.parse(uri).path
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
attr_reader :normalized_controller_name,
|
144
|
+
:parsed_uri
|
145
|
+
|
160
146
|
end
|
161
147
|
end
|
data/lib/logeater/reader.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "logeater/request"
|
2
2
|
require "zlib"
|
3
3
|
require "ruby-progressbar"
|
4
|
+
require "oj"
|
4
5
|
|
5
6
|
module Logeater
|
6
7
|
class Reader
|
@@ -24,12 +25,31 @@ module Logeater
|
|
24
25
|
remove_existing_entries!
|
25
26
|
import
|
26
27
|
end
|
27
|
-
|
28
|
+
|
28
29
|
def import
|
29
|
-
|
30
|
+
each_request do |attributes|
|
31
|
+
completed_requests.push Logeater::Request.new(attributes)
|
32
|
+
save! if completed_requests.length >= batch_size
|
33
|
+
end
|
30
34
|
save!
|
31
35
|
end
|
32
36
|
|
37
|
+
def parse(to: $stdout)
|
38
|
+
to << "["
|
39
|
+
first = true
|
40
|
+
each_request do |attributes|
|
41
|
+
if first
|
42
|
+
first = false
|
43
|
+
else
|
44
|
+
to << ",\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
to << Oj.dump(attributes, mode: :compat)
|
48
|
+
end
|
49
|
+
ensure
|
50
|
+
to << "]"
|
51
|
+
end
|
52
|
+
|
33
53
|
def remove_existing_entries!
|
34
54
|
Logeater::Request.where(app: app, logfile: filename).delete_all
|
35
55
|
end
|
@@ -45,7 +65,7 @@ module Logeater
|
|
45
65
|
def each_line
|
46
66
|
File.open(path) do |file|
|
47
67
|
io = File.extname(path) == ".gz" ? Zlib::GzipReader.new(file) : file
|
48
|
-
pbar = ProgressBar.create(title: filename, total: file.size, autofinish: false) if show_progress?
|
68
|
+
pbar = ProgressBar.create(title: filename, total: file.size, autofinish: false, output: $stderr) if show_progress?
|
49
69
|
io.each_line do |line|
|
50
70
|
yield line
|
51
71
|
pbar.progress = file.pos if show_progress?
|
@@ -55,25 +75,37 @@ module Logeater
|
|
55
75
|
end
|
56
76
|
alias :scan :each_line
|
57
77
|
|
78
|
+
def each_request
|
79
|
+
count = 0
|
80
|
+
each_line do |line|
|
81
|
+
process_line! line do |request|
|
82
|
+
yield request
|
83
|
+
count += 1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
count
|
87
|
+
end
|
88
|
+
|
89
|
+
|
58
90
|
|
59
91
|
private
|
60
92
|
attr_reader :parser, :requests, :completed_requests
|
61
93
|
|
62
|
-
def process_line!(line)
|
94
|
+
def process_line!(line, &block)
|
63
95
|
attributes = parser.parse!(line)
|
64
96
|
|
65
97
|
return if [:generic, :request_line].member? attributes[:type]
|
66
98
|
|
67
99
|
if attributes[:type] == :request_started
|
68
100
|
requests[attributes[:uuid]] = attributes
|
69
|
-
.slice(:uuid, :subdomain, :http_method, :path, :remote_ip)
|
101
|
+
.slice(:uuid, :subdomain, :http_method, :path, :remote_ip, :user_id, :tester_bar)
|
70
102
|
.merge(started_at: attributes[:timestamp], logfile: filename, app: app)
|
71
103
|
return
|
72
104
|
end
|
73
105
|
|
74
106
|
request_attributes = requests[attributes[:uuid]]
|
75
107
|
unless request_attributes
|
76
|
-
log "Attempting to record #{attributes[:type].inspect}; but there is no request started with UUID #{attributes[:uuid].inspect}"
|
108
|
+
log "Attempting to record #{attributes[:type].inspect}; but there is no request started with UUID #{attributes[:uuid].inspect}" if verbose?
|
77
109
|
return
|
78
110
|
end
|
79
111
|
|
@@ -89,10 +121,8 @@ module Logeater
|
|
89
121
|
.slice(:http_status, :http_response, :duration)
|
90
122
|
.merge(completed_at: attributes[:timestamp])
|
91
123
|
|
92
|
-
|
124
|
+
yield request_attributes
|
93
125
|
requests.delete attributes[:uuid]
|
94
|
-
|
95
|
-
save! if completed_requests.length >= batch_size
|
96
126
|
end
|
97
127
|
|
98
128
|
rescue Logeater::Parser::UnmatchedLine
|
data/lib/logeater/request.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
require "active_record"
|
2
2
|
require "activerecord-import"
|
3
|
-
require "activerecord-postgres-json"
|
4
3
|
|
5
4
|
module Logeater
|
6
5
|
class Request < ActiveRecord::Base
|
7
6
|
self.table_name = "requests"
|
8
|
-
|
9
|
-
serialize :params, ActiveRecord::Coders::JSON
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
|
13
8
|
end
|
14
9
|
end
|
data/lib/logeater/version.rb
CHANGED
data/lib/logeater.rb
CHANGED
@@ -5,9 +5,24 @@ require "logeater/version"
|
|
5
5
|
require "yaml"
|
6
6
|
require "erb"
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
database_url = ENV["DATABASE_URL"] unless ENV["RAILS_ENV"] == "test"
|
9
|
+
if database_url
|
10
|
+
uri = Addressable::URI.parse(database_url)
|
11
|
+
config = {
|
12
|
+
adapter: "postgresql",
|
13
|
+
encoding: "utf8",
|
14
|
+
min_messages: "WARNING",
|
15
|
+
database: uri.path[1..-1],
|
16
|
+
host: uri.host,
|
17
|
+
username: uri.user,
|
18
|
+
password: uri.password,
|
19
|
+
port: uri.port
|
20
|
+
}
|
21
|
+
else
|
22
|
+
config_file = File.expand_path("../../db/config.yml", __FILE__)
|
23
|
+
config = YAML.load(ERB.new(File.read(config_file)).result).with_indifferent_access[ENV["RAILS_ENV"] || "development"]
|
24
|
+
end
|
25
|
+
ActiveRecord::Base.establish_connection config
|
11
26
|
|
12
27
|
module Logeater
|
13
28
|
end
|
data/logeater.gemspec
CHANGED
@@ -18,21 +18,21 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "activerecord", "
|
22
|
-
spec.add_dependency "activesupport"
|
21
|
+
spec.add_dependency "activerecord", ">= 3.1.0", "< 5.0.0"
|
22
|
+
spec.add_dependency "activesupport", ">= 3.1.0", "< 5.0.0"
|
23
23
|
spec.add_dependency "pg"
|
24
24
|
spec.add_dependency "standalone_migrations"
|
25
25
|
spec.add_dependency "addressable"
|
26
26
|
spec.add_dependency "ruby-progressbar"
|
27
|
-
spec.add_dependency "activerecord-import"
|
28
|
-
spec.add_dependency "
|
27
|
+
spec.add_dependency "activerecord-import"
|
28
|
+
spec.add_dependency "thor"
|
29
|
+
spec.add_dependency "oj"
|
29
30
|
|
30
31
|
spec.add_development_dependency "bundler", "~> 1.7"
|
31
32
|
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
-
spec.add_development_dependency "rails", ">= 3.1.0", "< 5.0.0"
|
33
33
|
spec.add_development_dependency "pry"
|
34
34
|
spec.add_development_dependency "shoulda-context"
|
35
|
-
spec.add_development_dependency "
|
35
|
+
spec.add_development_dependency "minitest-reporters"
|
36
36
|
spec.add_development_dependency "database_cleaner"
|
37
37
|
|
38
38
|
end
|
@@ -28,8 +28,8 @@ class LogeaterTest < ActiveSupport::TestCase
|
|
28
28
|
assert_equal "single_request.log", request.logfile
|
29
29
|
assert_equal "0fc5154a-c288-4bad-9c7a-de3d7e7d2496", request.uuid
|
30
30
|
assert_equal "livingsaviorco", request.subdomain
|
31
|
-
assert_equal Time.
|
32
|
-
assert_equal Time.
|
31
|
+
assert_equal Time.utc(2015, 1, 10, 15, 18, BigDecimal.new("12.064392")), request.started_at
|
32
|
+
assert_equal Time.utc(2015, 1, 10, 15, 18, BigDecimal.new("12.262903")), request.completed_at
|
33
33
|
assert_equal 196, request.duration
|
34
34
|
assert_equal "GET", request.http_method
|
35
35
|
assert_equal "/people/1035826228", request.path
|
data/test/test_helper.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
ENV["RAILS_ENV"] = "test"
|
2
2
|
require "rubygems"
|
3
|
-
require "
|
4
|
-
require "
|
3
|
+
require "active_support/testing/autorun"
|
4
|
+
require "active_support/test_case"
|
5
5
|
require "pry"
|
6
6
|
require "database_cleaner"
|
7
7
|
require "shoulda/context"
|
8
|
-
require "turn"
|
9
8
|
require "logeater"
|
10
9
|
|
10
|
+
require "minitest/reporters"
|
11
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
12
|
+
|
11
13
|
load File.expand_path("../../db/schema.rb", __FILE__)
|
12
14
|
|
13
15
|
DatabaseCleaner.strategy = :truncation
|
@@ -16,6 +16,11 @@ class ParamsParserTest < ActiveSupport::TestCase
|
|
16
16
|
assert_parses '{"person_id"=>10.56}' => {"person_id"=>10.56}
|
17
17
|
end
|
18
18
|
|
19
|
+
should "handle negatives" do
|
20
|
+
assert_parses '{"person_id"=>-10}' => {"person_id"=>-10}
|
21
|
+
assert_parses '{"person_id"=>-10.56}' => {"person_id"=>-10.56}
|
22
|
+
end
|
23
|
+
|
19
24
|
should "handle booleans" do
|
20
25
|
assert_parses '{"visible"=>true}' => {"visible"=>true}
|
21
26
|
end
|
data/test/unit/parser_test.rb
CHANGED
@@ -33,7 +33,7 @@ class ParserTest < ActiveSupport::TestCase
|
|
33
33
|
end
|
34
34
|
|
35
35
|
should "identify the time, including milliseconds" do
|
36
|
-
assert_parses timestamp:
|
36
|
+
assert_parses timestamp: "2015-01-10T15:18:05.850839"
|
37
37
|
end
|
38
38
|
|
39
39
|
should "identify the remainder of the log message" do
|
@@ -67,6 +67,56 @@ class ParserTest < ActiveSupport::TestCase
|
|
67
67
|
|
68
68
|
|
69
69
|
|
70
|
+
context "given a log line for a Rails request" do
|
71
|
+
context "that indicates the current user (who was logged in with the tester bar), it" do
|
72
|
+
setup do
|
73
|
+
@line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [user.902544074:cph] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
|
74
|
+
end
|
75
|
+
|
76
|
+
should "identify the user's id" do
|
77
|
+
assert_parses user_id: 902544074
|
78
|
+
end
|
79
|
+
|
80
|
+
should "notice that the user was logged-in with the tester bar" do
|
81
|
+
assert_parses tester_bar: true
|
82
|
+
end
|
83
|
+
|
84
|
+
should "identify the remainder of the log message" do
|
85
|
+
assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "that indicates the current user (who was not logged in with the tester bar), it" do
|
90
|
+
setup do
|
91
|
+
@line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [user.902544074] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
|
92
|
+
end
|
93
|
+
|
94
|
+
should "identify the user's id" do
|
95
|
+
assert_parses user_id: 902544074
|
96
|
+
end
|
97
|
+
|
98
|
+
should "notice that the user was not logged-in with the tester bar" do
|
99
|
+
assert_parses tester_bar: false
|
100
|
+
end
|
101
|
+
|
102
|
+
should "identify the remainder of the log message" do
|
103
|
+
assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "that indicates that the user is logged out, it" do
|
108
|
+
setup do
|
109
|
+
@line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [guest] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
|
110
|
+
end
|
111
|
+
|
112
|
+
should "identify the remainder of the log message" do
|
113
|
+
assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
70
120
|
context "given the \"Started\" line, it" do
|
71
121
|
setup do
|
72
122
|
@line = "I, [2015-01-10T15:18:12.064392 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Started GET \"/people/1035826228?refresh_page=true\" for 71.218.222.249 at 2015-01-10 15:18:12 +0000"
|
@@ -155,6 +205,16 @@ class ParserTest < ActiveSupport::TestCase
|
|
155
205
|
end
|
156
206
|
end
|
157
207
|
|
208
|
+
context "when the \"Completed\" line doesn't have a textual description of the status, it" do
|
209
|
+
setup do
|
210
|
+
@line = "I, [2015-01-10T15:18:12.262903 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Completed 307 in 0.8ms"
|
211
|
+
end
|
212
|
+
|
213
|
+
should "be cool with that" do
|
214
|
+
assert_parses http_status: 307, http_response: nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
158
218
|
context "when the \"Completed\" line contains a breakdown of times, it" do
|
159
219
|
setup do
|
160
220
|
@line = "I, [2015-01-10T15:18:12.262903 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Completed 200 OK in 196ms (Views: 0.1ms | ActiveRecord: 50.0ms)"
|
metadata
CHANGED
@@ -1,43 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logeater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Lail
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.1.0
|
20
|
+
- - "<"
|
18
21
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
22
|
+
version: 5.0.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.
|
29
|
+
version: 3.1.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 5.0.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activesupport
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
39
|
+
version: 3.1.0
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 5.0.0
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
47
|
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
49
|
+
version: 3.1.0
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 5.0.0
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: pg
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,18 +110,18 @@ dependencies:
|
|
98
110
|
name: activerecord-import
|
99
111
|
requirement: !ruby/object:Gem::Requirement
|
100
112
|
requirements:
|
101
|
-
- - "
|
113
|
+
- - ">="
|
102
114
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0
|
115
|
+
version: '0'
|
104
116
|
type: :runtime
|
105
117
|
prerelease: false
|
106
118
|
version_requirements: !ruby/object:Gem::Requirement
|
107
119
|
requirements:
|
108
|
-
- - "
|
120
|
+
- - ">="
|
109
121
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0
|
122
|
+
version: '0'
|
111
123
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
124
|
+
name: thor
|
113
125
|
requirement: !ruby/object:Gem::Requirement
|
114
126
|
requirements:
|
115
127
|
- - ">="
|
@@ -123,53 +135,47 @@ dependencies:
|
|
123
135
|
- !ruby/object:Gem::Version
|
124
136
|
version: '0'
|
125
137
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
138
|
+
name: oj
|
127
139
|
requirement: !ruby/object:Gem::Requirement
|
128
140
|
requirements:
|
129
|
-
- - "
|
141
|
+
- - ">="
|
130
142
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
132
|
-
type: :
|
143
|
+
version: '0'
|
144
|
+
type: :runtime
|
133
145
|
prerelease: false
|
134
146
|
version_requirements: !ruby/object:Gem::Requirement
|
135
147
|
requirements:
|
136
|
-
- - "
|
148
|
+
- - ">="
|
137
149
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
150
|
+
version: '0'
|
139
151
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
152
|
+
name: bundler
|
141
153
|
requirement: !ruby/object:Gem::Requirement
|
142
154
|
requirements:
|
143
155
|
- - "~>"
|
144
156
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
157
|
+
version: '1.7'
|
146
158
|
type: :development
|
147
159
|
prerelease: false
|
148
160
|
version_requirements: !ruby/object:Gem::Requirement
|
149
161
|
requirements:
|
150
162
|
- - "~>"
|
151
163
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
164
|
+
version: '1.7'
|
153
165
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
166
|
+
name: rake
|
155
167
|
requirement: !ruby/object:Gem::Requirement
|
156
168
|
requirements:
|
157
|
-
- - "
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: 3.1.0
|
160
|
-
- - "<"
|
169
|
+
- - "~>"
|
161
170
|
- !ruby/object:Gem::Version
|
162
|
-
version:
|
171
|
+
version: '10.0'
|
163
172
|
type: :development
|
164
173
|
prerelease: false
|
165
174
|
version_requirements: !ruby/object:Gem::Requirement
|
166
175
|
requirements:
|
167
|
-
- - "
|
168
|
-
- !ruby/object:Gem::Version
|
169
|
-
version: 3.1.0
|
170
|
-
- - "<"
|
176
|
+
- - "~>"
|
171
177
|
- !ruby/object:Gem::Version
|
172
|
-
version:
|
178
|
+
version: '10.0'
|
173
179
|
- !ruby/object:Gem::Dependency
|
174
180
|
name: pry
|
175
181
|
requirement: !ruby/object:Gem::Requirement
|
@@ -199,7 +205,7 @@ dependencies:
|
|
199
205
|
- !ruby/object:Gem::Version
|
200
206
|
version: '0'
|
201
207
|
- !ruby/object:Gem::Dependency
|
202
|
-
name:
|
208
|
+
name: minitest-reporters
|
203
209
|
requirement: !ruby/object:Gem::Requirement
|
204
210
|
requirements:
|
205
211
|
- - ">="
|
@@ -244,8 +250,12 @@ files:
|
|
244
250
|
- db/config.yml
|
245
251
|
- db/migrate/20150110151439_create_requests.rb
|
246
252
|
- db/migrate/20150122021627_change_requests_params_to_json.rb
|
253
|
+
- db/migrate/20150207183757_change_requests_params_to_jsonb.rb
|
254
|
+
- db/migrate/20150224021844_change_requests_path_to_text.rb
|
255
|
+
- db/migrate/20150602022241_add_user_id_and_tester_bar_to_requests.rb
|
247
256
|
- db/schema.rb
|
248
257
|
- lib/logeater.rb
|
258
|
+
- lib/logeater/cli.rb
|
249
259
|
- lib/logeater/params_parser.rb
|
250
260
|
- lib/logeater/parser.rb
|
251
261
|
- lib/logeater/parser_errors.rb
|