request-log-analyzer 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/lib/cli/database_console_init.rb +2 -1
  2. data/lib/request_log_analyzer/aggregator/database_inserter.rb +10 -8
  3. data/lib/request_log_analyzer/aggregator.rb +1 -5
  4. data/lib/request_log_analyzer/controller.rb +10 -21
  5. data/lib/request_log_analyzer/database/base.rb +7 -6
  6. data/lib/request_log_analyzer/database/request.rb +22 -0
  7. data/lib/request_log_analyzer/database/source.rb +13 -0
  8. data/lib/request_log_analyzer/database/warning.rb +14 -0
  9. data/lib/request_log_analyzer/database.rb +16 -91
  10. data/lib/request_log_analyzer/file_format/amazon_s3.rb +6 -6
  11. data/lib/request_log_analyzer/file_format/apache.rb +21 -15
  12. data/lib/request_log_analyzer/file_format/merb.rb +21 -5
  13. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  14. data/lib/request_log_analyzer/file_format/rails.rb +8 -14
  15. data/lib/request_log_analyzer/file_format.rb +1 -13
  16. data/lib/request_log_analyzer/filter/anonymize.rb +2 -1
  17. data/lib/request_log_analyzer/filter.rb +6 -10
  18. data/lib/request_log_analyzer/log_processor.rb +6 -8
  19. data/lib/request_log_analyzer/output/fixed_width.rb +8 -7
  20. data/lib/request_log_analyzer/request.rb +61 -37
  21. data/lib/request_log_analyzer/source/database_loader.rb +3 -7
  22. data/lib/request_log_analyzer/source/log_parser.rb +3 -6
  23. data/lib/request_log_analyzer/source.rb +4 -6
  24. data/lib/request_log_analyzer/tracker/duration.rb +3 -3
  25. data/lib/request_log_analyzer/tracker/hourly_spread.rb +1 -2
  26. data/lib/request_log_analyzer/tracker/traffic.rb +186 -0
  27. data/lib/request_log_analyzer/tracker.rb +12 -19
  28. data/lib/request_log_analyzer.rb +3 -3
  29. data/request-log-analyzer.gemspec +4 -4
  30. data/spec/database.yml +6 -0
  31. data/spec/lib/mocks.rb +5 -2
  32. data/spec/unit/aggregator/database_inserter_spec.rb +3 -3
  33. data/spec/unit/database/base_class_spec.rb +9 -16
  34. data/spec/unit/database/database_spec.rb +9 -14
  35. data/spec/unit/file_format/apache_format_spec.rb +38 -1
  36. data/spec/unit/tracker/tracker_api_spec.rb +111 -36
  37. data/spec/unit/tracker/traffic_tracker_spec.rb +105 -0
  38. metadata +13 -6
@@ -3,6 +3,7 @@ $:.unshift(File.dirname(__FILE__) + '/..')
3
3
 
4
4
  $database = RequestLogAnalyzer::Database.new(ENV['RLA_DBCONSOLE_DATABASE'])
5
5
  $database.load_database_schema!
6
+ $database.register_default_orm_classes!
6
7
 
7
8
  require 'cli/tools'
8
9
 
@@ -39,4 +40,4 @@ end
39
40
  puts "request-log-analyzer database console"
40
41
  puts "-------------------------------------"
41
42
  puts "The following ActiveRecord classes are available:"
42
- puts $database.orm_classes.join(", ")
43
+ puts $database.orm_classes.map { |k| k.name.split('::').last }.join(", ")
@@ -31,7 +31,7 @@ module RequestLogAnalyzer::Aggregator
31
31
  # This will create a record in the requests table and create a record for every line that has been parsed,
32
32
  # in which the captured values will be stored.
33
33
  def aggregate(request)
34
- @request_object = database.request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
34
+ @request_object = RequestLogAnalyzer::Database::Request.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
35
35
  request.lines.each do |line|
36
36
  class_columns = database.get_class(line[:line_type]).column_names.reject { |column| ['id', 'source_id', 'request_id'].include?(column) }
37
37
  attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
@@ -45,23 +45,25 @@ module RequestLogAnalyzer::Aggregator
45
45
 
46
46
  # Finalizes the aggregator by closing the connection to the database
47
47
  def finalize
48
- @request_count = database.request_class.count
48
+ @request_count = RequestLogAnalyzer::Database::Request.count
49
49
  database.disconnect
50
50
  database.remove_orm_classes!
51
51
  end
52
52
 
53
53
  # Records w warining in the warnings table.
54
54
  def warning(type, message, lineno)
55
- database.warning_class.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
55
+ RequestLogAnalyzer::Database::Warning.create!(:warning_type => type.to_s, :message => message, :lineno => lineno)
56
56
  end
57
57
 
58
58
  # Records source changes in the sources table
59
59
  def source_change(change, filename)
60
- case change
61
- when :started
62
- @sources[filename] = database.source_class.create!(:filename => filename)
63
- when :finished
64
- @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
60
+ if File.exist?(filename)
61
+ case change
62
+ when :started
63
+ @sources[filename] = RequestLogAnalyzer::Database::Source.create!(:filename => filename)
64
+ when :finished
65
+ @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
66
+ end
65
67
  end
66
68
  end
67
69
 
@@ -8,16 +8,12 @@ module RequestLogAnalyzer::Aggregator
8
8
  # every aggregator should comply (by simply subclassing this class).
9
9
  class Base
10
10
 
11
- include RequestLogAnalyzer::FileFormat::Awareness
12
-
13
- attr_reader :options
14
- attr_reader :source
11
+ attr_reader :options, :source
15
12
 
16
13
  # Intializes a new RequestLogAnalyzer::Aggregator::Base instance
17
14
  # It will include the specific file format module.
18
15
  def initialize(source, options = {})
19
16
  @source = source
20
- self.register_file_format(source.file_format)
21
17
  @options = options
22
18
  end
23
19
 
@@ -17,14 +17,7 @@ module RequestLogAnalyzer
17
17
  # from several logrotated log files.
18
18
  class Controller
19
19
 
20
- include RequestLogAnalyzer::FileFormat::Awareness
21
-
22
- attr_reader :aggregators
23
- attr_reader :filters
24
- attr_reader :log_parser
25
- attr_reader :source
26
- attr_reader :output
27
- attr_reader :options
20
+ attr_reader :source, :filters, :aggregators, :output, :options
28
21
 
29
22
  # Builds a RequestLogAnalyzer::Controller given parsed command line arguments
30
23
  # <tt>arguments<tt> A CommandLine::Arguments hash containing parsed commandline parameters.
@@ -35,8 +28,8 @@ module RequestLogAnalyzer
35
28
  # Database command line options
36
29
  options[:database] = arguments[:database] if arguments[:database]
37
30
  options[:reset_database] = arguments[:reset_database]
38
- options[:debug] = arguments[:debug]
39
- options[:dump] = arguments[:dump]
31
+ options[:debug] = arguments[:debug]
32
+ options[:dump] = arguments[:dump]
40
33
  options[:parse_strategy] = arguments[:parse_strategy]
41
34
  options[:no_progress] = arguments[:no_progress]
42
35
 
@@ -125,17 +118,13 @@ module RequestLogAnalyzer
125
118
  @filters = []
126
119
  @output = options[:output]
127
120
 
128
- # Requester format through RequestLogAnalyzer::FileFormat and construct the parser
129
- register_file_format(@source.file_format)
130
-
131
- # Pass all warnings to every aggregator so they can do something useful with them.
132
- @source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } } if @source
133
-
134
- # Handle progress messagess
135
- @source.progress = lambda { |message, value| handle_progress(message, value) } if @source && !options[:no_progress]
121
+ # Register the request format for this session after checking its validity
122
+ raise "Invalid file format!" unless @source.file_format.valid?
136
123
 
137
- # Handle source change messages
138
- @source.source_changes = lambda { |change, filename| handle_source_change(change, filename) } if @source
124
+ # Install event handlers for wrnings, progress updates and source changes
125
+ @source.warning = lambda { |type, message, lineno| @aggregators.each { |agg| agg.warning(type, message, lineno) } }
126
+ @source.progress = lambda { |message, value| handle_progress(message, value) } unless options[:no_progress]
127
+ @source.source_changes = lambda { |change, filename| handle_source_change(change, filename) }
139
128
  end
140
129
 
141
130
  # Progress function.
@@ -176,7 +165,7 @@ module RequestLogAnalyzer
176
165
  # Adds a request filter to the controller.
177
166
  def add_filter(filter, filter_options = {})
178
167
  filter = RequestLogAnalyzer::Filter.const_get(RequestLogAnalyzer::to_camelcase(filter)) if filter.kind_of?(Symbol)
179
- @filters << filter.new(file_format, @options.merge(filter_options))
168
+ @filters << filter.new(source.file_format, @options.merge(filter_options))
180
169
  end
181
170
 
182
171
  # Push a request through the entire filterchain (@filters).
@@ -13,8 +13,9 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
13
13
  def line_type
14
14
  self.class.name.underscore.gsub(/_line$/, '').to_sym
15
15
  end
16
-
17
- cattr_accessor :database, :line_definition
16
+
17
+ class_inheritable_accessor :line_definition
18
+ cattr_accessor :database
18
19
 
19
20
  def self.subclass_from_line_definition(definition)
20
21
  klass = Class.new(RequestLogAnalyzer::Database::Base)
@@ -31,8 +32,8 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
31
32
  klass.send(:serialize, capture[:name], Hash)
32
33
  end
33
34
 
34
- database.request_class.has_many "#{definition.name}_lines".to_sym
35
- database.source_class.has_many "#{definition.name}_lines".to_sym
35
+ RequestLogAnalyzer::Database::Request.has_many "#{definition.name}_lines".to_sym
36
+ RequestLogAnalyzer::Database::Source.has_many "#{definition.name}_lines".to_sym
36
37
 
37
38
  return klass
38
39
  end
@@ -45,12 +46,12 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
45
46
 
46
47
  if klass.column_names.include?('request_id')
47
48
  klass.belongs_to :request
48
- database.request_class.has_many table.to_sym
49
+ RequestLogAnalyzer::Database::Request.has_many table.to_sym
49
50
  end
50
51
 
51
52
  if klass.column_names.include?('source_id')
52
53
  klass.belongs_to :source
53
- database.source_class.has_many table.to_sym
54
+ RequestLogAnalyzer::Database::Source.has_many table.to_sym
54
55
  end
55
56
 
56
57
  return klass
@@ -0,0 +1,22 @@
1
+ class RequestLogAnalyzer::Database::Request < RequestLogAnalyzer::Database::Base
2
+
3
+ # Returns an array of all the Line objects of this request in the correct order.
4
+ def lines
5
+ @lines ||= begin
6
+ lines = []
7
+ self.class.reflections.each { |r, d| lines += self.send(r).all }
8
+ lines.sort
9
+ end
10
+ end
11
+
12
+ # Creates the table to store requests in.
13
+ def self.create_table!
14
+ unless database.connection.table_exists?(:requests)
15
+ database.connection.create_table(:requests) do |t|
16
+ t.column :first_lineno, :integer
17
+ t.column :last_lineno, :integer
18
+ end
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,13 @@
1
+ class RequestLogAnalyzer::Database::Source < RequestLogAnalyzer::Database::Base
2
+
3
+ def self.create_table!
4
+ unless database.connection.table_exists?(:sources)
5
+ database.connection.create_table(:sources) do |t|
6
+ t.column :filename, :string
7
+ t.column :mtime, :datetime
8
+ t.column :filesize, :integer
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,14 @@
1
+ class RequestLogAnalyzer::Database::Warning < RequestLogAnalyzer::Database::Base
2
+
3
+ def self.create_table!
4
+ unless database.connection.table_exists?(:warnings)
5
+ database.connection.create_table(:warnings) do |t|
6
+ t.column :warning_type, :string, :limit => 30, :null => false
7
+ t.column :message, :string
8
+ t.column :source_id, :integer
9
+ t.column :lineno, :integer
10
+ end
11
+ end
12
+ end
13
+
14
+ end
@@ -10,7 +10,7 @@ class RequestLogAnalyzer::Database
10
10
  include RequestLogAnalyzer::Database::Connection
11
11
 
12
12
  attr_accessor :file_format
13
- attr_reader :request_class, :warning_class, :source_class, :line_classes
13
+ attr_reader :line_classes
14
14
 
15
15
  def initialize(connection_identifier = nil)
16
16
  @line_classes = []
@@ -24,97 +24,17 @@ class RequestLogAnalyzer::Database
24
24
  Object.const_get("#{line_type}_line".camelize)
25
25
  end
26
26
 
27
- # Returns the Request ORM class for the current database.
28
- #
29
- # It will create the class if not previously done so. The class will
30
- # include a create_table! method the migrate the database.
31
- def request_class
32
- @request_class ||= begin
33
- klass = Class.new(RequestLogAnalyzer::Database::Base) do
34
-
35
- def lines
36
- @lines ||= begin
37
- lines = []
38
- self.class.reflections.each { |r, d| lines += self.send(r).all }
39
- lines.sort
40
- end
41
- end
42
-
43
- # Creates the requests table
44
- def self.create_table!
45
- unless database.connection.table_exists?(:requests)
46
- database.connection.create_table(:requests) do |t|
47
- t.column :first_lineno, :integer
48
- t.column :last_lineno, :integer
49
- end
50
- end
51
- end
52
- end
53
-
54
- Object.const_set('Request', klass)
55
- Object.const_get('Request')
56
- end
57
- end
58
-
59
- # Returns the Source ORM class for the current database.
60
- #
61
- # It will create the class if not previously done so. The class will
62
- # include a create_table! method the migrate the database.
63
- def source_class
64
- @source_class ||= begin
65
- klass = Class.new(RequestLogAnalyzer::Database::Base) do
66
-
67
- # Creates the sources table
68
- def self.create_table!
69
- unless database.connection.table_exists?(:sources)
70
- database.connection.create_table(:sources) do |t|
71
- t.column :filename, :string
72
- t.column :mtime, :datetime
73
- t.column :filesize, :integer
74
- end
75
- end
76
- end
77
- end
78
-
79
- Object.const_set('Source', klass)
80
- Object.const_get('Source')
81
- end
82
- end
83
-
84
-
85
- # Returns the Warning ORM class for the current database.
86
- #
87
- # It will create the class if not previously done so. The class will
88
- # include a create_table! method the migrate the database.
89
- def warning_class
90
- @warning_class ||= begin
91
- klass = Class.new(RequestLogAnalyzer::Database::Base) do
92
-
93
- # Creates the warnings table
94
- def self.create_table!
95
- unless database.connection.table_exists?(:warnings)
96
- database.connection.create_table(:warnings) do |t|
97
- t.column :warning_type, :string, :limit => 30, :null => false
98
- t.column :message, :string
99
- t.column :source_id, :integer
100
- t.column :lineno, :integer
101
- end
102
- end
103
- end
104
- end
105
-
106
- Object.const_set('Warning', klass)
107
- Object.const_get('Warning')
108
- end
27
+ def default_classes
28
+ [RequestLogAnalyzer::Database::Request, RequestLogAnalyzer::Database::Source, RequestLogAnalyzer::Database::Warning]
109
29
  end
110
30
 
111
31
  # Loads the ORM classes by inspecting the tables in the current database
112
32
  def load_database_schema!
113
33
  connection.tables.map do |table|
114
34
  case table.to_sym
115
- when :warnings then warning_class
116
- when :sources then source_class
117
- when :requests then request_class
35
+ when :warnings then RequestLogAnalyzer::Database::Warning
36
+ when :sources then RequestLogAnalyzer::Database::Source
37
+ when :requests then RequestLogAnalyzer::Database::Request
118
38
  else load_activerecord_class(table)
119
39
  end
120
40
  end
@@ -122,7 +42,7 @@ class RequestLogAnalyzer::Database
122
42
 
123
43
  # Returns an array of all the ActiveRecord-bases ORM classes for this database
124
44
  def orm_classes
125
- [warning_class, request_class, source_class] + line_classes
45
+ default_classes + line_classes
126
46
  end
127
47
 
128
48
  # Loads an ActiveRecord-based class that correspond to the given parameter, which can either be
@@ -146,8 +66,6 @@ class RequestLogAnalyzer::Database
146
66
 
147
67
  def fileformat_classes
148
68
  raise "No file_format provided!" unless file_format
149
-
150
- default_classes = [request_class, source_class, warning_class]
151
69
  line_classes = file_format.line_definitions.map { |(name, definition)| load_activerecord_class(definition) }
152
70
  return default_classes + line_classes
153
71
  end
@@ -164,12 +82,19 @@ class RequestLogAnalyzer::Database
164
82
  remove_orm_classes!
165
83
  end
166
84
 
85
+ # Registers the default ORM classes in the default namespace
86
+ def register_default_orm_classes!
87
+ Object.const_set('Request', RequestLogAnalyzer::Database::Request)
88
+ Object.const_set('Source', RequestLogAnalyzer::Database::Source)
89
+ Object.const_set('Warning', RequestLogAnalyzer::Database::Warning)
90
+ end
91
+
167
92
  # Unregisters every ORM class constant
168
93
  def remove_orm_classes!
169
94
  orm_classes.each do |klass|
170
95
  if klass.respond_to?(:name) && !klass.name.blank?
171
- # klass_base_name = klass.name.split('::').last
172
- Object.send(:remove_const, klass.name) if Object.const_defined?(klass.name)
96
+ klass_name = klass.name.split('::').last
97
+ Object.send(:remove_const, klass_name) if Object.const_defined?(klass_name)
173
98
  end
174
99
  end
175
100
  end
@@ -9,7 +9,7 @@ module RequestLogAnalyzer::FileFormat
9
9
  line_definition :access do |line|
10
10
  line.header = true
11
11
  line.footer = true
12
- line.regexp = /^([^\ ]+) ([^\ ]+) \[([^\]]{26})\] (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
12
+ line.regexp = /^([^\ ]+) ([^\ ]+) \[(\d{2}\/[A-Za-z]{3}\/\d{4}.\d{2}:\d{2}:\d{2})(?: .\d{4})?\] (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
13
13
  line.captures << { :name => :bucket_owner, :type => :string } <<
14
14
  { :name => :bucket, :type => :string } <<
15
15
  { :name => :timestamp, :type => :timestamp } <<
@@ -21,8 +21,8 @@ module RequestLogAnalyzer::FileFormat
21
21
  { :name => :request_uri, :type => :string } <<
22
22
  { :name => :http_status, :type => :integer } <<
23
23
  { :name => :error_code, :type => :nillable_string } <<
24
- { :name => :bytes_sent, :type => :integer } <<
25
- { :name => :object_size, :type => :integer } <<
24
+ { :name => :bytes_sent, :type => :traffic, :unit => :byte } <<
25
+ { :name => :object_size, :type => :traffic, :unit => :byte } <<
26
26
  { :name => :total_time, :type => :duration, :unit => :msec } <<
27
27
  { :name => :turnaround_time, :type => :duration, :unit => :msec } <<
28
28
  { :name => :referer, :type => :referer } <<
@@ -34,7 +34,8 @@ module RequestLogAnalyzer::FileFormat
34
34
  analyze.hourly_spread
35
35
 
36
36
  analyze.frequency :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Most popular items"
37
- analyze.duration :duration => :total_time, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Duration"
37
+ analyze.duration :duration => :total_time, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Request duration"
38
+ analyze.traffic :traffic => :bytes_sent, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Traffic"
38
39
  analyze.frequency :category => :http_status, :title => 'HTTP status codes'
39
40
  analyze.frequency :category => :error_code, :title => 'Error codes'
40
41
  end
@@ -47,8 +48,7 @@ module RequestLogAnalyzer::FileFormat
47
48
  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
48
49
  # to speed up parsing.
49
50
  def convert_timestamp(value, definition)
50
- d = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/.match(value).captures
51
- "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
51
+ "#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
52
52
  end
53
53
 
54
54
  # Make sure that the string '-' is parsed as a nil value.
@@ -17,26 +17,30 @@ module RequestLogAnalyzer::FileFormat
17
17
  # A hash of predefined Apache log formats
18
18
  LOG_FORMAT_DEFAULTS = {
19
19
  :common => '%h %l %u %t "%r" %>s %b',
20
- :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"'
20
+ :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"',
21
+ :rack => '%h %l %u %t "%r" %>s %b %T',
22
+ :referer => '%{Referer}i -> %U',
23
+ :agent => '%{User-agent}i'
21
24
  }
22
-
25
+
23
26
  # A hash that defines how the log format directives should be parsed.
24
27
  LOG_DIRECTIVES = {
25
28
  '%' => { :regexp => '%', :captures => [] },
26
29
  'h' => { :regexp => '([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)', :captures => [{:name => :remote_host, :type => :string}] },
27
30
  'a' => { :regexp => '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', :captures => [{:name => :remote_ip, :type => :string}] },
28
- 'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :integer}] },
31
+ 'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] },
29
32
  'c' => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] },
30
- 'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :msec}] },
33
+ 'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :musec}] },
31
34
  'l' => { :regexp => '([\w-]+)', :captures => [{:name => :remote_logname, :type => :nillable_string}] },
32
- 'T' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
33
- 't' => { :regexp => '\[([^\]]{26})\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
35
+ 'T' => { :regexp => '((?:\d+(?:\.\d+))|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
36
+ 't' => { :regexp => '\[(\d{2}\/[A-Za-z]{3}\/\d{4}.\d{2}:\d{2}:\d{2})(?: .\d{4})?\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
34
37
  's' => { :regexp => '(\d{3})', :captures => [{:name => :http_status, :type => :integer}] },
35
38
  'u' => { :regexp => '(\w+|-)', :captures => [{:name => :user, :type => :nillable_string}] },
36
- 'r' => { :regexp => '([A-Z]+) ([^\s]+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
39
+ 'U' => { :regexp => '(\/\S*)', :captures => [{:name => :path, :type => :string}] },
40
+ 'r' => { :regexp => '([A-Z]+) (\S+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
37
41
  {:name => :path, :type => :path}, {:name => :http_version, :type => :string}]},
38
- 'i' => { 'Referer' => { :regexp => '([^\s]+)', :captures => [{:name => :referer, :type => :nillable_string}] },
39
- 'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
42
+ 'i' => { 'Referer' => { :regexp => '(\S+)', :captures => [{:name => :referer, :type => :nillable_string}] },
43
+ 'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
40
44
  }
41
45
  }
42
46
 
@@ -88,14 +92,13 @@ module RequestLogAnalyzer::FileFormat
88
92
 
89
93
  analyze.frequency :category => :http_method, :amount => 20, :title => "HTTP methods" if line_definition.captures?(:http_method)
90
94
  analyze.frequency :category => :http_status, :amount => 20, :title => "HTTP statuses" if line_definition.captures?(:http_status)
91
- analyze.frequency :category => :path, :amount => 20, :title => "Most popular URIs" if line_definition.captures?(:path)
95
+ analyze.frequency :category => lambda { |r| r.category }, :amount => 20, :title => "Most popular URIs" if line_definition.captures?(:path)
92
96
 
93
97
  analyze.frequency :category => :user_agent, :amount => 20, :title => "User agents" if line_definition.captures?(:user_agent)
94
98
  analyze.frequency :category => :referer, :amount => 20, :title => "Referers" if line_definition.captures?(:referer)
95
99
 
96
- if line_definition.captures?(:path) && line_definition.captures?(:duration)
97
- analyze.duration :duration => :duration, :category => :path , :title => 'Request duration'
98
- end
100
+ analyze.duration :duration => :duration, :category => lambda { |r| r.category }, :title => 'Request duration' if line_definition.captures?(:duration)
101
+ analyze.traffic :traffic => :bytes_sent, :category => lambda { |r| r.category }, :title => 'Traffic' if line_definition.captures?(:bytes_sent)
99
102
 
100
103
  return analyze.trackers
101
104
  end
@@ -103,14 +106,17 @@ module RequestLogAnalyzer::FileFormat
103
106
  # Define a custom Request class for the Apache file format to speed up timestamp handling.
104
107
  class Request < RequestLogAnalyzer::Request
105
108
 
109
+ def category
110
+ first(:path)
111
+ end
112
+
106
113
  MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
107
114
  'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
108
115
 
109
116
  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
110
117
  # to speed up parsing.
111
118
  def convert_timestamp(value, definition)
112
- d = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/.match(value).captures
113
- "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
119
+ "#{value[7,4]}#{MONTHS[value[3,3]]}#{value[0,2]}#{value[12,2]}#{value[15,2]}#{value[18,2]}".to_i
114
120
  end
115
121
 
116
122
  # This function can be overridden to rewrite the path for better categorization in the
@@ -1,11 +1,14 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
2
 
3
+ # The Merb file format parses the request header with the timestamp, the params line
4
+ # with the most important request information and the durations line which contains
5
+ # the different request durations that can be used for analysis.
3
6
  class Merb < Base
4
7
 
5
8
  # ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
6
9
  line_definition :started do |line|
7
10
  line.header = true
8
- line.teaser = /Started/
11
+ # line.teaser = /Started/
9
12
  line.regexp = /Started request handling\:\ (.+)/
10
13
  line.captures << { :name => :timestamp, :type => :timestamp }
11
14
  end
@@ -13,7 +16,7 @@ module RequestLogAnalyzer::FileFormat
13
16
  # ~ Params: {"action"=>"create", "controller"=>"session"}
14
17
  # ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
15
18
  line_definition :params do |line|
16
- line.teaser = /Params/
19
+ # line.teaser = /Params/
17
20
  line.regexp = /Params\:\ (\{.+\})/
18
21
  line.captures << { :name => :params, :type => :eval, :provides => {
19
22
  :namespace => :string, :controller => :string, :action => :string, :format => :string, :method => :string } }
@@ -36,15 +39,28 @@ module RequestLogAnalyzer::FileFormat
36
39
  end
37
40
 
38
41
  report do |analyze|
39
- analyze.timespan :line_type => :started
42
+
43
+ analyze.timespan
44
+ analyze.hourly_spread
45
+
40
46
  analyze.frequency :category => REQUEST_CATEGORIZER, :amount => 20, :title => "Top 20 by hits"
41
- analyze.hourly_spread :line_type => :started
42
47
  analyze.duration :dispatch_time, :category => REQUEST_CATEGORIZER, :title => 'Request dispatch duration'
48
+
43
49
  # analyze.duration :action_time, :category => REQUEST_CATEGORIZER, :title => 'Request action duration'
44
50
  # analyze.duration :after_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request after_filter duration'
45
51
  # analyze.duration :before_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request before_filter duration'
46
52
  end
47
-
53
+
54
+ class Request < RequestLogAnalyzer::Request
55
+
56
+ MONTHS = {'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
57
+ 'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' }
58
+
59
+ # Speed up timestamp conversion
60
+ def convert_timestamp(value, definition)
61
+ "#{value[26,4]}#{MONTHS[value[4,3]]}#{value[8,2]}#{value[11,2]}#{value[14,2]}#{value[17,2]}".to_i
62
+ end
63
+ end
48
64
  end
49
65
 
50
66
  end
@@ -0,0 +1,11 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ class Rack < Apache
4
+
5
+ def self.create(*args)
6
+ super(:rack, *args)
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -5,11 +5,11 @@ module RequestLogAnalyzer::FileFormat
5
5
  # Processing EmployeeController#index (for 123.123.123.123 at 2008-07-13 06:00:00) [GET]
6
6
  line_definition :processing do |line|
7
7
  line.header = true # this line is the first log line for a request
8
- line.teaser = /Processing /
8
+ # line.teaser = /Processing /
9
9
  line.regexp = /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/
10
10
  line.captures << { :name => :controller, :type => :string } \
11
11
  << { :name => :action, :type => :string } \
12
- << { :name => :format, :type => :format } \
12
+ << { :name => :format, :type => :string, :default => 'html' } \
13
13
  << { :name => :ip, :type => :string } \
14
14
  << { :name => :timestamp, :type => :timestamp } \
15
15
  << { :name => :method, :type => :string }
@@ -46,7 +46,7 @@ module RequestLogAnalyzer::FileFormat
46
46
  line_definition :completed do |line|
47
47
 
48
48
  line.footer = true
49
- line.teaser = /Completed in /
49
+ # line.teaser = /Completed in /
50
50
  line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
51
51
 
52
52
  line.captures << { :name => :duration, :type => :duration, :unit => :sec } \
@@ -62,15 +62,15 @@ module RequestLogAnalyzer::FileFormat
62
62
  << { :name => :url, :type => :string } # 2.2 variant
63
63
  end
64
64
 
65
-
66
-
67
65
  REQUEST_CATEGORIZER = Proc.new do |request|
68
66
  "#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
69
67
  end
70
68
 
71
69
  report do |analyze|
72
- analyze.timespan :line_type => :processing
73
- analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
70
+ analyze.timespan
71
+ analyze.hourly_spread
72
+
73
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20
74
74
  analyze.frequency :method, :title => 'HTTP methods'
75
75
  analyze.frequency :status, :title => 'HTTP statuses returned'
76
76
  analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
@@ -82,23 +82,17 @@ module RequestLogAnalyzer::FileFormat
82
82
  analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
83
83
  :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
84
84
 
85
- analyze.hourly_spread :line_type => :processing
86
85
  analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
87
86
  end
88
87
 
89
88
  # Define a custom Request class for the Rails file format to speed up timestamp handling
90
89
  # and to ensure that a format is always set.
91
90
  class Request < RequestLogAnalyzer::Request
92
-
91
+
93
92
  # Do not use DateTime.parse
94
93
  def convert_timestamp(value, definition)
95
94
  value.gsub(/[^0-9]/, '')[0...14].to_i unless value.nil?
96
95
  end
97
-
98
- # Set 'html' as default format for a request
99
- def convert_format(value, definition)
100
- value || 'html'
101
- end
102
96
  end
103
97
 
104
98
  end