request-log-analyzer 1.2.1 → 1.2.6

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 (44) hide show
  1. data/.gitignore +10 -0
  2. data/Rakefile +3 -1
  3. data/lib/request_log_analyzer/aggregator/database.rb +79 -46
  4. data/lib/request_log_analyzer/controller.rb +12 -7
  5. data/lib/request_log_analyzer/file_format/merb.rb +5 -7
  6. data/lib/request_log_analyzer/line_definition.rb +9 -0
  7. data/lib/request_log_analyzer/request.rb +2 -2
  8. data/lib/request_log_analyzer/source/log_parser.rb +4 -0
  9. data/lib/request_log_analyzer.rb +1 -1
  10. data/request-log-analyzer.gemspec +36 -0
  11. data/spec/fixtures/header_and_footer.log +6 -0
  12. data/spec/fixtures/merb_prefixed.log +9 -0
  13. data/spec/integration/command_line_usage_spec.rb +0 -2
  14. data/spec/lib/{helper.rb → helpers.rb} +1 -3
  15. data/spec/lib/macros.rb +2 -0
  16. data/spec/lib/matchers.rb +63 -0
  17. data/spec/lib/mocks.rb +12 -1
  18. data/spec/lib/testing_format.rb +6 -0
  19. data/spec/spec.opts +3 -0
  20. data/spec/spec_helper.rb +13 -4
  21. data/spec/unit/aggregator/database_spec.rb +234 -0
  22. data/spec/unit/aggregator/summarizer_spec.rb +0 -2
  23. data/spec/unit/controller/controller_spec.rb +0 -2
  24. data/spec/unit/controller/log_processor_spec.rb +0 -2
  25. data/spec/unit/file_format/file_format_api_spec.rb +50 -71
  26. data/spec/unit/file_format/line_definition_spec.rb +49 -45
  27. data/spec/unit/file_format/merb_format_spec.rb +20 -2
  28. data/spec/unit/file_format/rails_format_spec.rb +0 -2
  29. data/spec/unit/filter/anonymize_filter_spec.rb +0 -1
  30. data/spec/unit/filter/field_filter_spec.rb +0 -3
  31. data/spec/unit/filter/filter_spec.rb +17 -0
  32. data/spec/unit/filter/timespan_filter_spec.rb +0 -3
  33. data/spec/unit/source/log_parser_spec.rb +5 -4
  34. data/spec/unit/source/request_spec.rb +91 -52
  35. data/spec/unit/tracker/duration_tracker_spec.rb +0 -6
  36. data/spec/unit/tracker/frequency_tracker_spec.rb +0 -6
  37. data/spec/unit/tracker/hourly_spread_spec.rb +0 -4
  38. data/spec/unit/tracker/timespan_tracker_spec.rb +0 -4
  39. data/spec/unit/tracker/tracker_api_spec.rb +0 -2
  40. data/tasks/github-gem.rake +199 -192
  41. metadata +120 -92
  42. data/RELEASE_NOTES.rdoc +0 -10
  43. data/spec/unit/aggregator/database_inserter_spec.rb +0 -106
  44. data/tasks/rspec.rake +0 -12
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .svn/
2
+ .DS_Store
3
+ request-log-analyzer-*.gem
4
+ requests.db
5
+ /pkg
6
+ /doc
7
+ /tmp
8
+ /classes
9
+ /files
10
+ /coverage
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
1
  Dir[File.dirname(__FILE__) + "/tasks/*.rake"].each { |file| load(file) }
2
+
3
+ GithubGem::RakeTasks.new(:gem)
2
4
 
3
- task :default => :spec
5
+ task :default => "spec:specdoc"
@@ -16,10 +16,13 @@ module RequestLogAnalyzer::Aggregator
16
16
  # created to log all parse warnings.
17
17
  class Database < Base
18
18
 
19
+ attr_reader :request_class, :request_count, :orm_module, :warning_class
20
+
19
21
  # Establishes a connection to the database and creates the necessary database schema for the
20
22
  # current file format
21
23
  def prepare
22
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => options[:database])
24
+ initialize_orm_module!
25
+ establish_database_connection!
23
26
  File.unlink(options[:database]) if File.exist?(options[:database]) # TODO: keep old database?
24
27
  create_database_schema!
25
28
  end
@@ -28,9 +31,9 @@ module RequestLogAnalyzer::Aggregator
28
31
  # This will create a record in the requests table and create a record for every line that has been parsed,
29
32
  # in which the captured values will be stored.
30
33
  def aggregate(request)
31
- @request_object = @request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
34
+ @request_object = request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
32
35
  request.lines.each do |line|
33
- class_columns = @orm_module.const_get("#{line[:line_type]}_line".classify).column_names.reject { |column| ['id'].include?(column) }
36
+ class_columns = orm_module.const_get("#{line[:line_type]}_line".classify).column_names.reject { |column| ['id'].include?(column) }
34
37
  attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
35
38
  @request_object.send("#{line[:line_type]}_lines").build(attributes)
36
39
  end
@@ -41,13 +44,14 @@ module RequestLogAnalyzer::Aggregator
41
44
 
42
45
  # Finalizes the aggregator by closing the connection to the database
43
46
  def finalize
44
- @request_count = @orm_module::Request.count
45
- ActiveRecord::Base.remove_connection
47
+ @request_count = orm_module::Request.count
48
+ remove_database_connection!
49
+ deinitialize_orm_module!
46
50
  end
47
51
 
48
52
  # Records w warining in the warnings table.
49
53
  def warning(type, message, lineno)
50
- @orm_module::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
54
+ warning_class.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
51
55
  end
52
56
 
53
57
  # Prints a short report of what has been inserted into the database
@@ -61,91 +65,119 @@ module RequestLogAnalyzer::Aggregator
61
65
  output << "\n"
62
66
  end
63
67
 
68
+ # Retreives the connection that is used for the database inserter
69
+ def connection
70
+ orm_module::Base.connection
71
+ end
72
+
64
73
  protected
65
74
 
75
+ # Create a module and a default subclass of ActiveRecord::Base on which to establish the database connection
76
+ def initialize_orm_module!
77
+
78
+ # Create a Database module in the file format if it does not yet exists
79
+ file_format.class.const_set('Database', Module.new) unless file_format.class.const_defined?('Database')
80
+ @orm_module = file_format.class.const_get('Database')
81
+
82
+ # Register the base activerecord class
83
+ unless orm_module.const_defined?('Base')
84
+ orm_base_class = Class.new(ActiveRecord::Base)
85
+ orm_base_class.abstract_class = true
86
+ orm_module.const_set('Base', orm_base_class)
87
+ end
88
+ end
89
+
90
+ # Deinitializes the ORM module and the ActiveRecord::Base subclass.
91
+ def deinitialize_orm_module!
92
+ file_format.class.send(:remove_const, 'Database') if file_format.class.const_defined?('Database')
93
+ @orm_module = nil
94
+ end
95
+
96
+ # Established a connection with the database for this session
97
+ def establish_database_connection!
98
+ orm_module::Base.establish_connection(:adapter => 'sqlite3', :database => options[:database])
99
+ #ActiveRecord::Migration.class_eval("def self.connection; #{@orm_module.to_s}::Base.connection; end ")
100
+ end
101
+
102
+ def remove_database_connection!
103
+ #ActiveRecord::Migration.class_eval("def self.connection; ActiveRecord::Base.connection; end ")
104
+ orm_module::Base.remove_connection
105
+ end
106
+
66
107
  # This function creates a database table for a given line definition.
67
108
  # It will create a field for every capture in the line, and adds a lineno field to indicate at
68
109
  # what line in the original file the line was found, and a request_id to link lines related
69
110
  # to the same request. It will also create an index in the request_id field to speed up queries.
70
- def create_database_table(name, definition)
71
- ActiveRecord::Migration.verbose = options[:debug]
72
- ActiveRecord::Migration.create_table("#{name}_lines") do |t|
111
+ def create_database_table(definition)
112
+ connection.create_table("#{definition.name}_lines") do |t|
113
+
114
+ # Add default fields for every line type
73
115
  t.column(:request_id, :integer)
74
116
  t.column(:lineno, :integer)
117
+
75
118
  definition.captures.each do |capture|
76
-
77
119
  # Add a field for every capture
78
120
  t.column(capture[:name], column_type(capture[:type]))
79
-
80
- # If the capture provides other field as well, create them too
81
- if capture[:provides].kind_of?(Hash)
82
- capture[:provides].each do |field, field_type|
83
- t.column(field, column_type(field_type))
84
- end
85
- end
121
+
122
+ # If the capture provides other field as well, create columns for them, too
123
+ capture[:provides].each { |field, field_type| t.column(field, column_type(field_type)) } if capture[:provides].kind_of?(Hash)
86
124
  end
87
125
  end
88
- ActiveRecord::Migration.add_index("#{name}_lines", [:request_id])
126
+
127
+ # Create an index on the request_id column to support faster querying
128
+ connection.add_index("#{definition.name}_lines", [:request_id])
89
129
  end
90
130
 
91
131
  # Creates an ActiveRecord class for a given line definition.
92
132
  # A subclass of ActiveRecord::Base is created and an association with the Request class is
93
133
  # created using belongs_to / has_many. This association will later be used to create records
94
134
  # in the corresponding table. This table should already be created before this method is called.
95
- def create_activerecord_class(name, definition)
96
- class_name = "#{name}_line".camelize
97
- klass = Class.new(ActiveRecord::Base)
135
+ def create_activerecord_class(definition)
136
+ class_name = "#{definition.name}_line".camelize
137
+ klass = Class.new(orm_module::Base)
98
138
  klass.send(:belongs_to, :request)
99
139
 
100
- definition.captures.each do |capture|
101
- klass.send(:serialize, capture[:name], Hash) if capture[:provides]
140
+ definition.captures.select { |c| c.has_key?(:provides) }.each do |capture|
141
+ klass.send(:serialize, capture[:name], Hash)
102
142
  end
103
143
 
104
- @orm_module.const_set(class_name, klass) unless @orm_module.const_defined?(class_name)
105
- @request_class.send(:has_many, "#{name}_lines".to_sym)
144
+ orm_module.const_set(class_name, klass) unless orm_module.const_defined?(class_name)
145
+ request_class.send(:has_many, "#{definition.name}_lines".to_sym)
106
146
  end
107
147
 
108
148
  # Creates a requests table, in which a record is created for every request. It also creates an
109
149
  # ActiveRecord::Base class to communicate with this table.
110
150
  def create_request_table_and_class
111
- ActiveRecord::Migration.verbose = options[:debug]
112
- ActiveRecord::Migration.create_table("requests") do |t|
113
- t.integer :first_lineno
114
- t.integer :last_lineno
151
+ connection.create_table("requests") do |t|
152
+ t.column :first_lineno, :integer
153
+ t.column :last_lineno, :integer
115
154
  end
116
155
 
117
- @orm_module.const_set('Request', Class.new(ActiveRecord::Base)) unless @orm_module.const_defined?('Request')
118
- @request_class = @orm_module.const_get('Request')
156
+ orm_module.const_set('Request', Class.new(orm_module::Base)) unless orm_module.const_defined?('Request')
157
+ @request_class = orm_module.const_get('Request')
119
158
  end
120
159
 
121
160
  # Creates a warnings table and a corresponding Warning class to communicate with this table using ActiveRecord.
122
161
  def create_warning_table_and_class
123
- ActiveRecord::Migration.verbose = options[:debug]
124
- ActiveRecord::Migration.create_table("warnings") do |t|
125
- t.string :warning_type, :limit => 30, :null => false
126
- t.string :message
127
- t.integer :lineno
162
+ connection.create_table("warnings") do |t|
163
+ t.column :warning_type, :string, :limit => 30, :null => false
164
+ t.column :message, :string
165
+ t.column :lineno, :integer
128
166
  end
129
167
 
130
- @orm_module.const_set('Warning', Class.new(ActiveRecord::Base)) unless @orm_module.const_defined?('Warning')
168
+ orm_module.const_set('Warning', Class.new(orm_module::Base)) unless orm_module.const_defined?('Warning')
169
+ @warning_class = orm_module.const_get('Warning')
131
170
  end
132
171
 
133
172
  # Creates the database schema and related ActiveRecord::Base subclasses that correspond to the
134
173
  # file format definition. These ORM classes will later be used to create records in the database.
135
174
  def create_database_schema!
136
-
137
- if file_format.class.const_defined?('Database')
138
- @orm_module = file_format.class.const_get('Database')
139
- else
140
- @orm_module = file_format.class.const_set('Database', Module.new)
141
- end
142
-
143
175
  create_request_table_and_class
144
176
  create_warning_table_and_class
145
177
 
146
178
  file_format.line_definitions.each do |name, definition|
147
- create_database_table(name, definition)
148
- create_activerecord_class(name, definition)
179
+ create_database_table(definition)
180
+ create_activerecord_class(definition)
149
181
  end
150
182
  end
151
183
 
@@ -154,6 +186,7 @@ module RequestLogAnalyzer::Aggregator
154
186
  def column_type(type_indicator)
155
187
  case type_indicator
156
188
  when :eval; :text
189
+ when :hash; :text
157
190
  when :text; :text
158
191
  when :string; :string
159
192
  when :sec; :double
@@ -191,14 +191,11 @@ module RequestLogAnalyzer
191
191
  def run!
192
192
 
193
193
  @aggregators.each { |agg| agg.prepare }
194
+ install_signal_handlers
194
195
 
195
- begin
196
- @source.each_request do |request|
197
- aggregate_request(filter_request(request))
198
- end
199
- rescue Interrupt => e
200
- handle_progress(:interrupted)
201
- puts "Caught interrupt! Stopped parsing."
196
+ @source.each_request do |request|
197
+ aggregate_request(filter_request(request))
198
+ break if @interrupted
202
199
  end
203
200
 
204
201
  @aggregators.each { |agg| agg.finalize }
@@ -219,5 +216,13 @@ module RequestLogAnalyzer
219
216
  end
220
217
  end
221
218
 
219
+ def install_signal_handlers
220
+ Signal.trap("INT") do
221
+ handle_progress(:interrupted)
222
+ puts "Caught interrupt! Stopping parsing..."
223
+ @interrupted = true
224
+ end
225
+ end
226
+
222
227
  end
223
228
  end
@@ -16,18 +16,16 @@ module RequestLogAnalyzer::FileFormat
16
16
  line.teaser = /Params/
17
17
  line.regexp = /Params\:\ (\{.+\})/
18
18
  line.captures << { :name => :params, :type => :eval, :provides => {
19
- :namespace => :string, :controller => :string, :action => :string, :format => :string } }
19
+ :namespace => :string, :controller => :string, :action => :string, :format => :string, :method => :string } }
20
20
  end
21
21
 
22
22
  # ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
23
23
  line_definition :completed do |line|
24
24
  line.footer = true
25
- line.teaser = /\{:dispatch_time/
26
- line.regexp = /\{\:dispatch_time=>(\d+\.\d+(?:e-?\d+)?), (?:\:after_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?(?:\:before_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?\:action_time=>(\d+\.\d+(?:e-?\d+)?)\}/
27
- line.captures << { :name => :dispatch_time, :type => :duration } \
28
- << { :name => :after_filters_time, :type => :duration } \
29
- << { :name => :before_filters_time, :type => :duration } \
30
- << { :name => :action_time, :type => :duration }
25
+ line.regexp = /(\{.*\:dispatch_time\s*=>\s*\d+\.\d+.*\})/
26
+ line.captures << { :name => :times_hash, :type => :eval, :provides => {
27
+ :dispatch_time => :duration, :after_filters_time => :duration,
28
+ :before_filters_time => :duration, :action_time => :duration } }
31
29
  end
32
30
 
33
31
  REQUEST_CATEGORIZER = Proc.new do |request|
@@ -66,6 +66,8 @@ module RequestLogAnalyzer
66
66
 
67
67
  alias :=~ :matches
68
68
 
69
+ # matches the line and converts the captured values using the request's
70
+ # convert_value function.
69
71
  def match_for(line, request, lineno = nil, parser = nil)
70
72
  if match_info = matches(line, lineno, parser)
71
73
  convert_captured_values(match_info[:captures], request)
@@ -74,11 +76,18 @@ module RequestLogAnalyzer
74
76
  end
75
77
  end
76
78
 
79
+ # Updates a captures hash using the converters specified in the request
80
+ # and handle the :provides option in the line definition.
77
81
  def convert_captured_values(values, request)
78
82
  value_hash = {}
79
83
  captures.each_with_index do |capture, index|
84
+
85
+ # convert the value using the request convert_value function
80
86
  converted = request.convert_value(values[index], capture)
81
87
  value_hash[capture[:name]] ||= converted
88
+
89
+ # Add items directly to the resulting hash from the converted value
90
+ # if it is a hash and they are set in the :provides hash for this line definition
82
91
  if converted.kind_of?(Hash) && capture[:provides].kind_of?(Hash)
83
92
  capture[:provides].each do |name, type|
84
93
  value_hash[name] ||= request.convert_value(converted[name], { :type => type })
@@ -145,11 +145,11 @@ module RequestLogAnalyzer
145
145
  end
146
146
 
147
147
  def first_lineno
148
- @lines.first[:lineno]
148
+ @lines.map { |line| line[:lineno] }.reject { |v| v.nil? }.min
149
149
  end
150
150
 
151
151
  def last_lineno
152
- @lines.last[:lineno]
152
+ @lines.map { |line| line[:lineno] }.reject { |v| v.nil? }.max
153
153
  end
154
154
  end
155
155
  end
@@ -207,6 +207,10 @@ module RequestLogAnalyzer::Source
207
207
  warn(:unclosed_request, "Encountered header line (#{request_data[:line_definition].name.inspect}), but previous request was not closed!")
208
208
  @current_request = nil # remove all data that was parsed, skip next request as well.
209
209
  end
210
+ if footer_line?(request_data)
211
+ handle_request(@current_request, &block) # yield @current_request
212
+ @current_request = nil
213
+ end
210
214
  else
211
215
  @current_request = @file_format.request(request_data)
212
216
  end
@@ -11,7 +11,7 @@ module RequestLogAnalyzer
11
11
 
12
12
  # The current version of request-log-analyzer.
13
13
  # This will be diplayed in output reports etc.
14
- VERSION = '1.2.1'
14
+ VERSION = "1.2.6"
15
15
 
16
16
  # Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
17
17
  # <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'request-log-analyzer'
3
+ s.version = "1.2.6"
4
+ s.date = "2009-09-02"
5
+
6
+ s.rubyforge_project = 'r-l-a'
7
+
8
+ s.bindir = 'bin'
9
+ s.executables = ['request-log-analyzer']
10
+ s.default_executable = 'request-log-analyzer'
11
+
12
+ s.summary = "A command line tool to analyze request logs for Rails, Merb and other application servers"
13
+ s.description = <<-eos
14
+ Request log analyzer's purpose is to find ot how your web application is being used and to focus your optimization efforts.
15
+ This tool will parse all requests in the application's log file and aggregate the information. Once it is finished parsing
16
+ the log file(s), it will show the requests that take op most server time using various metrics. It can also insert all
17
+ parsed request information into a database so you can roll your own analysis. It supports Rails- and Merb-based applications
18
+ out of the box, but file formats of other applications can easily be supported by supplying an easy to write log file format
19
+ definition.
20
+ eos
21
+
22
+ s.rdoc_options << '--title' << s.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
23
+ s.extra_rdoc_files = ['README.rdoc']
24
+
25
+ s.requirements << "To use the database inserter, ActiveRecord and an appropriate database adapter are required."
26
+
27
+ s.add_development_dependency('rspec', '>= 1.2.4')
28
+ s.add_development_dependency('git', '>= 1.1.0')
29
+
30
+ s.authors = ['Willem van Bergen', 'Bart ten Brinke']
31
+ s.email = ['willem@railsdoctors.com', 'bart@railsdoctors.com']
32
+ s.homepage = 'http://railsdoctors.com'
33
+
34
+ s.files = %w(spec/unit/filter/anonymize_filter_spec.rb lib/request_log_analyzer/line_definition.rb lib/request_log_analyzer/output/html.rb lib/request_log_analyzer/controller.rb spec/fixtures/rails_22_cached.log lib/request_log_analyzer/file_format/rails_development.rb spec/lib/macros.rb spec/fixtures/merb_prefixed.log tasks/request_log_analyzer.rake spec/unit/file_format/file_format_api_spec.rb spec/integration/command_line_usage_spec.rb spec/fixtures/decompression.log.bz2 lib/request_log_analyzer/log_processor.rb lib/request_log_analyzer/tracker.rb lib/request_log_analyzer/filter.rb spec/fixtures/rails_unordered.log bin/request-log-analyzer request-log-analyzer.gemspec DESIGN.rdoc spec/unit/filter/timespan_filter_spec.rb lib/request_log_analyzer/filter/field.rb lib/request_log_analyzer/tracker/frequency.rb spec/fixtures/decompression.log.gz spec/fixtures/decompression.log spec/lib/matchers.rb spec/fixtures/test_order.log lib/request_log_analyzer/output/fixed_width.rb lib/request_log_analyzer/filter/anonymize.rb spec/lib/testing_format.rb lib/request_log_analyzer/tracker/timespan.rb lib/request_log_analyzer/aggregator.rb lib/cli/progressbar.rb README.rdoc spec/fixtures/merb.log lib/request_log_analyzer/tracker/hourly_spread.rb .gitignore spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb lib/request_log_analyzer/aggregator/echo.rb spec/unit/controller/log_processor_spec.rb lib/request_log_analyzer.rb Rakefile spec/spec_helper.rb spec/unit/filter/filter_spec.rb lib/request_log_analyzer/aggregator/summarizer.rb lib/request_log_analyzer/file_format/rails.rb spec/fixtures/test_language_combined.log spec/fixtures/decompression.tar.gz spec/unit/filter/field_filter_spec.rb spec/spec.opts lib/request_log_analyzer/aggregator/database.rb lib/request_log_analyzer/filter/timespan.rb lib/request_log_analyzer/source/log_parser.rb spec/fixtures/decompression.tgz spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/fixtures/header_and_footer.log lib/cli/tools.rb lib/request_log_analyzer/file_format/merb.rb spec/fixtures/multiple_files_1.log spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb lib/request_log_analyzer/source.rb lib/request_log_analyzer/request.rb spec/unit/controller/controller_spec.rb lib/request_log_analyzer/output.rb spec/lib/helpers.rb spec/fixtures/rails_1x.log spec/lib/mocks.rb spec/fixtures/decompression.log.zip spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/fixtures/test_file_format.log lib/request_log_analyzer/source/database.rb spec/unit/aggregator/database_spec.rb tasks/github-gem.rake lib/request_log_analyzer/tracker/duration.rb lib/request_log_analyzer/file_format.rb spec/unit/aggregator/summarizer_spec.rb spec/fixtures/rails_22.log spec/fixtures/multiple_files_2.log spec/fixtures/syslog_1x.log LICENSE spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb lib/cli/command_line_arguments.rb)
35
+ s.test_files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/unit/file_format/file_format_api_spec.rb spec/integration/command_line_usage_spec.rb spec/unit/filter/timespan_filter_spec.rb spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/controller/log_processor_spec.rb spec/unit/filter/filter_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb spec/unit/controller/controller_spec.rb spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/unit/aggregator/database_spec.rb spec/unit/aggregator/summarizer_spec.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb)
36
+ end
@@ -0,0 +1,6 @@
1
+ start!
2
+ this is a header and footer line
3
+ blah
4
+
5
+ this is a header and footer line
6
+ blah
@@ -0,0 +1,9 @@
1
+ Aug 31 18:35:23 typekit-web001 merb: ~
2
+ Aug 31 18:35:23 typekit-web001 merb: cache: [GET /] miss
3
+ Aug 31 18:35:24 typekit-web001 merb: ~ Started request handling: Mon Aug 31 18:35:25 +0000 2009
4
+ Aug 31 18:35:24 typekit-web001 merb: ~ Routed to: {"action"=>"index", "controller"=>"home"}
5
+ Aug 31 18:35:24 typekit-web001 merb: ~ Params: {"action"=>"index", "controller"=>"home"}
6
+ Aug 31 18:35:24 typekit-web001 merb: ~ In repository block default
7
+ Aug 31 18:35:24 typekit-web001 merb: ~ (0.000000) SELECT `id`, `created_at`, `updated_at`, `braintree_vault_id`, `cancelled_at` FROM `accounts` WHERE (`id` IN (6214)) ORDER BY `id`
8
+ Aug 31 18:35:24 typekit-web001 merb: ~ Redirecting to: /plans (302)
9
+ Aug 31 18:35:24 typekit-web001 merb: ~ {:after_filters_time=>0.0, :before_filters_time=>0.0, :dispatch_time=>0.012001, :action_time=>0.012001}
@@ -2,8 +2,6 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
3
  describe RequestLogAnalyzer, 'running from command line' do
4
4
 
5
- include RequestLogAnalyzer::Spec::Helper
6
-
7
5
  before(:each) do
8
6
  cleanup_temp_files!
9
7
  end
@@ -1,7 +1,5 @@
1
- module RequestLogAnalyzer::Spec::Helper
1
+ module RequestLogAnalyzer::Spec::Helpers
2
2
 
3
- include RequestLogAnalyzer::Spec::Mocks
4
-
5
3
  # Create or return a new TestingFormat
6
4
  def testing_format
7
5
  @testing_format ||= TestingFormat.new
@@ -0,0 +1,2 @@
1
+ module RequestLogAnalyzer::Spec::Macros
2
+ end
@@ -0,0 +1,63 @@
1
+ module RequestLogAnalyzer::Spec::Matchers
2
+
3
+ class HasLineDefinition
4
+
5
+ def initialize(line_type)
6
+ @line_type = line_type.to_sym
7
+ @captures = []
8
+ end
9
+
10
+ def capturing(*captures)
11
+ @captures += captures
12
+ return self
13
+ end
14
+
15
+ def matches?(file_format)
16
+ if file_format.new.line_definitions.include?(@line_type)
17
+ ld = file_format.new.line_definitions[@line_type]
18
+ @captures.all? { |c| ld.captures.include?(c) }
19
+ else
20
+ false
21
+ end
22
+ end
23
+ end
24
+
25
+ class Parse
26
+ def initialize(line)
27
+ @line = line
28
+ @captures = nil
29
+ end
30
+
31
+ def failure_message
32
+ message = "expected to parse the provided line"
33
+ if @found_captures
34
+ message << "with captures #{@captures.inspect}, but captured #{@found_captures.inspect}"
35
+ end
36
+ return message
37
+ end
38
+
39
+ def capturing(*captures)
40
+ @captures = captures
41
+ return self
42
+ end
43
+
44
+ def matches?(line_definition)
45
+ hash = line_definition.matches(@line)
46
+ if hash
47
+ @found_captures = hash[:captures].to_a
48
+ @captures.nil? ? true : @found_captures.eql?(@captures)
49
+ else
50
+ return false
51
+ end
52
+ end
53
+ end
54
+
55
+ def have_line_definition(line_type)
56
+ return HasLineDefinition.new(line_type)
57
+ end
58
+
59
+ def parse(line)
60
+ Parse.new(line)
61
+ end
62
+
63
+ end
data/spec/lib/mocks.rb CHANGED
@@ -38,10 +38,21 @@ module RequestLogAnalyzer::Spec::Mocks
38
38
  output.stub!(:title)
39
39
  output.stub!(:line)
40
40
  output.stub!(:with_style)
41
- output.stub!(:table) { yield [] }
41
+ output.stub!(:table).and_yield([])
42
42
  output.stub!(:io).and_return(mock_io)
43
43
  return output
44
44
  end
45
45
 
46
+ def mock_migrator
47
+ table_creator = mock('ActiveRecord table creator')
48
+ table_creator.stub!(:column)
49
+
50
+ migrator = mock('ActiveRecord::Base.connection for migrations')
51
+ migrator.stub!(:add_index)
52
+ migrator.stub!(:create_table).and_yield(table_creator).and_return(true)
53
+ migrator.stub!(:table_creator).and_return(table_creator)
54
+ return migrator
55
+ end
56
+
46
57
 
47
58
  end
@@ -27,6 +27,12 @@ class TestingFormat < RequestLogAnalyzer::FileFormat::Base
27
27
  line.captures = [{ :name => :request_no, :type => :integer }]
28
28
  end
29
29
 
30
+ format_definition.combined do |line|
31
+ line.header = true
32
+ line.footer = true
33
+ line.regexp = /this is a header and footer line/
34
+ end
35
+
30
36
  report do |analyze|
31
37
  analyze.frequency :test_capture, :title => 'What is testing exactly?'
32
38
  end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format specdoc
3
+ --reverse
data/spec/spec_helper.rb CHANGED
@@ -2,14 +2,23 @@ $:.reject! { |e| e.include? 'TextMate' }
2
2
  $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
3
 
4
4
  require 'rubygems'
5
- require 'spec'
5
+ require 'spec/autorun'
6
6
  require 'request_log_analyzer'
7
7
 
8
8
  module RequestLogAnalyzer::Spec
9
9
  end
10
10
 
11
- require File.dirname(__FILE__) + '/lib/testing_format'
12
- require File.dirname(__FILE__) + '/lib/mocks'
13
- require File.dirname(__FILE__) + '/lib/helper'
11
+ # Include all files in the spec_helper directory
12
+ Dir[File.dirname(__FILE__) + "/lib/**/*.rb"].each do |file|
13
+ require file
14
+ end
14
15
 
15
16
  Dir.mkdir("#{File.dirname(__FILE__)}/../tmp") unless File.exist?("#{File.dirname(__FILE__)}/../tmp")
17
+
18
+ Spec::Runner.configure do |config|
19
+ config.include RequestLogAnalyzer::Spec::Matchers
20
+ config.include RequestLogAnalyzer::Spec::Mocks
21
+ config.include RequestLogAnalyzer::Spec::Helpers
22
+
23
+ config.extend RequestLogAnalyzer::Spec::Macros
24
+ end