logeater 0.1.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.
data/logeater.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "logeater/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "logeater"
8
+ spec.version = Logeater::VERSION
9
+ spec.authors = ["Bob Lail"]
10
+ spec.email = ["bob.lail@cph.org"]
11
+ spec.summary = %q{Parses log files and imports them into a database}
12
+ spec.description = %q{Parses log files and imports them into a database}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", "~> 3.2.21"
22
+ spec.add_dependency "activesupport"
23
+ spec.add_dependency "pg"
24
+ spec.add_dependency "standalone_migrations"
25
+ spec.add_dependency "addressable"
26
+ spec.add_dependency "ruby-progressbar"
27
+ spec.add_dependency "activerecord-import", "~> 0.3.1"
28
+ spec.add_dependency "activerecord-postgres-json"
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.7"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rails", ">= 3.1.0", "< 5.0.0"
33
+ spec.add_development_dependency "pry"
34
+ spec.add_development_dependency "shoulda-context"
35
+ spec.add_development_dependency "turn"
36
+ spec.add_development_dependency "database_cleaner"
37
+
38
+ end
Binary file
@@ -0,0 +1,9 @@
1
+ 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
2
+ I, [2015-01-10T15:18:12.067034 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Processing by PeopleController#show as JS
3
+ I, [2015-01-10T15:18:12.067134 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Parameters: {"refresh_page"=>"true", "id"=>"1035826228"}
4
+ I, [2015-01-10T15:18:12.156216 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Rendered people/_name.html.erb (2.3ms)
5
+ I, [2015-01-10T15:18:12.159557 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Rendered people/_status.html.erb (3.1ms)
6
+ I, [2015-01-10T15:18:12.177934 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Rendered people/_household.html.erb (18.2ms)
7
+ I, [2015-01-10T15:18:12.184299 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Rendered person_tags/_tag.html.erb (1.1ms)
8
+ I, [2015-01-10T15:18:12.262438 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] [gzip] Compress reponse by 82.5 KB (87.6%) (2.6ms)
9
+ 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)
@@ -0,0 +1,80 @@
1
+ require "test_helper"
2
+
3
+ class LogeaterTest < ActiveSupport::TestCase
4
+ attr_reader :logfile
5
+
6
+
7
+ context "Given the log of a single request, it" do
8
+ setup do
9
+ @logfile = File.expand_path("../../data/single_request.log", __FILE__)
10
+ end
11
+
12
+ should "identify the name of the logfile" do
13
+ assert_equal "single_request.log", reader.filename
14
+ end
15
+
16
+ should "create an entry in the database" do
17
+ assert_difference "Logeater::Request.count", +1 do
18
+ reader.import
19
+ end
20
+ end
21
+
22
+ should "set all the attributes" do
23
+ reader.import
24
+ request = Logeater::Request.first
25
+
26
+ params = {"refresh_page" => "true", "id" => "1035826228"}
27
+ assert_equal "test", request.app
28
+ assert_equal "single_request.log", request.logfile
29
+ assert_equal "0fc5154a-c288-4bad-9c7a-de3d7e7d2496", request.uuid
30
+ assert_equal "livingsaviorco", request.subdomain
31
+ assert_equal Time.new(2015, 1, 10, 15, 18, BigDecimal.new("12.064392")), request.started_at
32
+ assert_equal Time.new(2015, 1, 10, 15, 18, BigDecimal.new("12.262903")), request.completed_at
33
+ assert_equal 196, request.duration
34
+ assert_equal "GET", request.http_method
35
+ assert_equal "/people/1035826228", request.path
36
+ assert_equal params, request.params
37
+ assert_equal "people", request.controller
38
+ assert_equal "show", request.action
39
+ assert_equal "71.218.222.249", request.remote_ip
40
+ assert_equal "JS", request.format
41
+ assert_equal 200, request.http_status
42
+ assert_equal "OK", request.http_response
43
+ end
44
+
45
+ should "erase any entries that had already been imported with that app and filename" do
46
+ Logeater::Request.create!(app: app, logfile: "single_request.log", uuid: "1")
47
+ Logeater::Request.create!(app: app, logfile: "single_request.log", uuid: "2")
48
+ Logeater::Request.create!(app: app, logfile: "single_request.log", uuid: "3")
49
+
50
+ assert_difference "Logeater::Request.count", -2 do
51
+ reader.reimport
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ context "Given a gzipped logfile, it" do
58
+ setup do
59
+ @logfile = File.expand_path("../../data/single_request.gz", __FILE__)
60
+ end
61
+
62
+ should "create an entry in the database" do
63
+ assert_difference "Logeater::Request.count", +1 do
64
+ reader.import
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ private
71
+
72
+ def app
73
+ "test"
74
+ end
75
+
76
+ def reader
77
+ Logeater::Reader.new(app, logfile)
78
+ end
79
+
80
+ end
@@ -0,0 +1,28 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require "rubygems"
3
+ require "rails"
4
+ require "rails/test_help"
5
+ require "pry"
6
+ require "database_cleaner"
7
+ require "shoulda/context"
8
+ require "turn"
9
+ require "logeater"
10
+
11
+ load File.expand_path("../../db/schema.rb", __FILE__)
12
+
13
+ DatabaseCleaner.strategy = :truncation
14
+ DatabaseCleaner.clean
15
+
16
+ DatabaseCleaner.strategy = :transaction
17
+
18
+ class ActiveSupport::TestCase
19
+
20
+ setup do
21
+ DatabaseCleaner.start
22
+ end
23
+
24
+ teardown do
25
+ DatabaseCleaner.clean
26
+ end
27
+
28
+ end
@@ -0,0 +1,96 @@
1
+ require "test_helper"
2
+
3
+ class ParamsParserTest < ActiveSupport::TestCase
4
+
5
+
6
+ context "Given a simple hash, it" do
7
+ should "parse it" do
8
+ assert_parses '{"utf8"=>"✓"}' => {"utf8"=>"✓"}
9
+ end
10
+
11
+ should "handle integers" do
12
+ assert_parses '{"person_id"=>10}' => {"person_id"=>10}
13
+ end
14
+
15
+ should "handle floats" do
16
+ assert_parses '{"person_id"=>10.56}' => {"person_id"=>10.56}
17
+ end
18
+
19
+ should "handle booleans" do
20
+ assert_parses '{"visible"=>true}' => {"visible"=>true}
21
+ end
22
+
23
+ should "handle nil" do
24
+ assert_parses '{"visible"=>nil}' => {"visible"=>nil}
25
+ end
26
+
27
+ should "handle arrays" do
28
+ assert_parses '{"ids"=>[1, 4]}' => {"ids"=>[1,4]}
29
+ end
30
+
31
+
32
+
33
+ should "handle empty strings" do
34
+ assert_parses '{"visible"=>""}' => {"visible"=>""}
35
+ end
36
+
37
+ should "handle empty arrays" do
38
+ assert_parses '{"array"=>[]}' => {"array"=>[]}
39
+ end
40
+
41
+ should "handle empty hashes" do
42
+ assert_parses '{"hash"=>{}}' => {"hash"=>{}}
43
+ end
44
+ end
45
+
46
+
47
+ context "Given a hash with more than one key, it" do
48
+ should "parse it" do
49
+ assert_parses '{"utf8"=>"✓", "authenticity_token"=>"kDM07..."}' => {"utf8"=>"✓", "authenticity_token"=>"kDM07..."}
50
+ end
51
+ end
52
+
53
+
54
+ context "Given a hash with a nested hash, it" do
55
+ should "handle nested hashes" do
56
+ assert_parses '{"person"=>{"name"=>"Tim"}}' => {"person"=>{"name"=>"Tim"}}
57
+ end
58
+
59
+ should "handle arrays of nested hashes" do
60
+ assert_parses '{"people"=>[{"id"=>1},{"id"=>2}]}' => {"people"=>[{"id"=>1},{"id"=>2}]}
61
+ end
62
+ end
63
+
64
+
65
+ context "Given a hash with a serialized Ruby object, it" do
66
+ should "parse it" do
67
+ assert_parses '{"tempfile"=>#<Tempfile:/tmp/RackMultipart20141213-1847-1c8fpzw>}' => {"tempfile"=>"Tempfile"}
68
+ end
69
+
70
+ should "ignore the object's ivars" do
71
+ assert_parses '{"photo"=>#<ActionDispatch::Http::UploadedFile:0x007f7c685318a8 @tempfile=#<Tempfile:/tmp/RackMultipart20141213-1847-1c8fpzw>, @original_filename="Martin-008.JPG", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"person[photo]\"; filename=\"Martin-008.JPG\"\\r\\nContent-Type: image/jpeg\\r\\n\">}' => {"photo"=>"ActionDispatch::Http::UploadedFile"}
72
+ end
73
+ end
74
+
75
+
76
+ context "Given an invalid input, it" do
77
+ should "raise MalformedParameters" do
78
+ assert_raises Logeater::Parser::MalformedParameters do
79
+ parse! '{"nope"=>}'
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ private
86
+
87
+ def assert_parses(params)
88
+ value, result = params.to_a[0]
89
+ assert_equal result, parse!(value)
90
+ end
91
+
92
+ def parse!(value)
93
+ Logeater::ParamsParser.new(value).parse!
94
+ end
95
+
96
+ end
@@ -0,0 +1,188 @@
1
+ require "test_helper"
2
+
3
+ class ParserTest < ActiveSupport::TestCase
4
+ attr_reader :line
5
+
6
+
7
+
8
+ context "given a line that doesn't start with the generic log prefix, it" do
9
+ setup do
10
+ @line = "\n"
11
+ end
12
+
13
+ should "raise UnmatchedLine" do
14
+ assert_raises Logeater::Parser::UnmatchedLine do
15
+ parse!
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+
22
+ context "given any generic log line, it" do
23
+ setup do
24
+ @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : the message\n"
25
+ end
26
+
27
+ should "identify the line as :generic" do
28
+ assert_parses type: :generic
29
+ end
30
+
31
+ should "identify the log level" do
32
+ assert_parses log_level: "INFO"
33
+ end
34
+
35
+ should "identify the time, including milliseconds" do
36
+ assert_parses timestamp: Time.new(2015, 1, 10, 15, 18, BigDecimal.new("5.850839"))
37
+ end
38
+
39
+ should "identify the remainder of the log message" do
40
+ assert_parses message: "the message"
41
+ end
42
+ end
43
+
44
+
45
+
46
+ context "given a log line for a Rails request, it" do
47
+ setup do
48
+ @line = "I, [2015-01-10T15:18:05.850839 #18070] INFO -- : [livingsaviorco] [2d89d962-57c4-47c9-a9e9-6a16a5f22a12] [gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
49
+ end
50
+
51
+ should "identify the line as :generic" do
52
+ assert_parses type: :request_line
53
+ end
54
+
55
+ should "identify the subdomain" do
56
+ assert_parses subdomain: "livingsaviorco"
57
+ end
58
+
59
+ should "identify the request's ID" do
60
+ assert_parses uuid: "2d89d962-57c4-47c9-a9e9-6a16a5f22a12"
61
+ end
62
+
63
+ should "identify the remainder of the log message" do
64
+ assert_parses message: "[gzip] Compress reponse by 42.2 KB (83.3%) (1.4ms)"
65
+ end
66
+ end
67
+
68
+
69
+
70
+ context "given the \"Started\" line, it" do
71
+ setup do
72
+ @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"
73
+ end
74
+
75
+ should "identify the line as :request_started" do
76
+ assert_parses type: :request_started
77
+ end
78
+
79
+ should "identify the HTTP method" do
80
+ assert_parses http_method: "GET"
81
+ end
82
+
83
+ should "identify the path (without params)" do
84
+ assert_parses path: "/people/1035826228"
85
+ end
86
+
87
+ should "identify the remote client's IP address" do
88
+ assert_parses remote_ip: "71.218.222.249"
89
+ end
90
+ end
91
+
92
+
93
+
94
+ context "given the \"Processing by\" line, it" do
95
+ setup do
96
+ @line = "I, [2015-01-10T15:18:12.067034 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Processing by Api::V1::PeopleController#show as JS"
97
+ end
98
+
99
+ should "identify the line as :request_controller" do
100
+ assert_parses type: :request_controller
101
+ end
102
+
103
+ should "identify the controller and action" do
104
+ assert_parses controller: "api/v1/people", action: "show"
105
+ end
106
+
107
+ should "identify the format requested" do
108
+ assert_parses format: "JS"
109
+ end
110
+ end
111
+
112
+
113
+
114
+ context "given the \"Parameters\" line, it" do
115
+ setup do
116
+ @line = "I, [2015-01-10T15:18:12.067134 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Parameters: {\"refresh_page\"=>\"true\", \"id\"=>\"1035826228\"}"
117
+ end
118
+
119
+ should "identify the line as :request_params" do
120
+ assert_parses type: :request_params
121
+ end
122
+
123
+ should "identify the params" do
124
+ assert_parses params: {"refresh_page" => "true", "id" => "1035826228"}
125
+ end
126
+ end
127
+
128
+ context "when the \"Parameters\" line contains invalid syntax, it" do
129
+ setup do
130
+ @line = "I, [2015-01-10T15:18:12.067134 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Parameters: {\"refresh_page\"=>}"
131
+ end
132
+
133
+ should "return the params unparsed" do
134
+ assert_parses params: "{\"refresh_page\"=>}"
135
+ end
136
+ end
137
+
138
+
139
+
140
+ context "given the \"Completed\" line, it" do
141
+ setup do
142
+ @line = "I, [2015-01-10T15:18:12.262903 #2354] INFO -- : [livingsaviorco] [0fc5154a-c288-4bad-9c7a-de3d7e7d2496] Completed 401 Unauthorized in 2ms"
143
+ end
144
+
145
+ should "identify the line as :request_completed" do
146
+ assert_parses type: :request_completed
147
+ end
148
+
149
+ should "identify the HTTP response" do
150
+ assert_parses http_status: 401, http_response: "Unauthorized"
151
+ end
152
+
153
+ should "identify the duration of the request" do
154
+ assert_parses duration: 2
155
+ end
156
+ end
157
+
158
+ context "when the \"Completed\" line contains a breakdown of times, it" do
159
+ setup do
160
+ @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)"
161
+ end
162
+
163
+ should "identify the duration of the request" do
164
+ assert_parses duration: 196
165
+ end
166
+ end
167
+
168
+
169
+
170
+ private
171
+
172
+ def assert_parses(expectations)
173
+ results = parse!
174
+
175
+ expectations.each do |key, value|
176
+ assert_equal value, results[key]
177
+ end
178
+ end
179
+
180
+ def parse!
181
+ parser.parse!(line)
182
+ end
183
+
184
+ def parser
185
+ Logeater::Parser.new
186
+ end
187
+
188
+ end