request-log-analyzer 1.2.7 → 1.2.8

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/Rakefile CHANGED
@@ -1,5 +1,8 @@
1
1
  Dir[File.dirname(__FILE__) + "/tasks/*.rake"].each { |file| load(file) }
2
2
 
3
+ # Create rake tasks for a gem manages by github. The tasks are created in the
4
+ # gem namespace
3
5
  GithubGem::RakeTasks.new(:gem)
4
-
5
- task :default => "spec:specdoc"
6
+
7
+ # Set the RSpec runner with specdoc output as default task.
8
+ task :default => "spec:specdoc"
@@ -21,6 +21,8 @@ begin
21
21
  end
22
22
 
23
23
  command_line.option(:format, :alias => :f, :default => 'rails')
24
+ command_line.option(:apache_format)
25
+
24
26
  command_line.option(:file, :alias => :e)
25
27
  command_line.option(:parse_strategy, :default => 'assume-correct')
26
28
  command_line.option(:dump)
@@ -143,10 +143,10 @@ module RequestLogAnalyzer::Aggregator
143
143
 
144
144
  orm_module.const_set(class_name, klass) unless orm_module.const_defined?(class_name)
145
145
  request_class.send(:has_many, "#{definition.name}_lines".to_sym)
146
- end
147
-
148
- # Creates a requests table, in which a record is created for every request. It also creates an
149
- # ActiveRecord::Base class to communicate with this table.
146
+ end
147
+
148
+ # Creates a requests table, in which a record is created for every parsed request.
149
+ # It also creates an ActiveRecord::Base class to communicate with this table.
150
150
  def create_request_table_and_class
151
151
  connection.create_table("requests") do |t|
152
152
  t.column :first_lineno, :integer
@@ -157,6 +157,19 @@ module RequestLogAnalyzer::Aggregator
157
157
  @request_class = orm_module.const_get('Request')
158
158
  end
159
159
 
160
+ # Creates a sources table, in which a record is created for every file that is parsed.
161
+ # It also creates an ActiveRecord::Base ORM class for the table.
162
+ def create_source_table_and_class
163
+ connection.create_table('sources') do |t|
164
+ t.column :filename, :string
165
+ t.column :mtime, :datetime
166
+ t.column :filesize, :integer
167
+ end
168
+
169
+ orm_module.const_set('Source', Class.new(orm_module::Base)) unless orm_module.const_defined?('Source')
170
+ @source_class = orm_module.const_get('Source')
171
+ end
172
+
160
173
  # Creates a warnings table and a corresponding Warning class to communicate with this table using ActiveRecord.
161
174
  def create_warning_table_and_class
162
175
  connection.create_table("warnings") do |t|
@@ -172,6 +185,7 @@ module RequestLogAnalyzer::Aggregator
172
185
  # Creates the database schema and related ActiveRecord::Base subclasses that correspond to the
173
186
  # file format definition. These ORM classes will later be used to create records in the database.
174
187
  def create_database_schema!
188
+ create_source_table_and_class
175
189
  create_request_table_and_class
176
190
  create_warning_table_and_class
177
191
 
@@ -24,7 +24,7 @@ module RequestLogAnalyzer::Aggregator
24
24
 
25
25
  # Include missing trackers through method missing.
26
26
  def method_missing(tracker_method, *args)
27
- track(tracker_method, args.first)
27
+ track(tracker_method, *args)
28
28
  end
29
29
 
30
30
  # Track the frequency of a specific category
@@ -46,7 +46,11 @@ module RequestLogAnalyzer
46
46
  end
47
47
 
48
48
  # Create the controller with the correct file format
49
- file_format = RequestLogAnalyzer::FileFormat.load(arguments[:format])
49
+ file_format = if arguments[:apache_format]
50
+ RequestLogAnalyzer::FileFormat.load(:apache, arguments[:apache_format])
51
+ else
52
+ RequestLogAnalyzer::FileFormat.load(arguments[:format])
53
+ end
50
54
 
51
55
  # register sources
52
56
  if arguments.parameters.length == 1
@@ -8,28 +8,69 @@ module RequestLogAnalyzer::FileFormat
8
8
 
9
9
  class Apache < Base
10
10
 
11
- # 125.76.230.10 - - [02/Sep/2009:03:33:46 +0200] "GET /cart/install.txt HTTP/1.1" 404 214 "-" "Toata dragostea mea pentru diavola"
12
- line_definition :access do |line|
13
- line.header = true
14
- line.footer = true
15
- line.regexp = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[([^\]]{26})\] "([A-Z]+) ([^\s]+) HTTP\/(\d+(?:\.\d+)*)" (\d+) \d+ "-" "([^"]+)"/
16
- line.captures << { :name => :ip_address, :type => :string } \
17
- << { :name => :timestamp, :type => :timestamp } \
18
- << { :name => :method, :type => :string } \
19
- << { :name => :path, :type => :string } \
20
- << { :name => :http_version, :type => :string } \
21
- << { :name => :status, :type => :integer } \
22
- << { :name => :user_agent, :type => :string }
23
- end
24
-
25
-
26
- report do |analyze|
27
- analyze.timespan :line_type => :access
28
- analyze.hourly_spread :line_type => :access
29
- analyze.frequency :category => :method, :amount => 20, :title => "HTTP methods frequency"
30
- analyze.frequency :category => :path, :amount => 20, :title => "Most popular paths"
11
+ # A hash of predefined Apache log format strings
12
+ LOG_FORMAT_DEFAULTS = {
13
+ :common => '%h %l %u %t "%r" %>s %b',
14
+ :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"'
15
+ }
16
+
17
+ # A hash that defines how the log format directives should be parsed.
18
+ LOG_DIRECTIVES = {
19
+ 'h' => { :regexp => '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', :captures => [{:name => :ip_address, :type => :string}] },
20
+ 't' => { :regexp => '\[([^\]]{26})\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
21
+ 's' => { :regexp => '(\d{3})', :captures => [{:name => :http_status, :type => :integer}] },
22
+ 'r' => { :regexp => '([A-Z]+) ([^\s]+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
23
+ {:name => :path, :type => :string}, {:name => :http_version, :type => :string}]}
24
+ }
25
+
26
+ # Creates the Apache log format language based on a Apache log format string.
27
+ # It will set up the line definition and the report trackers according to the Apache access log format,
28
+ # which should be passed as first argument. By default, is uses the 'combined' log format.
29
+ def self.create(*args)
30
+ access_line = access_line_definition(args.first)
31
+ self.new({ :access => access_line}, report_trackers(access_line))
31
32
  end
32
-
33
+
34
+ # Creates the access log line definition based on the Apache log format string
35
+ def self.access_line_definition(format_string)
36
+ format_string ||= :combined
37
+ format_string = LOG_FORMAT_DEFAULTS[format_string.to_sym] || format_string
38
+
39
+ line_regexp = ''
40
+ captures = []
41
+ format_string.scan(/([^%]*)(?:%(?:\{([^\}]+)\})?>?([A-Za-z]))?/) do |literal, arg, variable|
42
+
43
+ line_regexp << Regexp.quote(literal) # Make sure to parse the literal before the directive
44
+ if variable
45
+ # Check if we recognize the log directive
46
+ if directive = LOG_DIRECTIVES[variable]
47
+ line_regexp << directive[:regexp] # Parse the value of the directive
48
+ captures += directive[:captures] # Add the directive's information to the captures
49
+ else
50
+ line_regexp << '.*' # Just accept any input for this literal
51
+ end
52
+ end
53
+ end
54
+
55
+ # Return a new line definition object
56
+ return RequestLogAnalyzer::LineDefinition.new(:access, :regexp => Regexp.new(line_regexp),
57
+ :captures => captures, :header => true, :footer => true)
58
+ end
59
+
60
+ # Sets up the report trackers according to the access line definition.
61
+ def self.report_trackers(line_definition)
62
+ analyze = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
63
+
64
+ analyze.timespan if line_definition.captures?(:timestamp)
65
+ analyze.hourly_spread if line_definition.captures?(:timestamp)
66
+
67
+ analyze.frequency :category => :http_method, :amount => 20, :title => "HTTP methods" if line_definition.captures?(:http_method)
68
+ analyze.frequency :category => :http_status, :amount => 20, :title => "HTTP statuses" if line_definition.captures?(:http_status)
69
+ analyze.frequency :category => :path, :amount => 20, :title => "Most popular URIs" if line_definition.captures?(:path)
70
+
71
+ return analyze.trackers
72
+ end
73
+
33
74
  # Define a custom Request class for the Apache file format to speed up timestamp handling.
34
75
  class Request < RequestLogAnalyzer::Request
35
76
 
@@ -10,7 +10,7 @@ module RequestLogAnalyzer::FileFormat
10
10
  # * A FileFormat class (of which an imstance will be returned)
11
11
  # * A filename (from which the FileFormat class is loaded)
12
12
  # * A symbol of a built-in file format (e.g. :rails)
13
- def self.load(file_format)
13
+ def self.load(file_format, *args)
14
14
  klass = nil
15
15
  if file_format.kind_of?(RequestLogAnalyzer::FileFormat::Base)
16
16
  # this already is a file format! return itself
@@ -41,7 +41,7 @@ module RequestLogAnalyzer::FileFormat
41
41
  raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
42
42
  raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
43
43
 
44
- @current_file_format = klass.new # return an instance of the class
44
+ @current_file_format = klass.create(*args) # return an instance of the class
45
45
  end
46
46
 
47
47
  # Makes classes aware of a file format by registering the file_format variable
@@ -62,11 +62,17 @@ module RequestLogAnalyzer::FileFormat
62
62
  # A subclass of this class is instantiated when request-log-analyzer is started and this instance
63
63
  # is shared with all components of the application so they can act on the specifics of the format
64
64
  class Base
65
-
65
+
66
+ attr_reader :line_definitions, :report_trackers
67
+
68
+ ####################################################################################
69
+ # CLASS METHODS for format definition
70
+ ####################################################################################
71
+
66
72
  # Registers the line definer instance for a subclass.
67
73
  def self.inherited(subclass)
68
74
  if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
69
-
75
+
70
76
  # Create aline and report definer for this class
71
77
  subclass.class_eval do
72
78
  instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
@@ -77,32 +83,24 @@ module RequestLogAnalyzer::FileFormat
77
83
  # Create a custom Request class for this file format
78
84
  subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
79
85
  else
80
-
86
+
81
87
  # Copy the line and report definer from the parent class.
82
88
  subclass.class_eval do
83
89
  instance_variable_set(:@line_definer, superclass.line_definer.clone)
84
90
  instance_variable_set(:@report_definer, superclass.report_definer.clone)
85
91
  class << self; attr_accessor :line_definer, :report_definer; end
86
- end
87
-
92
+ end
93
+
88
94
  # Create a custom Request class based on the superclass's Request class
89
95
  subclass.const_set('Request', Class.new(subclass.superclass::Request)) unless subclass.const_defined?('Request')
90
96
  end
91
- end
92
-
97
+ end
98
+
93
99
  # Specifies a single line defintions.
94
100
  def self.line_definition(name, &block)
95
101
  @line_definer.send(name, &block)
96
102
  end
97
-
98
- def request_class
99
- self.class::Request
100
- end
101
-
102
- def request(*hashes)
103
- request_class.create(self, *hashes)
104
- end
105
-
103
+
106
104
  # Specifies multiple line definitions at once using a block
107
105
  def self.format_definition(&block)
108
106
  if block_given?
@@ -111,36 +109,61 @@ module RequestLogAnalyzer::FileFormat
111
109
  return self.line_definer
112
110
  end
113
111
  end
114
-
112
+
115
113
  # Specifies the summary report using a block.
116
114
  def self.report(mode = :append, &block)
117
- if mode == :overwrite
118
- self.report_definer.reset!
119
- end
120
-
115
+ self.report_definer.reset! if mode == :overwrite
121
116
  yield(self.report_definer)
122
117
  end
123
118
 
124
- # Returns all line definitions
125
- def line_definitions
126
- @line_definitions ||= self.class.line_definer.line_definitions
119
+ ####################################################################################
120
+ # Instantiation
121
+ ####################################################################################
122
+
123
+ def self.create(*args)
124
+ # Ignore arguments
125
+ return self.new(line_definer.line_definitions, report_definer.trackers)
127
126
  end
128
-
129
- # Returns all the defined trackers for the summary report.
130
- def report_trackers
131
- self.class.report_definer.trackers# => rescue []
127
+
128
+ def initialize(line_definitions = [], report_trackers = [])
129
+ @line_definitions, @report_trackers = line_definitions, report_trackers
132
130
  end
133
-
131
+
132
+ ####################################################################################
133
+ # INSTANCE methods
134
+ ####################################################################################
135
+
136
+ # Returns the Request class of this file format
137
+ def request_class
138
+ self.class::Request
139
+ end
140
+
141
+ # Returns a Request instance with the given parsed lines that should be provided as hashes.
142
+ def request(*hashes)
143
+ request_class.create(self, *hashes)
144
+ end
145
+
134
146
  # Checks whether the line definitions form a valid language.
135
147
  # A file format should have at least a header and a footer line type
136
148
  def valid?
137
- line_definitions.detect { |(name, ld)| ld.header } && line_definitions.detect { |(name, ld)| ld.footer }
149
+ line_definitions.any? { |(name, ld)| ld.header } && line_definitions.any? { |(name, ld)| ld.footer }
138
150
  end
139
-
151
+
152
+ # Returns true if this language captures the given symbol in one of its line definitions
153
+ def captures?(name)
154
+ line_definitions.any? { |(name, ld)| ld.captures?(name) }
155
+ end
156
+
140
157
  # Function that a file format con implement to monkey patch the environment.
141
158
  # * <tt>controller</tt> The environment is provided as a controller instance
142
159
  def setup_environment(controller)
143
-
160
+ end
161
+
162
+ # Parses a line by trying to parse it using every line definition in this file format
163
+ def parse_line(line, &warning_handler)
164
+ request_data = nil
165
+ self.line_definitions.any? { |lt, definition| request_data = definition.matches(line, &warning_handler) }
166
+ return request_data
144
167
  end
145
168
  end
146
169
  end
@@ -30,6 +30,9 @@ module RequestLogAnalyzer
30
30
  attr_accessor :teaser, :regexp, :captures
31
31
  attr_accessor :header, :footer
32
32
 
33
+ alias_method :header?, :header
34
+ alias_method :footer?, :footer
35
+
33
36
  # Initializes the LineDefinition instance with a hash containing the different elements of
34
37
  # the definition.
35
38
  def initialize(name, definition = {})
@@ -49,13 +52,13 @@ module RequestLogAnalyzer
49
52
  # with all the fields parsed from that line as content.
50
53
  # If the line definition has a teaser-check, a :teaser_check_failed warning will be emitted
51
54
  # if this teaser-check is passed, but the full regular exprssion does not ,atch.
52
- def matches(line, lineno = nil, parser = nil)
55
+ def matches(line, &warning_handler)
53
56
  if @teaser.nil? || @teaser =~ line
54
57
  if match_data = line.match(@regexp)
55
- return { :line_definition => self, :lineno => lineno, :captures => match_data.captures}
58
+ return { :line_definition => self, :captures => match_data.captures}
56
59
  else
57
- if @teaser && parser
58
- parser.warn(:teaser_check_failed, "Teaser matched for #{name.inspect}, but full line did not:\n#{line.inspect}")
60
+ if @teaser && warning_handler
61
+ warning_handler.call(:teaser_check_failed, "Teaser matched for #{name.inspect}, but full line did not:\n#{line.inspect}")
59
62
  end
60
63
  return false
61
64
  end
@@ -68,8 +71,8 @@ module RequestLogAnalyzer
68
71
 
69
72
  # matches the line and converts the captured values using the request's
70
73
  # convert_value function.
71
- def match_for(line, request, lineno = nil, parser = nil)
72
- if match_info = matches(line, lineno, parser)
74
+ def match_for(line, request, &warning_handler)
75
+ if match_info = matches(line, &warning_handler)
73
76
  convert_captured_values(match_info[:captures], request)
74
77
  else
75
78
  false
@@ -97,6 +100,11 @@ module RequestLogAnalyzer
97
100
  return value_hash
98
101
  end
99
102
 
103
+ # Returns true if this line captures values of the given name
104
+ def captures?(name)
105
+ captures.any? { |c| c[:name] == name }
106
+ end
107
+
100
108
  end
101
109
 
102
110
  end
@@ -17,7 +17,7 @@ module RequestLogAnalyzer::Source
17
17
  # All available parse strategies.
18
18
  PARSE_STRATEGIES = ['cautious', 'assume-correct']
19
19
 
20
- attr_reader :source_files
20
+ attr_reader :source_files, :current_file, :current_lineno
21
21
 
22
22
  # Initializes the log file parser instance.
23
23
  # It will apply the language specific FileFormat module to this instance. It will use the line
@@ -32,7 +32,8 @@ module RequestLogAnalyzer::Source
32
32
  @parsed_requests = 0
33
33
  @skipped_lines = 0
34
34
  @skipped_requests = 0
35
- @current_io = nil
35
+ @current_file = nil
36
+ @current_lineno = nil
36
37
  @source_files = options[:source_files]
37
38
 
38
39
  @options[:parse_strategy] ||= DEFAULT_PARSE_STRATEGY
@@ -124,27 +125,21 @@ module RequestLogAnalyzer::Source
124
125
  # <tt>options</tt>:: A hash of options that can be used by the parser.
125
126
  def parse_io(io, options = {}, &block) # :yields: request
126
127
 
127
- @current_io = io
128
- lineno = 0
129
- @current_io.each_line do |line|
130
- @progress_handler.call(:progress, @current_io.pos) if @progress_handler && @current_io.kind_of?(File)
128
+ @current_lineno = 0
129
+ io.each_line do |line|
130
+ @progress_handler.call(:progress, io.pos) if @progress_handler && io.kind_of?(File)
131
131
 
132
- request_data = nil
133
- file_format.line_definitions.each do |line_type, definition|
134
- request_data = definition.matches(line, lineno, self)
135
- break if request_data
136
- end
137
-
138
- if request_data
132
+ if request_data = file_format.parse_line(line) { |wt, message| warn(wt, message) }
139
133
  @parsed_lines += 1
140
- update_current_request(request_data, &block)
134
+ update_current_request(request_data.merge(:lineno => @current_lineno), &block)
141
135
  end
142
- lineno += 1
136
+
137
+ @current_lineno += 1
143
138
  end
144
139
 
145
140
  warn(:unfinished_request_on_eof, "End of file reached, but last request was not completed!") unless @current_request.nil?
146
141
 
147
- @current_io = nil
142
+ @current_lineno = nil
148
143
  end
149
144
 
150
145
  # Add a block to this method to install a progress handler while parsing.
@@ -169,7 +164,7 @@ module RequestLogAnalyzer::Source
169
164
  # <tt>type</tt>:: The warning type (a Symbol)
170
165
  # <tt>message</tt>:: A message explaining the warning
171
166
  def warn(type, message)
172
- @warning_handler.call(type, message, @current_io.lineno) if @warning_handler
167
+ @warning_handler.call(type, message, @current_lineno) if @warning_handler
173
168
  end
174
169
 
175
170
  protected
@@ -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.7"
14
+ VERSION = "1.2.8"
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.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'request-log-analyzer'
3
- s.version = "1.2.7"
4
- s.date = "2009-09-02"
3
+ s.version = "1.2.8"
4
+ s.date = "2009-09-08"
5
5
 
6
6
  s.rubyforge_project = 'r-l-a'
7
7
 
@@ -1,7 +1,7 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
3
  describe RequestLogAnalyzer, 'running from command line' do
4
-
4
+
5
5
  before(:each) do
6
6
  cleanup_temp_files!
7
7
  end
@@ -10,27 +10,27 @@ describe RequestLogAnalyzer, 'running from command line' do
10
10
  cleanup_temp_files!
11
11
  end
12
12
 
13
- it "should find 4 requests in default mode" do
13
+ it "should find 4 requests in default mode" do
14
14
  output = run("#{log_fixture(:rails_1x)}")
15
15
  output.detect { |line| /Parsed requests\:\s*4/ =~ line }.should_not be_nil
16
16
  end
17
17
 
18
- it "should find 3 requests with a --select option" do
18
+ it "should find 3 requests with a --select option" do
19
19
  output = run("#{log_fixture(:rails_1x)} --select controller PeopleController")
20
20
  output.detect { |line| /Parsed requests\:\s*4/ =~ line }.should_not be_nil
21
21
  end
22
22
 
23
- it "should find 1 requests with a --reject option" do
23
+ it "should find 1 requests with a --reject option" do
24
24
  output = run("#{log_fixture(:rails_1x)} --reject controller PeopleController")
25
25
  output.detect { |line| /Parsed requests\:\s*4/ =~ line }.should_not be_nil
26
26
  end
27
-
28
- it "should write output to a file with the --file option" do
27
+
28
+ it "should write output to a file with the --file option" do
29
29
  run("#{log_fixture(:rails_1x)} --file #{temp_output_file(:report)}")
30
30
  File.exist?(temp_output_file(:report)).should be_true
31
31
  end
32
32
 
33
- it "should write only ASCII characters to a file with the --file option" do
33
+ it "should write only ASCII characters to a file with the --file option" do
34
34
  run("#{log_fixture(:rails_1x)} --file #{temp_output_file(:report)}")
35
35
  /^[\x00-\x7F]*$/.match(File.read(temp_output_file(:report))).should_not be_nil
36
36
  end
@@ -39,31 +39,35 @@ describe RequestLogAnalyzer, 'running from command line' do
39
39
  output = run("#{log_fixture(:rails_1x)} --output HTML")
40
40
  output.any? { |line| /<html.*>/ =~ line}
41
41
  end
42
-
43
- it "should run with the --database option" do
42
+
43
+ it "should run with the --database option" do
44
44
  run("#{log_fixture(:rails_1x)} --database #{temp_output_file(:database)}")
45
45
  File.exist?(temp_output_file(:database)).should be_true
46
46
  end
47
47
 
48
- it "should use no colors in the report with the --boring option" do
48
+ it "should use no colors in the report with the --boring option" do
49
49
  output = run("#{log_fixture(:rails_1x)} --boring")
50
50
  output.any? { |line| /\e/ =~ line }.should be_false
51
51
  end
52
-
53
- it "should use only ASCII characters in the report with the --boring option" do
52
+
53
+ it "should use only ASCII characters in the report with the --boring option" do
54
54
  output = run("#{log_fixture(:rails_1x)} --boring")
55
55
  output.all? { |line| /^[\x00-\x7F]*$/ =~ line }.should be_true
56
56
  end
57
-
58
- it "should parse a Merb file if --format merb is set" do
57
+
58
+ it "should parse a Merb file if --apache-format is set" do
59
59
  output = run("#{log_fixture(:merb)} --format merb")
60
- output.detect { |line| /Parsed requests\:\s*11/ =~ line }.should_not be_nil
61
- end
62
-
60
+ output.detect { |line| /Parsed requests\:\s*11/ =~ line }.should_not be_nil
61
+ end
62
+
63
+ it "should parse a Apache access log file if --format merb is set" do
64
+ output = run("#{log_fixture(:apache)} --apache-format combined")
65
+ output.detect { |line| /Parsed requests\:\s*5/ =~ line }.should_not be_nil
66
+ end
67
+
63
68
  it "should dump the results to a YAML file" do
64
69
  run("#{log_fixture(:rails_1x)} --dump #{temp_output_file(:dump)}")
65
70
  File.exist?(temp_output_file(:dump)).should be_true
66
71
  YAML::load(File.read(temp_output_file(:dump))).should have_at_least(1).item
67
- end
68
-
69
- end
72
+ end
73
+ end
data/spec/lib/helpers.rb CHANGED
@@ -2,7 +2,7 @@ module RequestLogAnalyzer::Spec::Helpers
2
2
 
3
3
  # Create or return a new TestingFormat
4
4
  def testing_format
5
- @testing_format ||= TestingFormat.new
5
+ @testing_format ||= TestingFormat.create
6
6
  end
7
7
 
8
8
  # Load a log file from the fixture folder
data/spec/lib/matchers.rb CHANGED
@@ -13,9 +13,9 @@ module RequestLogAnalyzer::Spec::Matchers
13
13
  end
14
14
 
15
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) }
16
+ file_format = file_format.create if file_format.kind_of?(Class)
17
+ if ld = file_format.line_definitions[@line_type]
18
+ @captures.all? { |c| ld.captures.map { |cd| cd[:name] }.include?(c) }
19
19
  else
20
20
  false
21
21
  end
@@ -183,10 +183,21 @@ describe RequestLogAnalyzer::Aggregator::Database do
183
183
  @database_inserter.send :create_database_schema!
184
184
  end
185
185
 
186
- it "should create a Warnng class inheriting from ActiveRecord and the base class of the ORM module" do
186
+ it "should create a Warning class inheriting from ActiveRecord and the base class of the ORM module" do
187
187
  @database_inserter.send :create_database_schema!
188
188
  @database_inserter.warning_class.ancestors.should include(ActiveRecord::Base, @database_inserter.orm_module::Base)
189
189
  end
190
+
191
+ it "should create a sources table to track parsed files" do
192
+ @connection.should_receive(:create_table).with("sources")
193
+ @database_inserter.send :create_database_schema!
194
+ end
195
+
196
+ it "should create a Source ORM class" do
197
+ @database_inserter.send :create_database_schema!
198
+ @database_inserter.orm_module::Source.ancestors.should include(ActiveRecord::Base, @database_inserter.orm_module::Base)
199
+ end
200
+
190
201
 
191
202
  it "should create a table for every line type" do
192
203
  @database_inserter.should_receive(:create_database_table).with(an_instance_of(RequestLogAnalyzer::LineDefinition)).exactly(@line_type_cnt).times
@@ -1,35 +1,81 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
3
  describe RequestLogAnalyzer::FileFormat::Apache do
4
-
5
- before(:each) do
6
- @file_format = RequestLogAnalyzer::FileFormat.load(:apache)
7
- @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
8
- @sample = '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)"'
9
- end
10
-
11
- it "should have a valid language definitions" do
12
- @file_format.should be_valid
13
- end
14
-
15
- it "should parse a valid access log line" do
16
- @file_format.line_definitions[:access].matches(@sample, 1, nil).should be_kind_of(Hash)
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"'
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 be_kind_of(Array)
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
17
26
  end
18
27
 
19
- it "should read the correct values from a valid access log line" do
20
- @log_parser.parse_io(@sample) do |request|
21
- request[:ip_address].should == '69.41.0.45'
22
- request[:timestamp].should == 20090902120240
23
- request[:status].should == 404
24
- request[:method].should == 'GET'
25
- request[:http_version].should == '1.1'
28
+ describe '.create' do
29
+
30
+ before(:each) do
31
+ @format_string = '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"'
32
+ @format = RequestLogAnalyzer::FileFormat::Apache.create(@format_string)
33
+ end
34
+
35
+ it "should create the :access line definition" do
36
+ @format.should have_line_definition(:access).capturing(:timestamp, :ip_address, :http_method, :path, :http_version, :http_status)
37
+ end
38
+
39
+ it "should be a valid file format" do
40
+ @format.should be_valid
41
+ end
42
+
43
+ it "should setup report trackers" do
44
+ @format.report_trackers.should_not be_empty
26
45
  end
27
46
  end
47
+
48
+ context 'log parsing' do
49
+
50
+ before(:each) do
51
+ @file_format = RequestLogAnalyzer::FileFormat.load(:apache)
52
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
53
+ @sample = '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)"'
54
+ end
55
+
56
+ it "should have a valid language definitions" do
57
+ @file_format.should be_valid
58
+ end
59
+
60
+ it "should parse a valid access log line" do
61
+ @file_format.line_definitions[:access].matches(@sample).should be_kind_of(Hash)
62
+ end
63
+
64
+ it "should read the correct values from a valid access log line" do
65
+ @log_parser.parse_io(@sample) do |request|
66
+ request[:ip_address].should == '69.41.0.45'
67
+ request[:timestamp].should == 20090902120240
68
+ request[:http_status].should == 404
69
+ request[:http_method].should == 'GET'
70
+ request[:http_version].should == '1.1'
71
+ end
72
+ end
28
73
 
29
- it "should parse 5 request from fixture access log" do
30
- counter = mock('counter')
31
- counter.should_receive(:hit!).exactly(5).times
32
- @log_parser.parse_file(log_fixture(:apache)) { counter.hit! }
74
+ it "should parse 5 request from fixture access log" do
75
+ counter = mock('counter')
76
+ counter.should_receive(:hit!).exactly(5).times
77
+ @log_parser.parse_file(log_fixture(:apache)) { counter.hit! }
78
+ end
33
79
  end
34
-
35
80
  end
81
+
@@ -16,7 +16,7 @@ describe RequestLogAnalyzer::FileFormat do
16
16
 
17
17
  it "specify lines with a block for the format definition" do
18
18
  @first_file_format.format_definition do |format|
19
- format.block_test :regexp => /test (\w+)/, :captures => [:tester]
19
+ format.block_test :regexp => /test (\w+)/, :captures => [{:name => :tester, :type => :string}]
20
20
  end
21
21
 
22
22
  @first_file_format.should have_line_definition(:block_test).capturing(:tester)
@@ -71,7 +71,7 @@ end
71
71
  describe RequestLogAnalyzer::Source::LogParser, :decompression do
72
72
 
73
73
  before(:each) do
74
- @log_parser = RequestLogAnalyzer::Source::LogParser.new(RequestLogAnalyzer::FileFormat::Rails.new)
74
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(RequestLogAnalyzer::FileFormat::Rails.create)
75
75
  end
76
76
 
77
77
  it "should parse a rails gzipped log file" do
@@ -203,7 +203,7 @@ module GithubGem
203
203
  end
204
204
 
205
205
  def rubyforge_release_task
206
- sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version, "pgk/#{gemspec.name}-#{gemspec.version}.gem"
206
+ sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
207
207
  end
208
208
 
209
209
  def release_task
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request-log-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.7
4
+ version: 1.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-09-02 00:00:00 +02:00
13
+ date: 2009-09-08 00:00:00 +02:00
14
14
  default_executable: request-log-analyzer
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency