request-log-analyzer 1.2.7 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
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