ngmoco-request-log-analyzer 1.4.2
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 +10 -0
- data/DESIGN.rdoc +41 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +8 -0
- data/bin/request-log-analyzer +114 -0
- data/lib/cli/command_line_arguments.rb +301 -0
- data/lib/cli/database_console.rb +26 -0
- data/lib/cli/database_console_init.rb +43 -0
- data/lib/cli/progressbar.rb +213 -0
- data/lib/cli/tools.rb +46 -0
- data/lib/request_log_analyzer.rb +44 -0
- data/lib/request_log_analyzer/aggregator.rb +49 -0
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
- data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
- data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
- data/lib/request_log_analyzer/controller.rb +332 -0
- data/lib/request_log_analyzer/database.rb +102 -0
- data/lib/request_log_analyzer/database/base.rb +115 -0
- data/lib/request_log_analyzer/database/connection.rb +38 -0
- data/lib/request_log_analyzer/database/request.rb +22 -0
- data/lib/request_log_analyzer/database/source.rb +13 -0
- data/lib/request_log_analyzer/database/warning.rb +14 -0
- data/lib/request_log_analyzer/file_format.rb +160 -0
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
- data/lib/request_log_analyzer/file_format/apache.rb +141 -0
- data/lib/request_log_analyzer/file_format/merb.rb +67 -0
- data/lib/request_log_analyzer/file_format/rack.rb +11 -0
- data/lib/request_log_analyzer/file_format/rails.rb +176 -0
- data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
- data/lib/request_log_analyzer/filter.rb +30 -0
- data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
- data/lib/request_log_analyzer/filter/field.rb +42 -0
- data/lib/request_log_analyzer/filter/timespan.rb +45 -0
- data/lib/request_log_analyzer/line_definition.rb +111 -0
- data/lib/request_log_analyzer/log_processor.rb +99 -0
- data/lib/request_log_analyzer/mailer.rb +62 -0
- data/lib/request_log_analyzer/output.rb +113 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
- data/lib/request_log_analyzer/output/html.rb +184 -0
- data/lib/request_log_analyzer/request.rb +175 -0
- data/lib/request_log_analyzer/source.rb +72 -0
- data/lib/request_log_analyzer/source/database_loader.rb +87 -0
- data/lib/request_log_analyzer/source/log_parser.rb +274 -0
- data/lib/request_log_analyzer/tracker.rb +206 -0
- data/lib/request_log_analyzer/tracker/duration.rb +104 -0
- data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
- data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
- data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
- data/request-log-analyzer.gemspec +40 -0
- data/spec/database.yml +23 -0
- data/spec/fixtures/apache_combined.log +5 -0
- data/spec/fixtures/apache_common.log +10 -0
- data/spec/fixtures/decompression.log +12 -0
- data/spec/fixtures/decompression.log.bz2 +0 -0
- data/spec/fixtures/decompression.log.gz +0 -0
- data/spec/fixtures/decompression.log.zip +0 -0
- data/spec/fixtures/decompression.tar.gz +0 -0
- data/spec/fixtures/decompression.tgz +0 -0
- data/spec/fixtures/header_and_footer.log +6 -0
- data/spec/fixtures/merb.log +84 -0
- data/spec/fixtures/merb_prefixed.log +9 -0
- data/spec/fixtures/multiple_files_1.log +5 -0
- data/spec/fixtures/multiple_files_2.log +2 -0
- data/spec/fixtures/rails.db +0 -0
- data/spec/fixtures/rails_1x.log +59 -0
- data/spec/fixtures/rails_22.log +12 -0
- data/spec/fixtures/rails_22_cached.log +10 -0
- data/spec/fixtures/rails_unordered.log +24 -0
- data/spec/fixtures/syslog_1x.log +5 -0
- data/spec/fixtures/test_file_format.log +13 -0
- data/spec/fixtures/test_language_combined.log +14 -0
- data/spec/fixtures/test_order.log +16 -0
- data/spec/integration/command_line_usage_spec.rb +84 -0
- data/spec/integration/munin_plugins_rails_spec.rb +58 -0
- data/spec/integration/scout_spec.rb +151 -0
- data/spec/lib/helpers.rb +52 -0
- data/spec/lib/macros.rb +18 -0
- data/spec/lib/matchers.rb +77 -0
- data/spec/lib/mocks.rb +76 -0
- data/spec/lib/testing_format.rb +46 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
- data/spec/unit/aggregator/summarizer_spec.rb +26 -0
- data/spec/unit/controller/controller_spec.rb +41 -0
- data/spec/unit/controller/log_processor_spec.rb +18 -0
- data/spec/unit/database/base_class_spec.rb +183 -0
- data/spec/unit/database/connection_spec.rb +34 -0
- data/spec/unit/database/database_spec.rb +133 -0
- data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
- data/spec/unit/file_format/apache_format_spec.rb +203 -0
- data/spec/unit/file_format/file_format_api_spec.rb +69 -0
- data/spec/unit/file_format/line_definition_spec.rb +75 -0
- data/spec/unit/file_format/merb_format_spec.rb +52 -0
- data/spec/unit/file_format/rails_format_spec.rb +164 -0
- data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
- data/spec/unit/filter/field_filter_spec.rb +66 -0
- data/spec/unit/filter/filter_spec.rb +17 -0
- data/spec/unit/filter/timespan_filter_spec.rb +58 -0
- data/spec/unit/mailer_spec.rb +30 -0
- data/spec/unit/request_spec.rb +111 -0
- data/spec/unit/source/log_parser_spec.rb +119 -0
- data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
- data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
- data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
- data/spec/unit/tracker/tracker_api_spec.rb +124 -0
- data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
- data/tasks/github-gem.rake +323 -0
- data/tasks/request_log_analyzer.rake +26 -0
- metadata +220 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::Database::Connection do
|
|
4
|
+
describe '.from_string' do
|
|
5
|
+
|
|
6
|
+
it "should parse a name-value based string" do
|
|
7
|
+
string = 'adapter=sqlite3;database=filename.db'
|
|
8
|
+
RequestLogAnalyzer::Database::Connection.from_string(string).should == {:adapter => 'sqlite3', :database => 'filename.db'}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should parse an URI-based string for SQLite3" do
|
|
12
|
+
string = 'sqlite3://filename.db'
|
|
13
|
+
RequestLogAnalyzer::Database::Connection.from_string(string).should == {:adapter => 'sqlite3', :database => 'filename.db'}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should parse an URI-based string for MySQL" do
|
|
17
|
+
string = 'mysql://localhost.local/database'
|
|
18
|
+
RequestLogAnalyzer::Database::Connection.from_string(string).should ==
|
|
19
|
+
{ :adapter => 'mysql', :database => 'database', :host => 'localhost.local' }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "should parse an URI-based string for MySQL with only username" do
|
|
23
|
+
string = 'mysql://username@localhost.local/database'
|
|
24
|
+
RequestLogAnalyzer::Database::Connection.from_string(string).should ==
|
|
25
|
+
{ :adapter => 'mysql', :database => 'database', :host => 'localhost.local', :username => 'username' }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should parse an URI-based string for MySQL with username and password" do
|
|
29
|
+
string = 'mysql://username:password@localhost.local/database'
|
|
30
|
+
RequestLogAnalyzer::Database::Connection.from_string(string).should ==
|
|
31
|
+
{ :adapter => 'mysql', :database => 'database', :host => 'localhost.local', :username => 'username', :password => 'password' }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::Database do
|
|
4
|
+
|
|
5
|
+
describe '#load_database_schema!' do
|
|
6
|
+
|
|
7
|
+
context 'for a Rails request database' do
|
|
8
|
+
before(:each) do
|
|
9
|
+
@database = RequestLogAnalyzer::Database.new(log_fixture(:rails, :db))
|
|
10
|
+
@database.load_database_schema!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after(:each) { @database.remove_orm_classes! }
|
|
14
|
+
|
|
15
|
+
# FileFormat-agnostic classes
|
|
16
|
+
default_orm_class_names.each do |const|
|
|
17
|
+
it "should create the default #{const} constant" do
|
|
18
|
+
RequestLogAnalyzer::Database.const_defined?(const).should be_true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should create the default #{const} class inheriting from ActiveRecord::Base and RequestLogAnalyzer::Database::Base" do
|
|
22
|
+
RequestLogAnalyzer::Database.const_get(const).ancestors.should include(ActiveRecord::Base, RequestLogAnalyzer::Database::Base)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Some Fileformat-specific classes
|
|
27
|
+
['CompletedLine', 'ProcessingLine'].each do |const|
|
|
28
|
+
it "should create the #{const} constant" do
|
|
29
|
+
Object.const_defined?(const).should be_true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should create the #{const} class inheriting from ActiveRecord::Base and RequestLogAnalyzer::Database::Base" do
|
|
33
|
+
Object.const_get(const).ancestors.should include(ActiveRecord::Base, RequestLogAnalyzer::Database::Base)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should create a :belongs_to relation from the #{const} class to Request and Source" do
|
|
37
|
+
Object.const_get(const).send(:reflections).should include(:request, :source)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should create a :has_many relation from the Request and Source class to the #{const} class" do
|
|
41
|
+
RequestLogAnalyzer::Database::Request.send(:reflections).should include(const.underscore.pluralize.to_sym)
|
|
42
|
+
RequestLogAnalyzer::Database::Source.send(:reflections).should include(const.underscore.pluralize.to_sym)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#create_database_schema!' do
|
|
49
|
+
|
|
50
|
+
before(:each) do
|
|
51
|
+
@database = RequestLogAnalyzer::Database.new
|
|
52
|
+
@database.file_format = testing_format
|
|
53
|
+
@database.stub!(:connection).and_return(mock_connection)
|
|
54
|
+
|
|
55
|
+
# Stub the expected method calls for the preparation, these will be tested separately
|
|
56
|
+
@mock_class = Class.new(RequestLogAnalyzer::Database::Base)
|
|
57
|
+
@mock_class.stub!(:create_table!)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
after(:each) { @database.remove_orm_classes! }
|
|
61
|
+
|
|
62
|
+
default_orm_class_names.each do |klass|
|
|
63
|
+
|
|
64
|
+
it "should create a table for the default #{klass} class" do
|
|
65
|
+
@database.connection.should_receive(:create_table).with(klass.underscore.pluralize.to_sym)
|
|
66
|
+
@database.send :create_database_schema!
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "should create a #{klass} class inheriting from ActiveRecord and the base class of the ORM module" do
|
|
70
|
+
@database.send :create_database_schema!
|
|
71
|
+
RequestLogAnalyzer::Database.const_get(klass).ancestors.should include(ActiveRecord::Base, RequestLogAnalyzer::Database::Base)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
testing_format.line_definitions.each do |name, definition|
|
|
76
|
+
|
|
77
|
+
it "should create the #{(name.to_s + '_line').camelize} class for #{name.inspect} lines" do
|
|
78
|
+
@database.send :create_database_schema!
|
|
79
|
+
Object.const_defined?("#{name}_line".camelize).should be_true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should create the #{name.to_s + '_lines'} table for the parsed #{name.inspect} lines" do
|
|
83
|
+
@database.connection.should_receive(:create_table).with("#{name}_lines".to_sym)
|
|
84
|
+
@database.send :create_database_schema!
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe '#load_activerecord_class' do
|
|
90
|
+
|
|
91
|
+
before(:each) do
|
|
92
|
+
@database = RequestLogAnalyzer::Database.new
|
|
93
|
+
@connection = mock_connection
|
|
94
|
+
@database.stub!(:connection).and_return(@connection)
|
|
95
|
+
|
|
96
|
+
# Mock the has_many method of the defaukt ORM classes
|
|
97
|
+
RequestLogAnalyzer::Database::Request.stub!(:has_many)
|
|
98
|
+
RequestLogAnalyzer::Database::Source.stub!(:has_many)
|
|
99
|
+
|
|
100
|
+
@mock_class = Class.new(RequestLogAnalyzer::Database::Base)
|
|
101
|
+
|
|
102
|
+
RequestLogAnalyzer::Database::Base.stub!(:subclass_from_table).and_return(@mock_class)
|
|
103
|
+
RequestLogAnalyzer::Database::Base.stub!(:subclass_from_line_definition).and_return(@mock_class)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
after(:each) { @database.remove_orm_classes! }
|
|
107
|
+
|
|
108
|
+
it "should call :subclass_from_table when a table name is given as string" do
|
|
109
|
+
RequestLogAnalyzer::Database::Base.should_receive(:subclass_from_table).and_return(@mock_class)
|
|
110
|
+
@database.load_activerecord_class('test_lines')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "should call :subclass_from_table when a table name is given as symbol" do
|
|
114
|
+
RequestLogAnalyzer::Database::Base.should_receive(:subclass_from_table).and_return(@mock_class)
|
|
115
|
+
@database.load_activerecord_class(:test_lines)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "should call :subclass_from_table when a LineDefinition is given" do
|
|
119
|
+
RequestLogAnalyzer::Database::Base.should_receive(:subclass_from_line_definition).and_return(@mock_class)
|
|
120
|
+
@database.load_activerecord_class(RequestLogAnalyzer::LineDefinition.new(:test))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "should define the class in the ORM module" do
|
|
124
|
+
@database.load_activerecord_class(:test_lines)
|
|
125
|
+
Object.const_defined?('TestLine').should be_true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "should add the class to the line_classes array of the database" do
|
|
129
|
+
@database.load_activerecord_class(:test_lines)
|
|
130
|
+
@database.line_classes.should include(TestLine)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::FileFormat::AmazonS3 do
|
|
4
|
+
|
|
5
|
+
before(:each) do
|
|
6
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(:amazon_s3)
|
|
7
|
+
@log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
|
|
8
|
+
@sample = '2f88111968424e6306bf4d292c0188ccb94ff9374ea2836b50a1a79f7cd656e1 sample-bucket [06/Oct/2006:01:42:14 +0000] 207.171.172.6 65a011a29cdf8ec533ec3d1ccaae921c C980091AD89C936A REST.GET.OBJECT object.png "GET /sample-bucket/object.png HTTP/1.1" 200 - 1243 1243 988 987 "-" "aranhabot"'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should be a valid file format" do
|
|
12
|
+
@file_format.should be_valid
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should parse access lines and capture all of its fields" do
|
|
16
|
+
@file_format.should have_line_definition(:access).capturing(:bucket_owner, :bucket, :timestamp, :remote_ip, :requester,
|
|
17
|
+
:key, :operation, :total_time, :turnaround_time, :bytes_sent, :object_size, :referer, :user_agent)
|
|
18
|
+
end
|
|
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('dsadasdas dsaadsads dsaadsads').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[:bucket_owner].should == '2f88111968424e6306bf4d292c0188ccb94ff9374ea2836b50a1a79f7cd656e1'
|
|
31
|
+
request[:bucket].should == 'sample-bucket'
|
|
32
|
+
request[:remote_ip].should == '207.171.172.6'
|
|
33
|
+
request[:key].should == 'object.png'
|
|
34
|
+
request[:operation].should == 'REST.GET.OBJECT'
|
|
35
|
+
request[:requester].should == '65a011a29cdf8ec533ec3d1ccaae921c'
|
|
36
|
+
request[:request_id].should == 'C980091AD89C936A'
|
|
37
|
+
request[:request_uri].should == 'GET /sample-bucket/object.png HTTP/1.1'
|
|
38
|
+
request[:error_code].should == nil
|
|
39
|
+
request[:http_status].should == 200
|
|
40
|
+
request[:total_time].should == 0.988
|
|
41
|
+
request[:turnaround_time].should == 0.987
|
|
42
|
+
request[:bytes_sent].should == 1243
|
|
43
|
+
request[:object_size].should == 1243
|
|
44
|
+
request[:user_agent].should == 'aranhabot'
|
|
45
|
+
request[:referer].should == nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::FileFormat::Apache do
|
|
4
|
+
|
|
5
|
+
describe '.access_line_definition' do
|
|
6
|
+
before(:each) do
|
|
7
|
+
@format_string = '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i" %T'
|
|
8
|
+
@line_definition = RequestLogAnalyzer::FileFormat::Apache.access_line_definition(@format_string)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should create a Regexp to match the line" do
|
|
12
|
+
@line_definition.regexp.should be_kind_of(Regexp)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should create a list of captures for the values in the lines" do
|
|
16
|
+
@line_definition.captures.should have(12).items
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should make it a header line" do
|
|
20
|
+
@line_definition.should be_header
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should make it a footer line" do
|
|
24
|
+
@line_definition.should be_footer
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should capture :duration" do
|
|
28
|
+
@line_definition.captures?(:duration).should be_true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '.create' do
|
|
33
|
+
|
|
34
|
+
before(:each) do
|
|
35
|
+
@format_string = '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"'
|
|
36
|
+
@format = RequestLogAnalyzer::FileFormat::Apache.create(@format_string)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should create the :access line definition" do
|
|
40
|
+
@format.should have_line_definition(:access).capturing(:timestamp, :remote_host, :bytes_sent, :http_method, :path, :http_version, :http_status)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should be a valid file format" do
|
|
44
|
+
@format.should be_valid
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should setup report trackers" do
|
|
48
|
+
@format.report_trackers.should_not be_empty
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context '"Common" access log parsing' do
|
|
53
|
+
before(:all) do
|
|
54
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(:apache, :common)
|
|
55
|
+
@log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
|
|
56
|
+
@sample_1 = '1.129.119.13 - - [08/Sep/2009:07:54:09 -0400] "GET /profile/18543424 HTTP/1.0" 200 8223'
|
|
57
|
+
@sample_2 = '1.82.235.29 - - [08/Sep/2009:07:54:05 -0400] "GET /gallery/fresh?page=23&per_page=16 HTTP/1.1" 200 23414'
|
|
58
|
+
@nonsense_sample = 'addasdsasadadssadasd'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should have a valid language definitions" do
|
|
62
|
+
@file_format.should be_valid
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should not parse a valid access log line" do
|
|
66
|
+
@file_format.should_not parse_line(@nonsense_sample)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "should read the correct values from a valid HTTP/1.0 access log line" do
|
|
70
|
+
@file_format.should parse_line(@sample_1).as(:access).and_capture(
|
|
71
|
+
:remote_host => '1.129.119.13',
|
|
72
|
+
:remote_logname => nil,
|
|
73
|
+
:user => nil,
|
|
74
|
+
:timestamp => 20090908075409,
|
|
75
|
+
:http_status => 200,
|
|
76
|
+
:http_method => 'GET',
|
|
77
|
+
:http_version => '1.0',
|
|
78
|
+
:bytes_sent => 8223)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should read the correct values from a valid 200 access log line" do
|
|
82
|
+
@file_format.should parse_line(@sample_2).as(:access).and_capture(
|
|
83
|
+
:remote_host => '1.82.235.29',
|
|
84
|
+
:remote_logname => nil,
|
|
85
|
+
:user => nil,
|
|
86
|
+
:timestamp => 20090908075405,
|
|
87
|
+
:http_status => 200,
|
|
88
|
+
:http_method => 'GET',
|
|
89
|
+
:http_version => '1.1',
|
|
90
|
+
:bytes_sent => 23414)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should parse 10 request from fixture access log without warnings" do
|
|
94
|
+
request_counter.should_receive(:hit!).exactly(10).times
|
|
95
|
+
@log_parser.should_not_receive(:warn)
|
|
96
|
+
|
|
97
|
+
@log_parser.parse_file(log_fixture(:apache_common)) do |request|
|
|
98
|
+
request_counter.hit! if request.kind_of?(RequestLogAnalyzer::FileFormat::Apache::Request)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context '"Combined" access log parsing' do
|
|
104
|
+
|
|
105
|
+
before(:all) do
|
|
106
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(:apache, :combined)
|
|
107
|
+
@log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
|
|
108
|
+
@sample_1 = '69.41.0.45 - - [02/Sep/2009:12:02:40 +0200] "GET //phpMyAdmin/ HTTP/1.1" 404 209 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"'
|
|
109
|
+
@sample_2 = '10.0.1.1 - - [02/Sep/2009:05:08:33 +0200] "GET / HTTP/1.1" 200 30 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9"'
|
|
110
|
+
@nonsense_sample = 'addasdsasadadssadasd'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "should have a valid language definitions" do
|
|
114
|
+
@file_format.should be_valid
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "should not parse a valid access log line" do
|
|
118
|
+
@file_format.should_not parse_line(@nonsense_sample)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "should read the correct values from a valid 404 access log line" do
|
|
122
|
+
@file_format.should parse_line(@sample_1).as(:access).and_capture(
|
|
123
|
+
:remote_host => '69.41.0.45',
|
|
124
|
+
:remote_logname => nil,
|
|
125
|
+
:user => nil,
|
|
126
|
+
:timestamp => 20090902120240,
|
|
127
|
+
:http_status => 404,
|
|
128
|
+
:http_method => 'GET',
|
|
129
|
+
:http_version => '1.1',
|
|
130
|
+
:bytes_sent => 209,
|
|
131
|
+
:referer => nil,
|
|
132
|
+
:user_agent => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "should read the correct values from a valid 200 access log line" do
|
|
136
|
+
@file_format.should parse_line(@sample_2).as(:access).and_capture(
|
|
137
|
+
:remote_host => '10.0.1.1',
|
|
138
|
+
:remote_logname => nil,
|
|
139
|
+
:user => nil,
|
|
140
|
+
:timestamp => 20090902050833,
|
|
141
|
+
:http_status => 200,
|
|
142
|
+
:http_method => 'GET',
|
|
143
|
+
:http_version => '1.1',
|
|
144
|
+
:bytes_sent => 30,
|
|
145
|
+
:referer => nil,
|
|
146
|
+
:user_agent => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "should parse 5 request from fixture access log without warnings" do
|
|
150
|
+
request_counter.should_receive(:hit!).exactly(5).times
|
|
151
|
+
@log_parser.should_not_receive(:warn)
|
|
152
|
+
|
|
153
|
+
@log_parser.parse_file(log_fixture(:apache_combined)) do |request|
|
|
154
|
+
request_counter.hit! if request.kind_of?(RequestLogAnalyzer::FileFormat::Apache::Request)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context '"Rack" access log parser' do
|
|
160
|
+
before(:each) do
|
|
161
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(:rack)
|
|
162
|
+
@log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
|
|
163
|
+
@sample_1 = '127.0.0.1 - - [16/Sep/2009 07:40:08] "GET /favicon.ico HTTP/1.1" 500 63183 0.0453'
|
|
164
|
+
@sample_2 = '127.0.0.1 - - [01/Oct/2009 07:58:10] "GET / HTTP/1.1" 200 1 0.0045'
|
|
165
|
+
@nonsense_sample = 'addasdsasadadssadasd'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "should create a kind of an Apache file format" do
|
|
169
|
+
@file_format.should be_kind_of(RequestLogAnalyzer::FileFormat::Apache)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "should have a valid language definitions" do
|
|
173
|
+
@file_format.should be_valid
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it "should parse a valid access log line with status 500" do
|
|
177
|
+
@file_format.should parse_line(@sample_1).as(:access).and_capture(
|
|
178
|
+
:remote_host => '127.0.0.1', :timestamp => 20090916074008, :user => nil,
|
|
179
|
+
:http_status => 500, :http_method => 'GET', :http_version => '1.1',
|
|
180
|
+
:duration => 0.0453, :bytes_sent => 63183, :remote_logname => nil)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "should parse a valid access log line with status 200" do
|
|
184
|
+
@file_format.should parse_line(@sample_2).as(:access).and_capture(
|
|
185
|
+
:remote_host => '127.0.0.1', :timestamp => 20091001075810, :user => nil,
|
|
186
|
+
:http_status => 200, :http_method => 'GET', :http_version => '1.1',
|
|
187
|
+
:duration => 0.0045, :bytes_sent => 1, :remote_logname => nil)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "should not parse an invalid access log line" do
|
|
191
|
+
@file_format.should_not parse_line(@nonsense_sample)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "should parse 2 Apache requests from a sample without warnings" do
|
|
195
|
+
request_counter.should_receive(:hit!).twice
|
|
196
|
+
@log_parser.should_not_receive(:warn)
|
|
197
|
+
|
|
198
|
+
@log_parser.parse_io(log_stream(@sample_1, @nonsense_sample, @sample_2)) do |request|
|
|
199
|
+
request_counter.hit! if request.kind_of?(RequestLogAnalyzer::FileFormat::Apache::Request)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::FileFormat do
|
|
4
|
+
|
|
5
|
+
describe ".format_definition" do
|
|
6
|
+
|
|
7
|
+
before(:each) do
|
|
8
|
+
@first_file_format = Class.new(RequestLogAnalyzer::FileFormat::Base)
|
|
9
|
+
@second_file_format = Class.new(RequestLogAnalyzer::FileFormat::Base)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "should specify line definitions directly within the file_format" do
|
|
13
|
+
@first_file_format.format_definition.direct_test :regexp => /test/
|
|
14
|
+
@first_file_format.should have_line_definition(:direct_test)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "specify lines with a block for the format definition" do
|
|
18
|
+
@first_file_format.format_definition do |format|
|
|
19
|
+
format.block_test :regexp => /test (\w+)/, :captures => [{:name => :tester, :type => :string}]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@first_file_format.should have_line_definition(:block_test).capturing(:tester)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should specify a line with a block" do
|
|
26
|
+
@first_file_format.format_definition.hash_test do |line|
|
|
27
|
+
line.regexp = /test/
|
|
28
|
+
line.captures = []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@first_file_format.should have_line_definition(:hash_test)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "should define lines only for its own language" do
|
|
35
|
+
@first_file_format.format_definition.first :regexp => /test 123/
|
|
36
|
+
@second_file_format.format_definition.second :regexp => /test 456/
|
|
37
|
+
|
|
38
|
+
@first_file_format.should have_line_definition(:first)
|
|
39
|
+
@first_file_format.should_not have_line_definition(:second)
|
|
40
|
+
@second_file_format.should_not have_line_definition(:first)
|
|
41
|
+
@second_file_format.should have_line_definition(:second)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe ".load" do
|
|
46
|
+
|
|
47
|
+
it "should return an instance of a FileFormat class" do
|
|
48
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(TestingFormat)
|
|
49
|
+
@file_format.should be_kind_of(TestingFormat)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should return itself if it already is a FileFormat::Base instance" do
|
|
53
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(testing_format)
|
|
54
|
+
@file_format.should be_kind_of(TestingFormat)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should load a predefined file format from the /file_format dir" do
|
|
58
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(:rails)
|
|
59
|
+
@file_format.should be_kind_of(RequestLogAnalyzer::FileFormat::Rails)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should load a provided format file" do
|
|
63
|
+
format_filename = File.dirname(__FILE__) + '/../../lib/testing_format.rb'
|
|
64
|
+
@file_format = RequestLogAnalyzer::FileFormat.load(format_filename)
|
|
65
|
+
@file_format.should be_kind_of(TestingFormat)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|