logeater 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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