request-log-analyzer 1.10.0 → 1.11.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.
- data/.gitignore +2 -1
- data/.infinity_test +8 -0
- data/bin/request-log-analyzer +0 -1
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +2 -1
- data/lib/request_log_analyzer/aggregator.rb +5 -5
- data/lib/request_log_analyzer/controller.rb +0 -3
- data/lib/request_log_analyzer/database.rb +6 -7
- data/lib/request_log_analyzer/file_format/apache.rb +1 -1
- data/lib/request_log_analyzer/file_format/delayed_job2.rb +2 -2
- data/lib/request_log_analyzer/file_format/delayed_job21.rb +2 -2
- data/lib/request_log_analyzer/file_format/haproxy.rb +107 -13
- data/lib/request_log_analyzer/file_format/mysql.rb +5 -5
- data/lib/request_log_analyzer/file_format/rails3.rb +8 -1
- data/lib/request_log_analyzer/file_format.rb +43 -14
- data/lib/request_log_analyzer/filter.rb +4 -5
- data/lib/request_log_analyzer/line_definition.rb +6 -4
- data/lib/request_log_analyzer/output.rb +3 -5
- data/lib/request_log_analyzer/source/log_parser.rb +56 -4
- data/lib/request_log_analyzer/source.rb +3 -4
- data/lib/request_log_analyzer/tracker.rb +8 -8
- data/lib/request_log_analyzer.rb +15 -29
- data/request-log-analyzer.gemspec +3 -3
- data/spec/fixtures/mysql_slow_query.log +0 -1
- data/spec/integration/command_line_usage_spec.rb +0 -5
- data/spec/lib/helpers.rb +2 -2
- data/spec/lib/matchers.rb +38 -7
- data/spec/lib/mocks.rb +1 -5
- data/spec/unit/database/base_class_spec.rb +1 -0
- data/spec/unit/file_format/amazon_s3_format_spec.rb +58 -55
- data/spec/unit/file_format/apache_format_spec.rb +74 -162
- data/spec/unit/file_format/common_regular_expressions_spec.rb +51 -26
- data/spec/unit/file_format/delayed_job21_format_spec.rb +22 -31
- data/spec/unit/file_format/delayed_job2_format_spec.rb +27 -32
- data/spec/unit/file_format/delayed_job_format_spec.rb +44 -63
- data/spec/unit/file_format/haproxy_format_spec.rb +69 -71
- data/spec/unit/file_format/line_definition_spec.rb +26 -33
- data/spec/unit/file_format/merb_format_spec.rb +22 -37
- data/spec/unit/file_format/mysql_format_spec.rb +80 -123
- data/spec/unit/file_format/oink_format_spec.rb +29 -61
- data/spec/unit/file_format/postgresql_format_spec.rb +2 -4
- data/spec/unit/file_format/rack_format_spec.rb +49 -44
- data/spec/unit/file_format/rails3_format_spec.rb +39 -37
- data/spec/unit/file_format/rails_format_spec.rb +52 -68
- data/spec/unit/file_format/w3c_format_spec.rb +40 -39
- data/spec/unit/source/log_parser_spec.rb +1 -1
- metadata +4 -6
- data/lib/mixins/gets_memory_protection.rb +0 -80
- data/lib/request_log_analyzer/output/fancy_html.rb +0 -44
- data/lib/request_log_analyzer/source/database_loader.rb +0 -87
|
@@ -5,186 +5,170 @@ describe RequestLogAnalyzer::FileFormat::Rails do
|
|
|
5
5
|
describe '.create' do
|
|
6
6
|
|
|
7
7
|
context 'without providing a lines argument' do
|
|
8
|
-
|
|
8
|
+
subject { RequestLogAnalyzer::FileFormat::Rails.create }
|
|
9
9
|
|
|
10
|
-
it
|
|
11
|
-
|
|
12
|
-
end
|
|
10
|
+
it { should be_well_formed }
|
|
11
|
+
it { should have(11).report_trackers }
|
|
13
12
|
|
|
14
|
-
it "should parse the production
|
|
15
|
-
|
|
16
|
-
@rails.line_definitions.should == production_rails.line_definitions
|
|
13
|
+
it "should parse the lines in the production set" do
|
|
14
|
+
subject.line_definitions.should == RequestLogAnalyzer::FileFormat.load(:rails, 'production').line_definitions
|
|
17
15
|
end
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
context 'using a comma separated list of lines as argument' do
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
it "should return a valid language" do
|
|
24
|
-
@rails.should be_valid
|
|
25
|
-
end
|
|
19
|
+
subject { RequestLogAnalyzer::FileFormat.load(:rails, 'minimal,failure') }
|
|
26
20
|
|
|
27
|
-
it
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
it { should be_well_formed }
|
|
22
|
+
it { should have(10).report_trackers }
|
|
23
|
+
|
|
24
|
+
it { should have_line_definition(:processing) }
|
|
25
|
+
it { should have_line_definition(:completed) }
|
|
26
|
+
it { should have_line_definition(:failure) }
|
|
30
27
|
end
|
|
31
28
|
|
|
32
29
|
RequestLogAnalyzer::FileFormat::Rails::LINE_COLLECTIONS.keys.each do |constant|
|
|
33
30
|
|
|
34
31
|
context "using the '#{constant}' line collection constant" do
|
|
35
|
-
|
|
32
|
+
subject { RequestLogAnalyzer::FileFormat.load(:rails, constant) }
|
|
36
33
|
|
|
37
|
-
it
|
|
38
|
-
|
|
39
|
-
end
|
|
34
|
+
it { should be_well_formed }
|
|
35
|
+
it { should have_at_least(9).report_trackers }
|
|
40
36
|
|
|
41
|
-
it
|
|
42
|
-
|
|
43
|
-
end
|
|
37
|
+
it { should have_line_definition(:processing) }
|
|
38
|
+
it { should have_line_definition(:completed) }
|
|
44
39
|
end
|
|
45
40
|
end
|
|
46
41
|
end
|
|
42
|
+
|
|
43
|
+
subject { RequestLogAnalyzer::FileFormat.load(:rails, :all) }
|
|
47
44
|
|
|
48
45
|
describe '#parse_line' do
|
|
49
|
-
before(:each) { @rails = RequestLogAnalyzer::FileFormat.load(:rails, :all) }
|
|
50
46
|
|
|
51
47
|
{'with prefix' => 'LINE PREFIX: ', 'without prefix' => '' }.each do |context, prefix|
|
|
52
48
|
context context do
|
|
53
49
|
it "should parse a :processing line correctly" do
|
|
54
50
|
line = prefix + 'Processing PeopleController#index (for 1.1.1.1 at 2008-08-14 21:16:30) [GET]'
|
|
55
|
-
|
|
51
|
+
subject.should parse_line(line).as(:processing).and_capture(:controller => 'PeopleController', :action => 'index', :timestamp => 20080814211630, :method => 'GET', :ip => '1.1.1.1')
|
|
56
52
|
end
|
|
57
53
|
|
|
58
54
|
it "should parse a :processing line correctly when it contains ipv6 localhost address" do
|
|
59
55
|
line = prefix + 'Processing PeopleController#index (for ::1 at 2008-08-14 21:16:30) [GET]'
|
|
60
|
-
|
|
56
|
+
subject.should parse_line(line).as(:processing).and_capture(:controller => 'PeopleController', :action => 'index', :timestamp => 20080814211630, :method => 'GET', :ip => '::1')
|
|
61
57
|
end
|
|
62
58
|
|
|
63
59
|
it "should parse a :processing line correctly when it contains ipv6 address" do
|
|
64
60
|
line = prefix + 'Processing PeopleController#index (for 3ffe:1900:4545:3:200:f8ff:fe21:67cf at 2008-08-14 21:16:30) [GET]'
|
|
65
|
-
|
|
61
|
+
subject.should parse_line(line).as(:processing).and_capture(:controller => 'PeopleController', :action => 'index', :timestamp => 20080814211630, :method => 'GET', :ip => '3ffe:1900:4545:3:200:f8ff:fe21:67cf')
|
|
66
62
|
end
|
|
67
63
|
|
|
68
64
|
it "should parse a Rails 2.1 style :completed line correctly" do
|
|
69
65
|
line = prefix + 'Completed in 0.21665 (4 reqs/sec) | Rendering: 0.00926 (4%) | DB: 0.00000 (0%) | 200 OK [http://demo.nu/employees]'
|
|
70
|
-
|
|
66
|
+
subject.should parse_line(line).as(:completed).and_capture(:duration => 0.21665, :db => 0.0, :view => 0.00926, :status => 200, :url => 'http://demo.nu/employees')
|
|
71
67
|
end
|
|
72
68
|
|
|
73
69
|
it "should parse a Rails 2.2 style :completed line correctly" do
|
|
74
70
|
line = prefix + 'Completed in 614ms (View: 120, DB: 31) | 200 OK [http://floorplanner.local/demo]'
|
|
75
|
-
|
|
71
|
+
subject.should parse_line(line).as(:completed).and_capture(:duration => 0.614, :db => 0.031, :view => 0.120, :status => 200, :url => 'http://floorplanner.local/demo')
|
|
76
72
|
end
|
|
77
73
|
|
|
78
74
|
it "should parse a Rails 2.2 style :completed line correctly when AR is disabled" do
|
|
79
75
|
line = prefix + 'Completed in 597ms (View: 298 | 200 OK [http://shapado.com]'
|
|
80
|
-
|
|
76
|
+
subject.should parse_line(line).as(:completed).and_capture(:duration => 0.597, :db => nil, :view => 0.298, :status => 200, :url => 'http://shapado.com')
|
|
81
77
|
end
|
|
82
78
|
|
|
83
79
|
it "should parse a Rails 2.2 style :completed line without view" do
|
|
84
80
|
line = prefix + "Completed in 148ms (DB: 0) | 302 Found [http://iwp-sod.hargray.org/login]"
|
|
85
|
-
|
|
81
|
+
subject.should parse_line(line).as(:completed).and_capture(:duration => 0.148, :db => 0.0, :view => nil, :status => 302, :url => 'http://iwp-sod.hargray.org/login')
|
|
86
82
|
end
|
|
87
83
|
|
|
88
84
|
it "should parse a :failure line with exception correctly" do
|
|
89
85
|
line = prefix + "NoMethodError (undefined method `update_domain_account' for nil:NilClass):"
|
|
90
|
-
|
|
86
|
+
subject.should parse_line(line).as(:failure).and_capture(:error => 'NoMethodError', :message => "undefined method `update_domain_account' for nil:NilClass")
|
|
91
87
|
end
|
|
92
88
|
|
|
93
89
|
it "should parse a :cache_hit line correctly with an filter instance reference" do
|
|
94
90
|
line = prefix + 'Filter chain halted as [#<ActionController::Filters::AroundFilter:0x2a999ad120 @identifier=nil, @kind=:filter, @options={:only=>#<Set: {"cached"}>, :if=>:not_logged_in?, :unless=>nil}, @method=#<ActionController::Caching::Actions::ActionCacheFilter:0x2a999ad620 @check=nil, @options={:store_options=>{}, :layout=>nil, :cache_path=>#<Proc:0x0000002a999b8890@/app/controllers/cached_controller.rb:8>}>>] did_not_yield.'
|
|
95
|
-
|
|
91
|
+
subject.should parse_line(line).as(:cache_hit)
|
|
96
92
|
end
|
|
97
93
|
|
|
98
94
|
it "should parse a :cache_hit line correctly with an proc instance reference" do
|
|
99
95
|
line = prefix + 'Filter chain halted as [#<ActionController::Filters::AroundFilter:0x2a9a923e38 @method=#<Proc:0x0000002a9818b3f8@/usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/caching/actions.rb:64>, @kind=:filter, @identifier=nil, @options={:unless=>nil, :if=>nil, :only=>#<Set: {"show"}>}>] did_not_yield.'
|
|
100
|
-
|
|
96
|
+
subject.should parse_line(line).as(:cache_hit)
|
|
101
97
|
end
|
|
102
98
|
|
|
103
99
|
|
|
104
100
|
it "should parse a :parameters line correctly" do
|
|
105
101
|
line = prefix + ' Parameters: {"action"=>"cached", "controller"=>"cached"}'
|
|
106
|
-
|
|
102
|
+
subject.should parse_line(line).as(:parameters).and_capture(:params => {:action => 'cached', :controller => 'cached'})
|
|
107
103
|
end
|
|
108
104
|
|
|
109
105
|
it "should parse a :rendered line correctly" do
|
|
110
106
|
line = prefix + 'Rendered layouts/_footer (2.9ms)'
|
|
111
|
-
|
|
107
|
+
subject.should parse_line(line).as(:rendered).and_capture(:render_file => 'layouts/_footer', :render_duration => 0.0029)
|
|
112
108
|
end
|
|
113
109
|
|
|
114
110
|
it "should parse a :query_executed line with colors" do
|
|
115
111
|
line = prefix + ' [4;36;1mUser Load (0.4ms)[0m [0;1mSELECT * FROM `users` WHERE (`users`.`id` = 18205844) [0m'
|
|
116
|
-
|
|
112
|
+
subject.should parse_line(line).as(:query_executed).and_capture(:query_class => 'User', :query_duration => 0.0004, :query_sql => 'SELECT * FROM users WHERE (users.id = :int)')
|
|
117
113
|
end
|
|
118
114
|
|
|
119
115
|
it "should parse a :query_executed line without colors" do
|
|
120
116
|
line = prefix + ' User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) '
|
|
121
|
-
|
|
117
|
+
subject.should parse_line(line).as(:query_executed).and_capture(:query_class => 'User', :query_duration => 0.0004, :query_sql => 'SELECT * FROM users WHERE (users.id = :int)')
|
|
122
118
|
end
|
|
123
119
|
|
|
124
120
|
it "should parse a :query_cached line with colors" do
|
|
125
121
|
line = prefix + ' [4;35;1mCACHE (0.0ms)[0m [0mSELECT * FROM `users` WHERE (`users`.`id` = 0) [0m'
|
|
126
|
-
|
|
122
|
+
subject.should parse_line(line).as(:query_cached).and_capture(:cached_duration => 0.0, :cached_sql => 'SELECT * FROM users WHERE (users.id = :int)')
|
|
127
123
|
end
|
|
128
124
|
|
|
129
125
|
it "should parse a :query_cached line without colors" do
|
|
130
126
|
line = prefix + ' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) '
|
|
131
|
-
|
|
127
|
+
subject.should parse_line(line).as(:query_cached).and_capture(:cached_duration => 0.0, :cached_sql => 'SELECT * FROM users WHERE (users.id = :int)')
|
|
132
128
|
end
|
|
133
129
|
|
|
134
130
|
it "should not parse an unsupported line" do
|
|
135
131
|
line = prefix + 'nonsense line that should not be parsed as anything'
|
|
136
|
-
|
|
132
|
+
subject.should_not parse_line(line)
|
|
137
133
|
end
|
|
138
134
|
end
|
|
139
135
|
end
|
|
140
136
|
end
|
|
141
137
|
|
|
142
138
|
describe '#parse_io' do
|
|
143
|
-
|
|
144
|
-
@log_parser = RequestLogAnalyzer::Source::LogParser.new(
|
|
145
|
-
RequestLogAnalyzer::FileFormat.load(:rails), :parse_strategy => 'cautious')
|
|
146
|
-
end
|
|
139
|
+
let(:log_parser) { RequestLogAnalyzer::Source::LogParser.new(subject, :parse_strategy => 'cautious') }
|
|
147
140
|
|
|
148
141
|
it "should parse a Rails 2.1 style log and find valid Rails requests without warnings" do
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
@log_parser.parse_file(log_fixture(:rails_1x)) do |request|
|
|
153
|
-
request_counter.hit! if request.kind_of?(RequestLogAnalyzer::FileFormat::Rails::Request) && request.completed?
|
|
154
|
-
end
|
|
142
|
+
log_parser.should_receive(:handle_request).exactly(4).times
|
|
143
|
+
log_parser.should_not_receive(:warn)
|
|
144
|
+
log_parser.parse_file(log_fixture(:rails_1x))\
|
|
155
145
|
end
|
|
156
146
|
|
|
157
147
|
it "should parse a Rails 2.2 style log and find valid Rails requests without warnings" do
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@log_parser.parse_file(log_fixture(:rails_22)) do |request|
|
|
162
|
-
request_counter.hit! if request.kind_of?(RequestLogAnalyzer::FileFormat::Rails::Request) && request.completed?
|
|
163
|
-
end
|
|
148
|
+
log_parser.should_receive(:handle_request).once
|
|
149
|
+
log_parser.should_not_receive(:warn)
|
|
150
|
+
log_parser.parse_file(log_fixture(:rails_22))
|
|
164
151
|
end
|
|
165
152
|
|
|
166
153
|
it "should parse a Rails SyslogLogger file with prefix and find valid requests without warnings" do
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
@log_parser.parse_file(log_fixture(:syslog_1x)) do |request|
|
|
171
|
-
request_counter.hit! if request.kind_of?(RequestLogAnalyzer::FileFormat::Rails::Request) && request.completed?
|
|
172
|
-
end
|
|
154
|
+
log_parser.should_receive(:handle_request).once
|
|
155
|
+
log_parser.should_not_receive(:warn)
|
|
156
|
+
log_parser.parse_file(log_fixture(:syslog_1x))
|
|
173
157
|
end
|
|
174
158
|
|
|
175
159
|
it "should parse cached requests" do
|
|
176
|
-
|
|
177
|
-
|
|
160
|
+
log_parser.should_not_receive(:warn)
|
|
161
|
+
log_parser.parse_file(log_fixture(:rails_22_cached)) do |request|
|
|
178
162
|
request.should be_completed
|
|
179
163
|
request =~ :cache_hit
|
|
180
164
|
end
|
|
181
165
|
end
|
|
182
166
|
|
|
183
167
|
it "should detect unordered requests in the logs" do
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
168
|
+
log_parser.should_not_receive(:handle_request)
|
|
169
|
+
log_parser.should_receive(:warn).with(:unclosed_request, anything).once
|
|
170
|
+
log_parser.should_receive(:warn).with(:no_current_request, anything).at_least(:twice)
|
|
171
|
+
log_parser.parse_file(log_fixture(:rails_unordered))
|
|
188
172
|
end
|
|
189
173
|
end
|
|
190
174
|
end
|
|
@@ -2,45 +2,46 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe RequestLogAnalyzer::FileFormat::W3c do
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
subject { RequestLogAnalyzer::FileFormat.load(:w3c) }
|
|
6
|
+
|
|
7
|
+
it { should be_well_formed }
|
|
8
|
+
it { should have_line_definition(:access).capturing(:timestamp, :remote_ip, :username, :local_ip, :port,
|
|
9
|
+
:method, :path, :http_status, :bytes_sent, :bytes_received, :duration, :user_agent, :referer) }
|
|
10
|
+
|
|
11
|
+
it { should have(10).report_trackers }
|
|
12
|
+
|
|
13
|
+
let(:sample1) { '2002-05-24 20:18:01 172.224.24.114 - 206.73.118.24 80 GET /Default.htm - 200 7930 248 31 Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+2000+Server) http://64.224.24.114/' }
|
|
14
|
+
let(:irrelevant) { '#Software: Microsoft Internet Information Services 6.0' }
|
|
15
|
+
|
|
16
|
+
describe '#parse_line' do
|
|
17
|
+
it { should parse_line(sample1, 'an access line').and_capture(
|
|
18
|
+
:timestamp => 20020524201801,
|
|
19
|
+
:remote_ip => "172.224.24.114",
|
|
20
|
+
:username => nil,
|
|
21
|
+
:local_ip => "206.73.118.24",
|
|
22
|
+
:port => 80,
|
|
23
|
+
:method => 'GET',
|
|
24
|
+
:path => '/Default.htm',
|
|
25
|
+
:http_status => 200,
|
|
26
|
+
:bytes_sent => 7930,
|
|
27
|
+
:bytes_received => 248,
|
|
28
|
+
:duration => 0.031,
|
|
29
|
+
:user_agent => 'Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+2000+Server)',
|
|
30
|
+
:referer => 'http://64.224.24.114/')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
it { should_not parse_line(irrelevant, 'an irrelevant line') }
|
|
34
|
+
it { should_not parse_line('nonsense', 'a nonsense line') }
|
|
10
35
|
end
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
it "should match the sample line" do
|
|
21
|
-
@file_format.parse_line(@sample).should include(:line_definition, :captures)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
it "should not match a nonsense line" do
|
|
25
|
-
@file_format.parse_line('#Software: Microsoft Internet Information Services 6.0').should be_nil
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
it "should parse and convert the sample fields correctly" do
|
|
29
|
-
@log_parser.parse_io(StringIO.new(@sample)) do |request|
|
|
30
|
-
request[:timestamp].should == 20020524201801
|
|
31
|
-
request[:remote_ip].should == "172.224.24.114"
|
|
32
|
-
request[:username].should == nil
|
|
33
|
-
request[:local_ip].should == "206.73.118.24"
|
|
34
|
-
request[:port].should == 80
|
|
35
|
-
request[:method].should == 'GET'
|
|
36
|
-
request[:path].should == '/Default.htm'
|
|
37
|
-
request[:http_status].should == 200
|
|
38
|
-
request[:bytes_sent].should == 7930
|
|
39
|
-
request[:bytes_received].should == 248
|
|
40
|
-
request[:duration].should == 0.031
|
|
41
|
-
request[:user_agent].should == 'Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+2000+Server)'
|
|
42
|
-
request[:referer].should == 'http://64.224.24.114/'
|
|
36
|
+
|
|
37
|
+
describe '#parse_io' do
|
|
38
|
+
let(:log_parser) { RequestLogAnalyzer::Source::LogParser.new(subject) }
|
|
39
|
+
let(:snippet) { log_snippet(irrelevant, sample1, sample1) }
|
|
40
|
+
|
|
41
|
+
it "should parse a snippet successully without warnings" do
|
|
42
|
+
log_parser.should_receive(:handle_request).twice
|
|
43
|
+
log_parser.should_not_receive(:warn)
|
|
44
|
+
log_parser.parse_io(snippet)
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
|
-
|
|
46
|
-
end
|
|
47
|
+
end
|
|
@@ -11,7 +11,7 @@ describe RequestLogAnalyzer::Source::LogParser, :requests do
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
it "should have a valid language" do
|
|
14
|
-
@log_parser.file_format.should
|
|
14
|
+
@log_parser.file_format.should be_well_formed
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
it "should set the :source for every parsed line" do
|
metadata
CHANGED
|
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
|
4
4
|
prerelease: false
|
|
5
5
|
segments:
|
|
6
6
|
- 1
|
|
7
|
-
-
|
|
7
|
+
- 11
|
|
8
8
|
- 0
|
|
9
|
-
version: 1.
|
|
9
|
+
version: 1.11.0
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Willem van Bergen
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-
|
|
18
|
+
date: 2011-04-27 00:00:00 -04:00
|
|
19
19
|
default_executable: request-log-analyzer
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -83,6 +83,7 @@ extra_rdoc_files:
|
|
|
83
83
|
- README.rdoc
|
|
84
84
|
files:
|
|
85
85
|
- .gitignore
|
|
86
|
+
- .infinity_test
|
|
86
87
|
- DESIGN.rdoc
|
|
87
88
|
- Gemfile
|
|
88
89
|
- LICENSE
|
|
@@ -94,7 +95,6 @@ files:
|
|
|
94
95
|
- lib/cli/database_console_init.rb
|
|
95
96
|
- lib/cli/progressbar.rb
|
|
96
97
|
- lib/cli/tools.rb
|
|
97
|
-
- lib/mixins/gets_memory_protection.rb
|
|
98
98
|
- lib/request_log_analyzer.rb
|
|
99
99
|
- lib/request_log_analyzer/aggregator.rb
|
|
100
100
|
- lib/request_log_analyzer/aggregator/database_inserter.rb
|
|
@@ -131,12 +131,10 @@ files:
|
|
|
131
131
|
- lib/request_log_analyzer/log_processor.rb
|
|
132
132
|
- lib/request_log_analyzer/mailer.rb
|
|
133
133
|
- lib/request_log_analyzer/output.rb
|
|
134
|
-
- lib/request_log_analyzer/output/fancy_html.rb
|
|
135
134
|
- lib/request_log_analyzer/output/fixed_width.rb
|
|
136
135
|
- lib/request_log_analyzer/output/html.rb
|
|
137
136
|
- lib/request_log_analyzer/request.rb
|
|
138
137
|
- lib/request_log_analyzer/source.rb
|
|
139
|
-
- lib/request_log_analyzer/source/database_loader.rb
|
|
140
138
|
- lib/request_log_analyzer/source/log_parser.rb
|
|
141
139
|
- lib/request_log_analyzer/tracker.rb
|
|
142
140
|
- lib/request_log_analyzer/tracker/duration.rb
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
class File
|
|
2
|
-
alias_method :gets_original, :gets
|
|
3
|
-
# The size of the reads we will use to add to the line buffer.
|
|
4
|
-
MAX_READ_SIZE=1024*100
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
# This method returns the next line of the File.
|
|
8
|
-
#
|
|
9
|
-
# It works by moving the file pointer forward +MAX_READ_SIZE+ at a time,
|
|
10
|
-
# storing seen lines in <tt>@line_buffer</tt>. Once the buffer contains at
|
|
11
|
-
# least two lines (ensuring we have seen on full line) or the file pointer
|
|
12
|
-
# reaches the end of the File, the last line from the buffer is returned.
|
|
13
|
-
# When the buffer is exhausted, this will throw +nil+ (from the empty Array).
|
|
14
|
-
#
|
|
15
|
-
# Read portions of the file that do not contain the +sep_string+ are not added to
|
|
16
|
-
# the buffer. This prevents <tt>@line_buffer<tt> from growing signficantly when parsing
|
|
17
|
-
# large lines.
|
|
18
|
-
#
|
|
19
|
-
def gets(sep_string = $/)
|
|
20
|
-
@read_size ||= MAX_READ_SIZE
|
|
21
|
-
# A buffer to hold lines read, but not yet returned.
|
|
22
|
-
@line_buffer ||= Array.new
|
|
23
|
-
|
|
24
|
-
# Record where we are.
|
|
25
|
-
@current_pos ||= pos
|
|
26
|
-
|
|
27
|
-
# Last Position in the file
|
|
28
|
-
@last_pos ||= nil
|
|
29
|
-
if @last_pos.nil?
|
|
30
|
-
seek(0, IO::SEEK_END)
|
|
31
|
-
@last_pos = pos
|
|
32
|
-
seek(@current_pos, IO::SEEK_SET)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
# If we have more than one line in the buffer or we have reached the
|
|
37
|
-
# beginning of the file, send the last line in the buffer to the caller.
|
|
38
|
-
# (This may be +nil+, if the buffer has been exhausted.)
|
|
39
|
-
#
|
|
40
|
-
if @line_buffer.size > 2 or @current_pos >= @last_pos
|
|
41
|
-
self.lineno += 1
|
|
42
|
-
return @line_buffer.shift
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
sep =
|
|
46
|
-
|
|
47
|
-
chunk = String.new
|
|
48
|
-
while chunk and chunk !~ /#{sep_string}/
|
|
49
|
-
chunk = read(@read_size)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Appends new lines to the last element of the buffer
|
|
53
|
-
line_buffer_pos = @line_buffer.any? ? @line_buffer.size-1 : 0
|
|
54
|
-
|
|
55
|
-
if chunk
|
|
56
|
-
@line_buffer[line_buffer_pos] = @line_buffer[line_buffer_pos].to_s<< chunk
|
|
57
|
-
else
|
|
58
|
-
# at the end
|
|
59
|
-
return @line_buffer.shift
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
# Divide the last line of the buffer based on +sep_string+ and #flatten!
|
|
64
|
-
# those new lines into the buffer.
|
|
65
|
-
#
|
|
66
|
-
@line_buffer[line_buffer_pos] = @line_buffer[line_buffer_pos].scan(/.*?#{Regexp.escape(sep_string)}|.+/)
|
|
67
|
-
@line_buffer.flatten!
|
|
68
|
-
|
|
69
|
-
#
|
|
70
|
-
# If we made it this far, we need to read more data to try and find the
|
|
71
|
-
# end of a line or the end of the file. Move the file pointer
|
|
72
|
-
# forward a step, to give us new bytes to read.
|
|
73
|
-
#
|
|
74
|
-
@current_pos += @read_size
|
|
75
|
-
seek(@current_pos, IO::SEEK_SET)
|
|
76
|
-
|
|
77
|
-
# We have more data now, so try again to read a line...
|
|
78
|
-
gets(sep_string)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
begin
|
|
2
|
-
require 'rubygems'
|
|
3
|
-
require 'gchart'
|
|
4
|
-
rescue LoadError
|
|
5
|
-
$stderr.puts "The FancyHTML output format requires the googlechart gem:"
|
|
6
|
-
$stderr.puts " (sudo) gem install googlecharts"
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
module RequestLogAnalyzer::Output
|
|
10
|
-
|
|
11
|
-
class FancyHTML < HTML
|
|
12
|
-
|
|
13
|
-
def report_tracker(tracker)
|
|
14
|
-
case tracker
|
|
15
|
-
when RequestLogAnalyzer::Tracker::HourlySpread then report_hourly_spread(tracker)
|
|
16
|
-
else tracker.report(self)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def report_hourly_spread(tracker)
|
|
21
|
-
title tracker.title
|
|
22
|
-
puts tag(:img, nil, :width => '700', :height => '120', :src =>
|
|
23
|
-
Gchart.sparkline(:data => tracker.hour_frequencies, :size => '700x120', :line_colors => '0077CC',
|
|
24
|
-
:axis_with_labels => 'x,y', :axis_labels => [x_axis_labels(tracker),y_axis_labels(tracker)]))
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def x_axis_labels(tracker)
|
|
28
|
-
frmt = '%H:%M'
|
|
29
|
-
x_axis_labels = []
|
|
30
|
-
num_labels = 5
|
|
31
|
-
start = tracker.first_timestamp
|
|
32
|
-
step = tracker.timespan / (num_labels - 1)
|
|
33
|
-
for i in 0...num_labels do
|
|
34
|
-
x_axis_labels << (start + step * i).strftime(frmt)
|
|
35
|
-
end
|
|
36
|
-
x_axis_labels
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def y_axis_labels(tracker)
|
|
40
|
-
sorted_frequencies = tracker.hour_frequencies.sort
|
|
41
|
-
[sorted_frequencies.first, sorted_frequencies.last]
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'activerecord'
|
|
3
|
-
|
|
4
|
-
module RequestLogAnalyzer::Source
|
|
5
|
-
|
|
6
|
-
# Active Resource hook
|
|
7
|
-
class Request < ActiveRecord::Base
|
|
8
|
-
has_many :completed_lines
|
|
9
|
-
has_many :processing_lines
|
|
10
|
-
def convert(file_format)
|
|
11
|
-
send_attributes = self.attributes
|
|
12
|
-
send_attributes.merge!(self.completed_lines.first.attributes) if self.completed_lines.first
|
|
13
|
-
send_attributes.merge!(self.processing_lines.first.attributes) if self.processing_lines.first
|
|
14
|
-
return RequestLogAnalyzer::Request.new(file_format, send_attributes)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class CompletedLine < ActiveRecord::Base
|
|
19
|
-
belongs_to :request
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
class ProcessingLine < ActiveRecord::Base
|
|
23
|
-
belongs_to :request
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# The Database class gets log data from the database.
|
|
27
|
-
class DatabaseLoader < Base
|
|
28
|
-
|
|
29
|
-
attr_reader :source_files, :file_format, :requests
|
|
30
|
-
|
|
31
|
-
# Initializes the log file parser instance.
|
|
32
|
-
# It will apply the language specific FileFormat module to this instance. It will use the line
|
|
33
|
-
# definitions in this module to parse any input that it is given (see parse_io).
|
|
34
|
-
#
|
|
35
|
-
# <tt>format</tt>:: The current file format instance
|
|
36
|
-
# <tt>options</tt>:: A hash of options that are used by the parser
|
|
37
|
-
def initialize(format, options = {})
|
|
38
|
-
super(format, options)
|
|
39
|
-
@source_files = options[:source_files]
|
|
40
|
-
@parsed_requests = 0
|
|
41
|
-
@requests = []
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Reads the input, which can either be a file, sequence of files or STDIN to parse
|
|
45
|
-
# lines specified in the FileFormat. This lines will be combined into Request instances,
|
|
46
|
-
# that will be yielded. The actual parsing occurs in the parse_io method.
|
|
47
|
-
# <tt>options</tt>:: A Hash of options that will be pased to parse_io.
|
|
48
|
-
def each_request(options = {}, &block) # :yields: request
|
|
49
|
-
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => @source_files)
|
|
50
|
-
|
|
51
|
-
@progress_handler.call(:started, @source_files) if @progress_handler
|
|
52
|
-
RequestLogAnalyzer::Source::Request.find(:all).each do |request|
|
|
53
|
-
@parsed_requests += 1
|
|
54
|
-
@progress_handler.call(:progress, @parsed_requests) if @progress_handler
|
|
55
|
-
|
|
56
|
-
yield request.convert(self.file_format)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
@progress_handler.call(:finished, @source_files) if @progress_handler
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Add a block to this method to install a progress handler while parsing.
|
|
63
|
-
# <tt>proc</tt>:: The proc that will be called to handle progress update messages
|
|
64
|
-
def progress=(proc)
|
|
65
|
-
@progress_handler = proc
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Add a block to this method to install a warning handler while parsing,
|
|
69
|
-
# <tt>proc</tt>:: The proc that will be called to handle parse warning messages
|
|
70
|
-
def warning=(proc)
|
|
71
|
-
@warning_handler = proc
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# This method is called by the parser if it encounteres any parsing problems.
|
|
75
|
-
# It will call the installed warning handler if any.
|
|
76
|
-
#
|
|
77
|
-
# By default, RequestLogAnalyzer::Controller will install a warning handler
|
|
78
|
-
# that will pass the warnings to each aggregator so they can do something useful
|
|
79
|
-
# with it.
|
|
80
|
-
#
|
|
81
|
-
# <tt>type</tt>:: The warning type (a Symbol)
|
|
82
|
-
# <tt>message</tt>:: A message explaining the warning
|
|
83
|
-
def warn(type, message)
|
|
84
|
-
@warning_handler.call(type, message, @current_io.lineno) if @warning_handler
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|