request-log-analyzer 1.10.0 → 1.11.0

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 (49) hide show
  1. data/.gitignore +2 -1
  2. data/.infinity_test +8 -0
  3. data/bin/request-log-analyzer +0 -1
  4. data/lib/request_log_analyzer/aggregator/database_inserter.rb +2 -1
  5. data/lib/request_log_analyzer/aggregator.rb +5 -5
  6. data/lib/request_log_analyzer/controller.rb +0 -3
  7. data/lib/request_log_analyzer/database.rb +6 -7
  8. data/lib/request_log_analyzer/file_format/apache.rb +1 -1
  9. data/lib/request_log_analyzer/file_format/delayed_job2.rb +2 -2
  10. data/lib/request_log_analyzer/file_format/delayed_job21.rb +2 -2
  11. data/lib/request_log_analyzer/file_format/haproxy.rb +107 -13
  12. data/lib/request_log_analyzer/file_format/mysql.rb +5 -5
  13. data/lib/request_log_analyzer/file_format/rails3.rb +8 -1
  14. data/lib/request_log_analyzer/file_format.rb +43 -14
  15. data/lib/request_log_analyzer/filter.rb +4 -5
  16. data/lib/request_log_analyzer/line_definition.rb +6 -4
  17. data/lib/request_log_analyzer/output.rb +3 -5
  18. data/lib/request_log_analyzer/source/log_parser.rb +56 -4
  19. data/lib/request_log_analyzer/source.rb +3 -4
  20. data/lib/request_log_analyzer/tracker.rb +8 -8
  21. data/lib/request_log_analyzer.rb +15 -29
  22. data/request-log-analyzer.gemspec +3 -3
  23. data/spec/fixtures/mysql_slow_query.log +0 -1
  24. data/spec/integration/command_line_usage_spec.rb +0 -5
  25. data/spec/lib/helpers.rb +2 -2
  26. data/spec/lib/matchers.rb +38 -7
  27. data/spec/lib/mocks.rb +1 -5
  28. data/spec/unit/database/base_class_spec.rb +1 -0
  29. data/spec/unit/file_format/amazon_s3_format_spec.rb +58 -55
  30. data/spec/unit/file_format/apache_format_spec.rb +74 -162
  31. data/spec/unit/file_format/common_regular_expressions_spec.rb +51 -26
  32. data/spec/unit/file_format/delayed_job21_format_spec.rb +22 -31
  33. data/spec/unit/file_format/delayed_job2_format_spec.rb +27 -32
  34. data/spec/unit/file_format/delayed_job_format_spec.rb +44 -63
  35. data/spec/unit/file_format/haproxy_format_spec.rb +69 -71
  36. data/spec/unit/file_format/line_definition_spec.rb +26 -33
  37. data/spec/unit/file_format/merb_format_spec.rb +22 -37
  38. data/spec/unit/file_format/mysql_format_spec.rb +80 -123
  39. data/spec/unit/file_format/oink_format_spec.rb +29 -61
  40. data/spec/unit/file_format/postgresql_format_spec.rb +2 -4
  41. data/spec/unit/file_format/rack_format_spec.rb +49 -44
  42. data/spec/unit/file_format/rails3_format_spec.rb +39 -37
  43. data/spec/unit/file_format/rails_format_spec.rb +52 -68
  44. data/spec/unit/file_format/w3c_format_spec.rb +40 -39
  45. data/spec/unit/source/log_parser_spec.rb +1 -1
  46. metadata +4 -6
  47. data/lib/mixins/gets_memory_protection.rb +0 -80
  48. data/lib/request_log_analyzer/output/fancy_html.rb +0 -44
  49. data/lib/request_log_analyzer/source/database_loader.rb +0 -87
data/.gitignore CHANGED
@@ -10,4 +10,5 @@ requests.db
10
10
  /files
11
11
  /coverage
12
12
  Gemfile.lock
13
- /.bundle/
13
+ /.bundle/
14
+ *.rbc
data/.infinity_test ADDED
@@ -0,0 +1,8 @@
1
+ infinity_test do
2
+
3
+ use :rubies => %w(1.8.7 1.9.2 ree), :test_framework => :rspec # jruby rbx
4
+
5
+ before(:each_ruby) do |environment|
6
+ environment.system('bundle install')
7
+ end
8
+ end
@@ -58,7 +58,6 @@ begin
58
58
 
59
59
  command_line.switch(:debug)
60
60
  command_line.switch(:no_progress)
61
- command_line.switch(:gets_memory_protection)
62
61
  command_line.switch(:silent)
63
62
 
64
63
  command_line.minimum_parameters = 1
@@ -1,4 +1,3 @@
1
-
2
1
  module RequestLogAnalyzer::Aggregator
3
2
 
4
3
  # The database aggregator will create an SQLite3 database with all parsed request information.
@@ -19,6 +18,8 @@ module RequestLogAnalyzer::Aggregator
19
18
  # Establishes a connection to the database and creates the necessary database schema for the
20
19
  # current file format
21
20
  def prepare
21
+ require 'request_log_analyzer/database'
22
+
22
23
  @sources = {}
23
24
  @database = RequestLogAnalyzer::Database.new(options[:database])
24
25
  @database.file_format = source.file_format
@@ -1,9 +1,5 @@
1
1
  module RequestLogAnalyzer::Aggregator
2
2
 
3
- autoload :Echo, 'request_log_analyzer/aggregator/echo'
4
- autoload :Summarizer, 'request_log_analyzer/aggregator/summarizer'
5
- autoload :DatabaseInserter, 'request_log_analyzer/aggregator/database_inserter'
6
-
7
3
  # The base class of an aggregator. This class provides the interface to which
8
4
  # every aggregator should comply (by simply subclassing this class).
9
5
  class Base
@@ -46,4 +42,8 @@ module RequestLogAnalyzer::Aggregator
46
42
  end
47
43
 
48
44
  end
49
- end
45
+ end
46
+
47
+ require 'request_log_analyzer/aggregator/echo'
48
+ require 'request_log_analyzer/aggregator/summarizer'
49
+ require 'request_log_analyzer/aggregator/database_inserter'
@@ -21,8 +21,6 @@ module RequestLogAnalyzer
21
21
  # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
22
22
  def self.build_from_arguments(arguments)
23
23
 
24
- require 'mixins/gets_memory_protection' if arguments[:gets_memory_protection]
25
-
26
24
  options = {}
27
25
 
28
26
  # Copy fields
@@ -58,7 +56,6 @@ module RequestLogAnalyzer
58
56
 
59
57
  # Handle output format casing
60
58
  if options[:output].class == String
61
- options[:output] = 'FancyHTML' if options[:output] =~ /^fancy_?html$/i
62
59
  options[:output] = 'HTML' if options[:output] =~ /^html$/i
63
60
  options[:output] = 'FixedWidth' if options[:output] =~ /^fixed_?width$/i
64
61
  end
@@ -3,12 +3,7 @@ require 'active_record'
3
3
 
4
4
  class RequestLogAnalyzer::Database
5
5
 
6
- autoload :Connection, 'request_log_analyzer/database/connection'
7
- autoload :Base, 'request_log_analyzer/database/base'
8
- autoload :Request, 'request_log_analyzer/database/request'
9
- autoload :Source, 'request_log_analyzer/database/source'
10
- autoload :Warning, 'request_log_analyzer/database/warning'
11
-
6
+ require 'request_log_analyzer/database/connection'
12
7
  include RequestLogAnalyzer::Database::Connection
13
8
 
14
9
  attr_accessor :file_format
@@ -100,5 +95,9 @@ class RequestLogAnalyzer::Database
100
95
  end
101
96
  end
102
97
  end
103
-
104
98
  end
99
+
100
+ require 'request_log_analyzer/database/base'
101
+ require 'request_log_analyzer/database/request'
102
+ require 'request_log_analyzer/database/source'
103
+ require 'request_log_analyzer/database/warning'
@@ -31,7 +31,7 @@ module RequestLogAnalyzer::FileFormat
31
31
  # A hash that defines how the log format directives should be parsed.
32
32
  LOG_DIRECTIVES = {
33
33
  '%' => { nil => { :regexp => '%', :captures => [] } },
34
- 'h' => { nil => { :regexp => '([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)', :captures => [{:name => :remote_host, :type => :string}] } },
34
+ 'h' => { nil => { :regexp => "(#{hostname_or_ip_address})", :captures => [{:name => :remote_host, :type => :string}] } },
35
35
  'a' => { nil => { :regexp => "(#{ip_address})", :captures => [{:name => :remote_ip, :type => :string}] } },
36
36
  'b' => { nil => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] } },
37
37
  'c' => { nil => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] } },
@@ -8,7 +8,7 @@ module RequestLogAnalyzer::FileFormat
8
8
 
9
9
  line_definition :job_lock do |line|
10
10
  line.header = true
11
- line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[Worker\(\S+ host:(\S+) pid:(\d+)\)\] acquired lock on (\S+)/
11
+ line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[Worker\(\S+ host:(#{hostname_or_ip_address}) pid:(\d+)\)\] acquired lock on (\S+)/
12
12
 
13
13
  line.capture(:timestamp).as(:timestamp)
14
14
  line.capture(:host)
@@ -18,7 +18,7 @@ module RequestLogAnalyzer::FileFormat
18
18
 
19
19
  line_definition :job_completed do |line|
20
20
  line.footer = true
21
- line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[JOB\] \S+ host:(\S+) pid:(\d+) completed after (\d+\.\d+)/
21
+ line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \* \[JOB\] \S+ host:(#{hostname_or_ip_address}) pid:(\d+) completed after (\d+\.\d+)/
22
22
  line.capture(:timestamp).as(:timestamp)
23
23
  line.capture(:host)
24
24
  line.capture(:pid).as(:integer)
@@ -8,7 +8,7 @@ module RequestLogAnalyzer::FileFormat
8
8
 
9
9
  line_definition :job_lock do |line|
10
10
  line.header = true
11
- line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(\S+) pid:(\d+)\)\] acquired lock on (\S+)/
11
+ line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(#{hostname_or_ip_address}) pid:(\d+)\)\] acquired lock on (\S+)/
12
12
 
13
13
  line.capture(:timestamp).as(:timestamp)
14
14
  line.capture(:host)
@@ -18,7 +18,7 @@ module RequestLogAnalyzer::FileFormat
18
18
 
19
19
  line_definition :job_completed do |line|
20
20
  line.footer = true
21
- line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(\S+) pid:(\d+)\)\] (\S+) completed after (\d+\.\d+)/
21
+ line.regexp = /(#{timestamp('%Y-%m-%dT%H:%M:%S%z')}): \[Worker\(\S+ host:(#{hostname_or_ip_address}) pid:(\d+)\)\] (\S+) completed after (\d+\.\d+)/
22
22
  line.capture(:timestamp).as(:timestamp)
23
23
  line.capture(:host)
24
24
  line.capture(:pid).as(:integer)
@@ -4,31 +4,45 @@ module RequestLogAnalyzer::FileFormat
4
4
 
5
5
  extend CommonRegularExpressions
6
6
 
7
- # Define line types
8
- line_definition :haproxy do |line|
9
- line.header = true
10
- line.footer = true
11
-
12
- line.regexp = %r{
7
+ # substitute version specific parts of the haproxy entry regexp.
8
+ def self.compose_regexp(millisecs, backends, counters, connections, queues)
9
+ %r{
13
10
  (#{ip_address}):\d+\s # client_ip ':' client_port
14
- \[(#{timestamp('%d/%b/%Y:%H:%M:%S')})\.\d{3}\]\s # '[' accept_date ']'
11
+ \[(#{timestamp('%d/%b/%Y:%H:%M:%S')})#{millisecs}\]\s # '[' accept_date ']'
15
12
  (\S+)\s # frontend_name
16
- (\S+)\/(\S+)\s # backend_name '/' server_name
17
- (\d+|-1)\/(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s # Tq '/' Tw '/' Tc '/' Tr '/' Tt
13
+ #{backends}
14
+ #{counters}
18
15
  (\d+)\s # status_code
19
16
  \+?(\d+)\s # bytes_read
20
17
  (\S+)\s # captured_request_cookie
21
18
  (\S+)\s # captured_response_cookie
22
19
  (\w|-)(\w|-)(\w|-)(\w|-)\s # termination_state
23
- (\d+)\/(\d+)\/(\d+)\/(\d+)\/\+?(\d+)\s # actconn '/' feconn '/' beconn '/' srv_conn '/' retries
24
- (\d+)\/(\d+)\s # srv_queue '/' backend_queue
20
+ #{connections}
21
+ #{queues}
25
22
  (\S*)\s? # captured_request_headers
26
23
  (\S*)\s? # captured_response_headers
27
24
  "([^"]*)" # '"' http_request '"'
28
25
  }x
26
+ end
27
+
28
+ # Define line types
29
+
30
+ # line definition for haproxy 1.3 and higher
31
+ line_definition :haproxy13 do |line|
32
+ line.header = true
33
+ line.footer = true
34
+ line.teaser = /\.\d{3}\] \S+ \S+\/\S+ / # .millisecs] frontend_name backend_name/server_name
35
+
36
+ line.regexp = compose_regexp(
37
+ '\.\d{3}', # millisecs
38
+ '(\S+)\/(\S+)\s', # backend_name '/' server_name
39
+ '(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s', # Tq '/' Tw '/' Tc '/' Tr '/' Tt
40
+ '(\d+)\/(\d+)\/(\d+)\/(\d+)\/\+?(\d+)\s', # actconn '/' feconn '/' beconn '/' srv_conn '/' retries
41
+ '(\d+)\/(\d+)\s' # srv_queue '/' backend_queue
42
+ )
29
43
 
30
44
  line.capture(:client_ip).as(:string)
31
- line.capture(:accept_date).as(:timestamp)
45
+ line.capture(:timestamp).as(:timestamp)
32
46
  line.capture(:frontend_name).as(:string)
33
47
  line.capture(:backend_name).as(:string)
34
48
  line.capture(:server_name).as(:string)
@@ -57,9 +71,89 @@ module RequestLogAnalyzer::FileFormat
57
71
  line.capture(:http_request).as(:nillable_string)
58
72
  end
59
73
 
74
+ # haproxy 1.2 has a few fields less than 1.3+
75
+ line_definition :haproxy12 do |line|
76
+ line.header = true
77
+ line.footer = true
78
+ line.teaser = /\.\d{3}\] \S+ \S+ / # .millisecs] frontend_name server_name
79
+
80
+ line.regexp = compose_regexp(
81
+ '\.\d{3}', # millisecs
82
+ '(\S+)\s', # server_name
83
+ '(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s', # Tq '/' Tw '/' Tc '/' Tr '/' Tt
84
+ '(\d+)\/(\d+)\/(\d+)\s', # srv_conn '/' listener_conn '/' process_conn
85
+ '(\d+)\/(\d+)\s' # srv_queue '/' backend_queue
86
+ )
87
+
88
+ line.capture(:client_ip).as(:string)
89
+ line.capture(:timestamp).as(:timestamp)
90
+ line.capture(:frontend_name).as(:string)
91
+ line.capture(:server_name).as(:string)
92
+ line.capture(:tq).as(:nillable_duration, :unit => :msec)
93
+ line.capture(:tw).as(:nillable_duration, :unit => :msec)
94
+ line.capture(:tc).as(:nillable_duration, :unit => :msec)
95
+ line.capture(:tr).as(:nillable_duration, :unit => :msec)
96
+ line.capture(:tt).as(:duration, :unit => :msec)
97
+ line.capture(:status_code).as(:integer)
98
+ line.capture(:bytes_read).as(:traffic, :unit => :byte)
99
+ line.capture(:captured_request_cookie).as(:nillable_string)
100
+ line.capture(:captured_response_cookie).as(:nillable_string)
101
+ line.capture(:termination_event_code).as(:nillable_string)
102
+ line.capture(:terminated_session_state).as(:nillable_string)
103
+ line.capture(:clientside_persistence_cookie).as(:nillable_string)
104
+ line.capture(:serverside_persistence_cookie).as(:nillable_string)
105
+ line.capture(:srv_conn).as(:integer)
106
+ line.capture(:listener_conn).as(:integer)
107
+ line.capture(:process_conn).as(:integer)
108
+ line.capture(:srv_queue).as(:integer)
109
+ line.capture(:backend_queue).as(:integer)
110
+ line.capture(:captured_request_headers).as(:nillable_string)
111
+ line.capture(:captured_response_headers).as(:nillable_string)
112
+ line.capture(:http_request).as(:nillable_string)
113
+ end
114
+
115
+ # and haproxy 1.1 has even less fields
116
+ line_definition :haproxy11 do |line|
117
+ line.header = true
118
+ line.footer = true
119
+ line.teaser = /:\d{2}\] \S+ \S+ / # :secs] frontend_name server_name
120
+
121
+ line.regexp = compose_regexp(
122
+ '', # no millisec precision in this version of haproxy
123
+ '(\S+)\s', # server_name
124
+ '(\d+|-1)\/(\d+|-1)\/(\d+|-1)\/\+?(\d+)\s', # Tq '/' Tc '/' Tr '/' Tt
125
+ '(\d+)\/(\d+)\s', # listener_conn '/' process_conn
126
+ '' # no queues in this version of haproxy
127
+ )
128
+
129
+ line.capture(:client_ip).as(:string)
130
+ line.capture(:timestamp).as(:timestamp)
131
+ line.capture(:frontend_name).as(:string)
132
+ line.capture(:server_name).as(:string)
133
+ line.capture(:tq).as(:nillable_duration, :unit => :msec)
134
+ line.capture(:tc).as(:nillable_duration, :unit => :msec)
135
+ line.capture(:tr).as(:nillable_duration, :unit => :msec)
136
+ line.capture(:tt).as(:duration, :unit => :msec)
137
+ line.capture(:status_code).as(:integer)
138
+ line.capture(:bytes_read).as(:traffic, :unit => :byte)
139
+ line.capture(:captured_request_cookie).as(:nillable_string)
140
+ line.capture(:captured_response_cookie).as(:nillable_string)
141
+ line.capture(:termination_event_code).as(:nillable_string)
142
+ line.capture(:terminated_session_state).as(:nillable_string)
143
+ line.capture(:clientside_persistence_cookie).as(:nillable_string)
144
+ line.capture(:serverside_persistence_cookie).as(:nillable_string)
145
+ line.capture(:listener_conn).as(:integer)
146
+ line.capture(:process_conn).as(:integer)
147
+ line.capture(:srv_queue).as(:integer)
148
+ line.capture(:backend_queue).as(:integer)
149
+ line.capture(:captured_request_headers).as(:nillable_string)
150
+ line.capture(:captured_response_headers).as(:nillable_string)
151
+ line.capture(:http_request).as(:nillable_string)
152
+ end
153
+
60
154
  # Define the summary report
61
155
  report do |analyze|
62
- analyze.hourly_spread :field => :accept_date
156
+ analyze.hourly_spread :field => :timestamp
63
157
 
64
158
  analyze.frequency :client_ip,
65
159
  :title => "Hits per IP"
@@ -15,7 +15,7 @@ module RequestLogAnalyzer::FileFormat
15
15
  line_definition :user_host do |line|
16
16
  line.header = :alternative
17
17
  line.teaser = /\# User\@Host\: /
18
- line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ ([\w\.-]*) \[(#{ip_address(true)})\]/
18
+ line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ (#{hostname(true)}) \[(#{ip_address(true)})\]/
19
19
 
20
20
  line.capture(:user)
21
21
  line.capture(:host)
@@ -34,18 +34,18 @@ module RequestLogAnalyzer::FileFormat
34
34
  end
35
35
 
36
36
  line_definition :use_database do |line|
37
- line.regexp = /^use (\w+);\s*$/
37
+ line.regexp = /^\s*use (\w+);\s*$/
38
38
  line.capture(:database)
39
39
  end
40
40
 
41
41
  line_definition :query_part do |line|
42
- line.regexp = /^(?!(?:use |\# |SET ))(.*[^;\s])\s*$/
42
+ line.regexp = /^\s*(?!(?:use |\# |SET ))(.*[^;\s])\s*$/
43
43
  line.capture(:query_fragment)
44
44
  end
45
45
 
46
46
  line_definition :query do |line|
47
47
  line.footer = true
48
- line.regexp = /^(?!(?:use |\# |SET ))(.*);\s*$/
48
+ line.regexp = /^(?!\s*(?:use |\# |SET ))(.*);\s*$/
49
49
  line.capture(:query).as(:sql)
50
50
  end
51
51
 
@@ -84,7 +84,7 @@ module RequestLogAnalyzer::FileFormat
84
84
  sql.gsub!(/(:int,)+:int/, ':ints') # replace multiple ints by a list
85
85
  sql.gsub!(/(:string,)+:string/, ':strings') # replace multiple strings by a list
86
86
 
87
- return sql.rstrip
87
+ return sql.strip
88
88
  end
89
89
 
90
90
  def host
@@ -23,12 +23,19 @@ module RequestLogAnalyzer::FileFormat
23
23
  # Processing by QueriesController#index as HTML
24
24
  line_definition :processing do |line|
25
25
  line.teaser = /Processing by /
26
- line.regexp = /Processing by ([A-Za-z0-9\-:]+)\#(\w+) as ([\w\/\*]+)/
26
+ line.regexp = /Processing by ([A-Za-z0-9\-:]+)\#(\w+) as ([\w\/\*]*)/
27
27
 
28
28
  line.capture(:controller)
29
29
  line.capture(:action)
30
30
  line.capture(:format)
31
31
  end
32
+
33
+ # Parameters: {"action"=>"cached", "controller"=>"cached"}
34
+ line_definition :parameters do |line|
35
+ line.teaser = / Parameters:/
36
+ line.regexp = / Parameters:\s+(\{.*\})/
37
+ line.capture(:params).as(:eval)
38
+ end
32
39
 
33
40
  # Completed 200 OK in 224ms (Views: 200.2ms | ActiveRecord: 3.4ms)
34
41
  # Completed 302 Found in 23ms
@@ -34,7 +34,7 @@ module RequestLogAnalyzer::FileFormat
34
34
 
35
35
  elsif file_format.kind_of?(String) && File.exist?(file_format) && File.file?(file_format)
36
36
  # load a format from a ruby file
37
- require file_format
37
+ require File.expand_path(file_format)
38
38
 
39
39
  const = RequestLogAnalyzer.to_camelcase(File.basename(file_format, '.rb'))
40
40
  if RequestLogAnalyzer::FileFormat.const_defined?(const)
@@ -121,6 +121,18 @@ module RequestLogAnalyzer::FileFormat
121
121
  'Z' => '(?:[+-]\d{4}|[A-Z]{3,4})',
122
122
  '%' => '%'
123
123
  }
124
+
125
+ # Creates a regular expression to match a hostname
126
+ def hostname(blank = false)
127
+ regexp = /(?:(?:[a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*(?:[A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])/
128
+ add_blank_option(regexp, blank)
129
+ end
130
+
131
+ # Creates a regular expression to match a hostname or ip address
132
+ def hostname_or_ip_address(blank = false)
133
+ regexp = Regexp.union(hostname, ip_address)
134
+ add_blank_option(regexp, blank)
135
+ end
124
136
 
125
137
  # Create a regular expression for a timestamp, generated by a strftime call.
126
138
  # Provide the format string to construct a matching regular expression.
@@ -139,12 +151,7 @@ module RequestLogAnalyzer::FileFormat
139
151
  end
140
152
  end
141
153
 
142
- regexp = Regexp.new(regexp)
143
- return case blank
144
- when String then Regexp.union(regexp, Regexp.new(Regexp.quote(blank)))
145
- when true then Regexp.union(regexp, //)
146
- else regexp
147
- end
154
+ add_blank_option(Regexp.new(regexp), blank)
148
155
  end
149
156
 
150
157
  # Construct a regular expression to parse IPv4 and IPv6 addresses.
@@ -162,12 +169,22 @@ module RequestLogAnalyzer::FileFormat
162
169
  ipv6_regex_compressed_hex_4_dec = /(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::(?:(?:[0-9A-Fa-f]{1,4}:)*)#{ipv4_regexp}/
163
170
  ipv6_regexp = Regexp.union(ipv6_regex_8_hex, ipv6_regex_compressed_hex, ipv6_regex_6_hex_4_dec, ipv6_regex_compressed_hex_4_dec)
164
171
 
165
- # Allow the field to be blank if this option is given. This can be true to
166
- # allow an empty string or a string alternative for the nil value.
167
- ip_regexp = case blank
168
- when String then Regexp.union(ipv4_regexp, ipv6_regexp, Regexp.new(Regexp.quote(blank)))
169
- when true then Regexp.union(ipv4_regexp, ipv6_regexp, //)
170
- else Regexp.union(ipv4_regexp, ipv6_regexp)
172
+ add_blank_option(Regexp.union(ipv4_regexp, ipv6_regexp), blank)
173
+ end
174
+
175
+ def anchored(regexp)
176
+ /^#{regexp}$/
177
+ end
178
+
179
+ protected
180
+
181
+ # Allow the field to be blank if this option is given. This can be true to
182
+ # allow an empty string or a string alternative for the nil value.
183
+ def add_blank_option(regexp, blank)
184
+ case blank
185
+ when String; Regexp.union(regexp, Regexp.new(Regexp.quote(blank)))
186
+ when true; Regexp.union(regexp, //)
187
+ else regexp
171
188
  end
172
189
  end
173
190
  end
@@ -260,9 +277,12 @@ module RequestLogAnalyzer::FileFormat
260
277
  end
261
278
 
262
279
  # Checks whether the file format is valid so it can be safely used with RLA.
263
- def valid?
280
+ def well_formed?
264
281
  valid_line_definitions? && valid_request_class?
265
282
  end
283
+
284
+ alias_method :valid?, :well_formed?
285
+
266
286
 
267
287
  # Checks whether the line definitions form a valid language.
268
288
  # A file format should have at least a header and a footer line type
@@ -294,5 +314,14 @@ module RequestLogAnalyzer::FileFormat
294
314
 
295
315
  return nil
296
316
  end
317
+
318
+ # Returns the max line length for this file format if any.
319
+ def max_line_length
320
+ self.class.const_get(MAX_LINE_LENGTH) if self.class.const_defined?(:MAX_LINE_LENGTH)
321
+ end
322
+
323
+ def line_divider
324
+ self.class.const_get(LINE_DIVIDER) if self.class.const_defined?(:LINE_DIVIDER)
325
+ end
297
326
  end
298
327
  end
@@ -1,9 +1,5 @@
1
1
  module RequestLogAnalyzer::Filter
2
2
 
3
- autoload :Field, 'request_log_analyzer/filter/field'
4
- autoload :Timespan, 'request_log_analyzer/filter/timespan'
5
- autoload :Anonymize, 'request_log_analyzer/filter/anonymize'
6
-
7
3
  # Base filter class used to filter input requests.
8
4
  # All filters should interit from this base.
9
5
  class Base
@@ -24,5 +20,8 @@ module RequestLogAnalyzer::Filter
24
20
  request
25
21
  end
26
22
  end
23
+ end
27
24
 
28
- end
25
+ require 'request_log_analyzer/filter/field'
26
+ require 'request_log_analyzer/filter/timespan'
27
+ require 'request_log_analyzer/filter/anonymize'
@@ -70,6 +70,10 @@ module RequestLogAnalyzer
70
70
  captures << new_capture_hash
71
71
  CaptureDefiner.new(new_capture_hash)
72
72
  end
73
+
74
+ def all_captured_variables
75
+ captures.map { |c| c[:name] } + captures.map { |c| c[:provides] }.compact.map { |pr| pr.keys }.flatten
76
+ end
73
77
 
74
78
  # Checks whether a given line matches this definition.
75
79
  # It will return false if a line does not match. If the line matches, a hash is returned
@@ -128,9 +132,7 @@ module RequestLogAnalyzer
128
132
 
129
133
  # Returns true if this line captures values of the given name
130
134
  def captures?(name)
131
- captures.any? { |c| c[:name] == name }
135
+ all_captured_variables.include?(name)
132
136
  end
133
-
134
137
  end
135
-
136
- end
138
+ end
@@ -1,10 +1,6 @@
1
1
  # Module for generating output
2
2
  module RequestLogAnalyzer::Output
3
3
 
4
- autoload :FixedWidth, 'request_log_analyzer/output/fixed_width'
5
- autoload :HTML, 'request_log_analyzer/output/html'
6
- autoload :FancyHTML, 'request_log_analyzer/output/fancy_html'
7
-
8
4
  # Loads a Output::Base subclass instance.
9
5
  def self.load(file_format, *args)
10
6
 
@@ -111,6 +107,8 @@ module RequestLogAnalyzer::Output
111
107
  def table_has_header?(columns)
112
108
  columns.any? { |column| !column[:title].nil? }
113
109
  end
114
-
115
110
  end
116
111
  end
112
+
113
+ require 'request_log_analyzer/output/fixed_width'
114
+ require 'request_log_analyzer/output/html'
@@ -13,6 +13,11 @@ module RequestLogAnalyzer::Source
13
13
 
14
14
  include Enumerable
15
15
 
16
+ # The maximum number of bytes to read from a line.
17
+ DEFAULT_MAX_LINE_LENGTH = 8096
18
+
19
+ DEFAULT_LINE_DIVIDER = "\n"
20
+
16
21
  # The default parse strategy that will be used to parse the input.
17
22
  DEFAULT_PARSE_STRATEGY = 'assume-correct'
18
23
 
@@ -48,6 +53,14 @@ module RequestLogAnalyzer::Source
48
53
  end
49
54
  end
50
55
 
56
+ def max_line_length
57
+ file_format.max_line_length || DEFAULT_MAX_LINE_LENGTH
58
+ end
59
+
60
+ def line_divider
61
+ file_format.line_divider || DEFAULT_LINE_DIVIDER
62
+ end
63
+
51
64
  # Reads the input, which can either be a file, sequence of files or STDIN to parse
52
65
  # lines specified in the FileFormat. This lines will be combined into Request instances,
53
66
  # that will be yielded. The actual parsing occurs in the parse_io method.
@@ -132,6 +145,13 @@ module RequestLogAnalyzer::Source
132
145
  def parse_stream(stream, options = {}, &block)
133
146
  parse_io(stream, options, &block)
134
147
  end
148
+
149
+ # Parses a string. It will simply call parse_io. This function does not support progress updates.
150
+ # <tt>string</tt>:: The string that should be parsed.
151
+ # <tt>options</tt>:: A Hash of options that will be pased to parse_io.
152
+ def parse_string(string, options = {}, &block)
153
+ parse_io(StringIO.new(string), options, &block)
154
+ end
135
155
 
136
156
  # This method loops over each line of the input stream. It will try to parse this line as any of
137
157
  # the lines that are defined by the current file format (see RequestLogAnalyazer::FileFormat).
@@ -143,11 +163,15 @@ module RequestLogAnalyzer::Source
143
163
  # - The method will yield progress updates if a progress handler is installed using progress=
144
164
  # - The method will yield parse warnings if a warning handler is installed using warning=
145
165
  #
166
+ # This is a Ruby 1.9 specific version that offers memory protection.
167
+ #
146
168
  # <tt>io</tt>:: The IO instance to use as source
147
169
  # <tt>options</tt>:: A hash of options that can be used by the parser.
148
- def parse_io(io, options = {}, &block) # :yields: request
149
- @current_lineno = 0
150
- while line = io.gets
170
+ def parse_io_19(io, options = {}, &block) # :yields: request
171
+ @max_line_length = options[:max_line_length] || max_line_length
172
+ @line_divider = options[:line_divider] || line_divider
173
+ @current_lineno = 0
174
+ while line = io.gets(@line_divider, @max_line_length)
151
175
  @current_lineno += 1
152
176
  @progress_handler.call(:progress, io.pos) if @progress_handler && @current_lineno % 255 == 0
153
177
  parse_line(line, &block)
@@ -157,6 +181,35 @@ module RequestLogAnalyzer::Source
157
181
  @current_lineno = nil
158
182
  end
159
183
 
184
+ # This method loops over each line of the input stream. It will try to parse this line as any of
185
+ # the lines that are defined by the current file format (see RequestLogAnalyazer::FileFormat).
186
+ # It will then combine these parsed line into requests using heuristics. These requests (see
187
+ # RequestLogAnalyzer::Request) will then be yielded for further processing in the pipeline.
188
+ #
189
+ # - RequestLogAnalyzer::LineDefinition#matches is called to test if a line matches a line definition of the file format.
190
+ # - update_current_request is used to combine parsed lines into requests using heuristics.
191
+ # - The method will yield progress updates if a progress handler is installed using progress=
192
+ # - The method will yield parse warnings if a warning handler is installed using warning=
193
+ #
194
+ # This is a Ruby 1.8 specific version that doesn't offer memory protection.
195
+ #
196
+ # <tt>io</tt>:: The IO instance to use as source
197
+ # <tt>options</tt>:: A hash of options that can be used by the parser.
198
+ def parse_io_18(io, options = {}, &block) # :yields: request
199
+ @line_divider = options[:line_divider] || line_divider
200
+ @current_lineno = 0
201
+ while line = io.gets(@line_divider)
202
+ @current_lineno += 1
203
+ @progress_handler.call(:progress, io.pos) if @progress_handler && @current_lineno % 255 == 0
204
+ parse_line(line, &block)
205
+ end
206
+
207
+ warn(:unfinished_request_on_eof, "End of file reached, but last request was not completed!") unless @current_request.nil?
208
+ @current_lineno = nil
209
+ end
210
+
211
+ alias_method :parse_io, RUBY_VERSION.to_f < 1.9 ? :parse_io_18 : :parse_io_19
212
+
160
213
  # Parses a single line using the current file format. If successful, use the parsed
161
214
  # information to build a request
162
215
  # <tt>line</tt>:: The line to parse
@@ -295,5 +348,4 @@ module RequestLogAnalyzer::Source
295
348
  hash[:line_definition].footer
296
349
  end
297
350
  end
298
-
299
351
  end
@@ -8,9 +8,6 @@
8
8
  # - Currently, RequestLogAnalyzer::Source::LogParser is the only implemented source.
9
9
  module RequestLogAnalyzer::Source
10
10
 
11
- autoload :LogParser, 'request_log_analyzer/source/log_parser'
12
- autoload :DatabaseLoader, 'request_log_analyzer/source/database_loader'
13
-
14
11
  # The base Source class. All other sources should inherit from this class.
15
12
  #
16
13
  # A source implememtation should at least implement the each_request method, which should yield
@@ -65,4 +62,6 @@ module RequestLogAnalyzer::Source
65
62
  end
66
63
 
67
64
  end
68
- end
65
+ end
66
+
67
+ require 'request_log_analyzer/source/log_parser'