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.
Files changed (112) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +8 -0
  6. data/bin/request-log-analyzer +114 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +213 -0
  11. data/lib/cli/tools.rb +46 -0
  12. data/lib/request_log_analyzer.rb +44 -0
  13. data/lib/request_log_analyzer/aggregator.rb +49 -0
  14. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  15. data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
  16. data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
  17. data/lib/request_log_analyzer/controller.rb +332 -0
  18. data/lib/request_log_analyzer/database.rb +102 -0
  19. data/lib/request_log_analyzer/database/base.rb +115 -0
  20. data/lib/request_log_analyzer/database/connection.rb +38 -0
  21. data/lib/request_log_analyzer/database/request.rb +22 -0
  22. data/lib/request_log_analyzer/database/source.rb +13 -0
  23. data/lib/request_log_analyzer/database/warning.rb +14 -0
  24. data/lib/request_log_analyzer/file_format.rb +160 -0
  25. data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
  26. data/lib/request_log_analyzer/file_format/apache.rb +141 -0
  27. data/lib/request_log_analyzer/file_format/merb.rb +67 -0
  28. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  29. data/lib/request_log_analyzer/file_format/rails.rb +176 -0
  30. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  31. data/lib/request_log_analyzer/filter.rb +30 -0
  32. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  33. data/lib/request_log_analyzer/filter/field.rb +42 -0
  34. data/lib/request_log_analyzer/filter/timespan.rb +45 -0
  35. data/lib/request_log_analyzer/line_definition.rb +111 -0
  36. data/lib/request_log_analyzer/log_processor.rb +99 -0
  37. data/lib/request_log_analyzer/mailer.rb +62 -0
  38. data/lib/request_log_analyzer/output.rb +113 -0
  39. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  40. data/lib/request_log_analyzer/output/html.rb +184 -0
  41. data/lib/request_log_analyzer/request.rb +175 -0
  42. data/lib/request_log_analyzer/source.rb +72 -0
  43. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  44. data/lib/request_log_analyzer/source/log_parser.rb +274 -0
  45. data/lib/request_log_analyzer/tracker.rb +206 -0
  46. data/lib/request_log_analyzer/tracker/duration.rb +104 -0
  47. data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
  48. data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
  49. data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
  50. data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
  51. data/request-log-analyzer.gemspec +40 -0
  52. data/spec/database.yml +23 -0
  53. data/spec/fixtures/apache_combined.log +5 -0
  54. data/spec/fixtures/apache_common.log +10 -0
  55. data/spec/fixtures/decompression.log +12 -0
  56. data/spec/fixtures/decompression.log.bz2 +0 -0
  57. data/spec/fixtures/decompression.log.gz +0 -0
  58. data/spec/fixtures/decompression.log.zip +0 -0
  59. data/spec/fixtures/decompression.tar.gz +0 -0
  60. data/spec/fixtures/decompression.tgz +0 -0
  61. data/spec/fixtures/header_and_footer.log +6 -0
  62. data/spec/fixtures/merb.log +84 -0
  63. data/spec/fixtures/merb_prefixed.log +9 -0
  64. data/spec/fixtures/multiple_files_1.log +5 -0
  65. data/spec/fixtures/multiple_files_2.log +2 -0
  66. data/spec/fixtures/rails.db +0 -0
  67. data/spec/fixtures/rails_1x.log +59 -0
  68. data/spec/fixtures/rails_22.log +12 -0
  69. data/spec/fixtures/rails_22_cached.log +10 -0
  70. data/spec/fixtures/rails_unordered.log +24 -0
  71. data/spec/fixtures/syslog_1x.log +5 -0
  72. data/spec/fixtures/test_file_format.log +13 -0
  73. data/spec/fixtures/test_language_combined.log +14 -0
  74. data/spec/fixtures/test_order.log +16 -0
  75. data/spec/integration/command_line_usage_spec.rb +84 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +151 -0
  78. data/spec/lib/helpers.rb +52 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +76 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +24 -0
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  94. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  95. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  96. data/spec/unit/file_format/rails_format_spec.rb +164 -0
  97. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  98. data/spec/unit/filter/field_filter_spec.rb +66 -0
  99. data/spec/unit/filter/filter_spec.rb +17 -0
  100. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  101. data/spec/unit/mailer_spec.rb +30 -0
  102. data/spec/unit/request_spec.rb +111 -0
  103. data/spec/unit/source/log_parser_spec.rb +119 -0
  104. data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
  105. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  106. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  107. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  108. data/spec/unit/tracker/tracker_api_spec.rb +124 -0
  109. data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
  110. data/tasks/github-gem.rake +323 -0
  111. data/tasks/request_log_analyzer.rake +26 -0
  112. 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