request-log-analyzer 1.1.1 → 1.1.2

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.
@@ -27,14 +27,6 @@ begin
27
27
  strip.switch(:keep_junk_lines, :j)
28
28
  end
29
29
 
30
- command_line.command(:anonymize) do |anonymize|
31
- anonymize.minimum_parameters = 1
32
- anonymize.option(:format, :alias => :f, :default => 'rails')
33
- anonymize.option(:output, :alias => :o)
34
- anonymize.switch(:discard_teaser_lines, :t)
35
- anonymize.switch(:keep_junk_lines, :j)
36
- end
37
-
38
30
  command_line.option(:format, :alias => :f, :default => 'rails')
39
31
  command_line.option(:file, :alias => :e)
40
32
  command_line.switch(:assume_correct_order)
@@ -111,9 +103,6 @@ when :install
111
103
  when :strip
112
104
  require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
113
105
  RequestLogAnalyzer::LogProcessor.build(:strip, arguments).run!
114
- when :anonymize
115
- require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
116
- RequestLogAnalyzer::LogProcessor.build(:anonymize, arguments).run!
117
106
  else
118
107
  puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
119
108
  puts "Website: http://github.com/wvanbergen/request-log-analyzer"
@@ -12,15 +12,19 @@ module RequestLogAnalyzer::Aggregator
12
12
  @trackers = []
13
13
  end
14
14
 
15
+ def reset!
16
+ @trackers = []
17
+ end
18
+
15
19
  def method_missing(tracker_method, *args)
16
20
  track(tracker_method, args.first)
17
21
  end
18
22
 
19
- def category(category_field, options = {})
23
+ def frequency(category_field, options = {})
20
24
  if category_field.kind_of?(Symbol)
21
- track(:category, options.merge(:category => category_field))
25
+ track(:frequency, options.merge(:category => category_field))
22
26
  elsif category_field.kind_of?(Hash)
23
- track(:category, category_field.merge(options))
27
+ track(:frequency, category_field.merge(options))
24
28
  end
25
29
  end
26
30
 
@@ -131,7 +131,7 @@ module RequestLogAnalyzer
131
131
  def handle_progress(message, value = nil)
132
132
  case message
133
133
  when :started
134
- @progress_bar = CommandLine::ProgressBar.new(File.basename(value), File.size(value))
134
+ @progress_bar = CommandLine::ProgressBar.new(File.basename(value), File.size(value), STDOUT)
135
135
  when :finished
136
136
  @progress_bar.finish
137
137
  @progress_bar = nil
@@ -160,6 +160,18 @@ module RequestLogAnalyzer
160
160
  @filters << filter.new(file_format, @options.merge(filter_options))
161
161
  end
162
162
 
163
+ def filter_request(request)
164
+ @filters.each do |filter|
165
+ request = filter.filter(request)
166
+ return nil if request.nil?
167
+ end
168
+ return request
169
+ end
170
+
171
+ def aggregate_request(request)
172
+ @aggregators.each { |agg| agg.aggregate(request) }
173
+ end
174
+
163
175
  # Runs RequestLogAnalyzer
164
176
  # 1. Call prepare on every aggregator
165
177
  # 2. Generate requests from source object
@@ -175,8 +187,8 @@ module RequestLogAnalyzer
175
187
 
176
188
  begin
177
189
  @source.each_request do |request|
178
- @filters.each { |filter| request = filter.filter(request) }
179
- @aggregators.each { |agg| agg.aggregate(request) } if request
190
+ request = filter_request(request)
191
+ aggregate_request(request) unless request.nil?
180
192
  end
181
193
  rescue Interrupt => e
182
194
  handle_progress(:interrupted)
@@ -1,6 +1,5 @@
1
1
  module RequestLogAnalyzer::FileFormat
2
2
 
3
-
4
3
  def self.const_missing(const)
5
4
  RequestLogAnalyzer::load_default_class_file(self, const)
6
5
  end
@@ -24,14 +23,24 @@ module RequestLogAnalyzer::FileFormat
24
23
  elsif file_format.kind_of?(String) && File.exist?(file_format)
25
24
  # load a format from a ruby file
26
25
  require file_format
27
- klass = Object.const_get(RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb')))
26
+ const = RequestLogAnalyzer::to_camelcase(File.basename(file_format, '.rb'))
27
+ if RequestLogAnalyzer::FileFormat.const_defined?(const)
28
+ klass = RequestLogAnalyzer::FileFormat.const_get(const)
29
+ elsif Object.const_defined?(const)
30
+ klass = Object.const_get(const)
31
+ else
32
+ raise "Cannot load class #{const} from #{file_format}!"
33
+ end
28
34
 
29
35
  else
30
36
  # load a provided file format
31
37
  klass = RequestLogAnalyzer::FileFormat.const_get(RequestLogAnalyzer::to_camelcase(file_format))
32
38
  end
33
39
 
34
- raise if klass.nil?
40
+ # check the returned klass to see if it can be used
41
+ raise "Could not load a file format from #{file_format.inspect}" if klass.nil?
42
+ raise "Invalid FileFormat class" unless klass.kind_of?(Class) && klass.ancestors.include?(RequestLogAnalyzer::FileFormat::Base)
43
+
35
44
  @current_file_format = klass.new # return an instance of the class
36
45
  end
37
46
 
@@ -56,13 +65,29 @@ module RequestLogAnalyzer::FileFormat
56
65
 
57
66
  # Registers the line definer instance for a subclass.
58
67
  def self.inherited(subclass)
59
- subclass.instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
60
- subclass.class_eval { class << self; attr_accessor :line_definer; end }
61
- subclass.class_eval { class << self; attr_accessor :report_definer; end }
62
-
63
- unless subclass.const_defined?('Request')
64
- subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request))
65
- end
68
+ if subclass.superclass == RequestLogAnalyzer::FileFormat::Base
69
+
70
+ # Create aline and report definer for this class
71
+ subclass.class_eval do
72
+ instance_variable_set(:@line_definer, RequestLogAnalyzer::LineDefinition::Definer.new)
73
+ instance_variable_set(:@report_definer, RequestLogAnalyzer::Aggregator::Summarizer::Definer.new)
74
+ class << self; attr_accessor :line_definer, :report_definer; end
75
+ end
76
+
77
+ # Create a custom Request class for this file format
78
+ subclass.const_set('Request', Class.new(RequestLogAnalyzer::Request)) unless subclass.const_defined?('Request')
79
+ else
80
+
81
+ # Copy the line and report definer from the parent class.
82
+ subclass.class_eval do
83
+ instance_variable_set(:@line_definer, superclass.line_definer)
84
+ instance_variable_set(:@report_definer, superclass.report_definer)
85
+ class << self; attr_accessor :line_definer, :report_definer; end
86
+ end
87
+
88
+ # Create a custom Request class based on the superclass's Request class
89
+ subclass.const_set('Request', Class.new(subclass.superclass::Request)) unless subclass.const_defined?('Request')
90
+ end
66
91
  end
67
92
 
68
93
  # Specifies a single line defintions.
@@ -77,16 +102,19 @@ module RequestLogAnalyzer::FileFormat
77
102
  # Specifies multiple line definitions at once using a block
78
103
  def self.format_definition(&block)
79
104
  if block_given?
80
- yield(@line_definer)
105
+ yield self.line_definer
81
106
  else
82
- return @line_definer
107
+ return self.line_definer
83
108
  end
84
109
  end
85
110
 
86
111
  # Specifies the summary report using a block.
87
- def self.report(&block)
88
- @report_definer = RequestLogAnalyzer::Aggregator::Summarizer::Definer.new
89
- yield(@report_definer)
112
+ def self.report(mode = :append, &block)
113
+ if mode == :overwrite
114
+ self.report_definer.reset!
115
+ end
116
+
117
+ yield(self.report_definer)
90
118
  end
91
119
 
92
120
  # Returns all line definitions
@@ -96,7 +124,7 @@ module RequestLogAnalyzer::FileFormat
96
124
 
97
125
  # Returns all the defined trackers for the summary report.
98
126
  def report_trackers
99
- self.class.instance_variable_get(:@report_definer).trackers rescue []
127
+ self.class.report_definer.trackers# => rescue []
100
128
  end
101
129
 
102
130
  # Checks whether the line definitions form a valid language.
@@ -7,7 +7,7 @@ module RequestLogAnalyzer::FileFormat
7
7
  line.header = true
8
8
  line.teaser = /Started/
9
9
  line.regexp = /Started request handling\:\ (.+)/
10
- line.captures << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly }
10
+ line.captures << { :name => :timestamp, :type => :timestamp }
11
11
  end
12
12
 
13
13
  # ~ Params: {"action"=>"create", "controller"=>"session"}
@@ -23,10 +23,10 @@ module RequestLogAnalyzer::FileFormat
23
23
  line.footer = true
24
24
  line.teaser = /\{:dispatch_time/
25
25
  line.regexp = /\{\:dispatch_time=>(\d+\.\d+(?:e-?\d+)?), (?:\:after_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?(?:\:before_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?\:action_time=>(\d+\.\d+(?:e-?\d+)?)\}/
26
- line.captures << { :name => :dispatch_time, :type => :sec, :anonymize => :slightly } \
27
- << { :name => :after_filters_time, :type => :sec, :anonymize => :slightly } \
28
- << { :name => :before_filters_time, :type => :sec, :anonymize => :slightly } \
29
- << { :name => :action_time, :type => :sec, :anonymize => :slightly }
26
+ line.captures << { :name => :dispatch_time, :type => :duration } \
27
+ << { :name => :after_filters_time, :type => :duration } \
28
+ << { :name => :before_filters_time, :type => :duration } \
29
+ << { :name => :action_time, :type => :duration }
30
30
  end
31
31
 
32
32
 
@@ -9,9 +9,9 @@ module RequestLogAnalyzer::FileFormat
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 => :string } \
13
- << { :name => :ip, :type => :string, :anonymize => :ip } \
14
- << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
12
+ << { :name => :format, :type => :format } \
13
+ << { :name => :ip, :type => :string } \
14
+ << { :name => :timestamp, :type => :timestamp } \
15
15
  << { :name => :method, :type => :string }
16
16
  end
17
17
 
@@ -28,7 +28,7 @@ module RequestLogAnalyzer::FileFormat
28
28
  << { :name => :message, :type => :string } \
29
29
  << { :name => :line, :type => :integer } \
30
30
  << { :name => :file, :type => :string } \
31
- << { :name => :stack_trace, :type => :string, :anonymize => true }
31
+ << { :name => :stack_trace, :type => :string }
32
32
  end
33
33
 
34
34
 
@@ -49,42 +49,56 @@ module RequestLogAnalyzer::FileFormat
49
49
  line.teaser = /Completed in /
50
50
  line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
51
51
 
52
- line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
53
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
54
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
52
+ line.captures << { :name => :duration, :type => :duration, :unit => :sec } \
53
+ << { :name => :view, :type => :duration, :unit => :sec } \
54
+ << { :name => :db, :type => :duration, :unit => :sec } \
55
55
  << { :name => :status, :type => :integer } \
56
- << { :name => :url, :type => :string, :anonymize => :url } # Old variant
56
+ << { :name => :url, :type => :string } # Old variant
57
57
 
58
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
59
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
60
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
61
- << { :name => :status, :type => :integer} \
62
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
58
+ line.captures << { :name => :duration, :type => :duration, :unit => :msec } \
59
+ << { :name => :view, :type => :duration, :unit => :msec } \
60
+ << { :name => :db, :type => :duration, :unit => :msec } \
61
+ << { :name => :status, :type => :integer } \
62
+ << { :name => :url, :type => :string } # 2.2 variant
63
63
  end
64
64
 
65
65
 
66
66
 
67
67
  REQUEST_CATEGORIZER = Proc.new do |request|
68
- format = request[:format] || 'html'
69
- "#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
68
+ "#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
70
69
  end
71
70
 
72
71
  report do |analyze|
73
72
  analyze.timespan :line_type => :processing
74
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
75
- analyze.category :method, :title => 'HTTP methods'
76
- analyze.category :status, :title => 'HTTP statuses returned'
77
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
73
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
74
+ analyze.frequency :method, :title => 'HTTP methods'
75
+ analyze.frequency :status, :title => 'HTTP statuses returned'
76
+ analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
78
77
 
79
78
  analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
80
79
  analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
81
80
  analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
82
81
 
83
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
82
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
84
83
  :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
85
84
 
86
85
  analyze.hourly_spread :line_type => :processing
87
- analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
86
+ analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
87
+ end
88
+
89
+ # Define a custom Request class for the Rails file format to speed up timestamp handling
90
+ # and to ensure that a format is always set.
91
+ class Request < RequestLogAnalyzer::Request
92
+
93
+ # Do not use DateTime.parse
94
+ def convert_timestamp(value, definition)
95
+ value.gsub(/[^0-9]/, '')[0...14].to_i unless value.nil?
96
+ end
97
+
98
+ # Set 'html' as default format for a request
99
+ def convert_format(value, definition)
100
+ value || 'html'
101
+ end
88
102
  end
89
103
 
90
104
  end
@@ -10,8 +10,8 @@ module RequestLogAnalyzer::FileFormat
10
10
  line.captures << { :name => :controller, :type => :string } \
11
11
  << { :name => :action, :type => :string } \
12
12
  << { :name => :format, :type => :string } \
13
- << { :name => :ip, :type => :string, :anonymize => :ip } \
14
- << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
13
+ << { :name => :ip, :type => :string } \
14
+ << { :name => :timestamp, :type => :timestamp } \
15
15
  << { :name => :method, :type => :string }
16
16
  end
17
17
 
@@ -25,14 +25,14 @@ module RequestLogAnalyzer::FileFormat
25
25
  line.teaser = /Rendered /
26
26
  line.regexp = /Rendered (\w+(?:\/\w+)+) \((\d+\.\d+)ms\)/
27
27
  line.captures << { :name => :render_file, :type => :string } \
28
- << { :name => :render_duration, :type => :msec }
28
+ << { :name => :render_duration, :type => :duration, :unit => :msec }
29
29
  end
30
30
 
31
31
  # User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) 
32
32
  line_definition :query_executed do |line|
33
33
  line.regexp = /\s+(?:\e\[4;36;1m)?((?:\w+::)*\w+) Load \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0;1m)?(.+) (?:\e\[0m)?/
34
34
  line.captures << { :name => :query_class, :type => :string } \
35
- << { :name => :query_duration, :type => :msec } \
35
+ << { :name => :query_duration, :type => :duration, :unit => :msec } \
36
36
  << { :name => :query_sql, :type => :string }
37
37
  end
38
38
 
@@ -40,7 +40,7 @@ module RequestLogAnalyzer::FileFormat
40
40
  line_definition :query_cached do |line|
41
41
  line.teaser = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
42
42
  line.regexp = /\s+(?:\e\[4;35;1m)?CACHE \((\d+\.\d+)ms\)(?:\e\[0m)?\s+(?:\e\[0m)?(.+) (?:\e\[0m)?/
43
- line.captures << { :name => :cached_duration, :type => :msec } \
43
+ line.captures << { :name => :cached_duration, :type => :duration, :unit => :msec } \
44
44
  << { :name => :cached_sql, :type => :string }
45
45
  end
46
46
 
@@ -52,7 +52,7 @@ module RequestLogAnalyzer::FileFormat
52
52
  << { :name => :message, :type => :string } \
53
53
  << { :name => :line, :type => :integer } \
54
54
  << { :name => :file, :type => :string } \
55
- << { :name => :stack_trace, :type => :string, :anonymize => true }
55
+ << { :name => :stack_trace, :type => :string }
56
56
  end
57
57
 
58
58
 
@@ -73,17 +73,17 @@ module RequestLogAnalyzer::FileFormat
73
73
  line.teaser = /Completed in /
74
74
  line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
75
75
 
76
- line.captures << { :name => :duration, :type => :sec, :anonymize => :slightly } \
77
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
78
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
76
+ line.captures << { :name => :duration, :type => :duration } \
77
+ << { :name => :view, :type => :duration } \
78
+ << { :name => :db, :type => :duration } \
79
79
  << { :name => :status, :type => :integer } \
80
- << { :name => :url, :type => :string, :anonymize => :url } # Old variant
80
+ << { :name => :url, :type => :string } # Old variant
81
81
 
82
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
83
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
84
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
82
+ line.captures << { :name => :duration, :type => :duration, :unit => :msec } \
83
+ << { :name => :view, :type => :duration, :unit => :msec } \
84
+ << { :name => :db, :type => :duration, :unit => :msec } \
85
85
  << { :name => :status, :type => :integer} \
86
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
86
+ << { :name => :url, :type => :string } # 2.2 variant
87
87
  end
88
88
 
89
89
  REQUEST_CATEGORIZER = Proc.new do |request|
@@ -93,20 +93,27 @@ module RequestLogAnalyzer::FileFormat
93
93
 
94
94
  report do |analyze|
95
95
  analyze.timespan :line_type => :processing
96
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
97
- analyze.category :method, :title => 'HTTP methods'
98
- analyze.category :status, :title => 'HTTP statuses returned'
99
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
96
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
97
+ analyze.frequency :method, :title => 'HTTP methods'
98
+ analyze.frequency :status, :title => 'HTTP statuses returned'
99
+ analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
100
100
 
101
101
  analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
102
102
  analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
103
103
  analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
104
104
 
105
- analyze.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
105
+ analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
106
106
  :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
107
107
 
108
108
  analyze.hourly_spread :line_type => :processing
109
- analyze.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
109
+ analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
110
110
  end
111
+
112
+ class Request < RequestLogAnalyzer::Request
113
+
114
+ def convert_timestamp(value, definition)
115
+ value.gsub(/[^0-9]/)[0...14].to_i unless value.nil?
116
+ end
117
+ end
111
118
  end
112
119
  end
@@ -1,11 +1,11 @@
1
1
  module RequestLogAnalyzer::Filter
2
2
 
3
- # Filter to select or reject a specific field
3
+ # Filter to anonymize parsed values
4
4
  # Options
5
5
  # * <tt>:mode</tt> :reject or :accept.
6
6
  # * <tt>:field</tt> Specific field to accept or reject.
7
7
  # * <tt>:value</tt> Value that the field should match to be accepted or rejected.
8
- class Anonimize < Base
8
+ class Anonymize < Base
9
9
 
10
10
  def prepare
11
11
  end
@@ -22,9 +22,7 @@ module RequestLogAnalyzer::Filter
22
22
  value * ((75 + rand(50)) / 100.0)
23
23
  end
24
24
 
25
- def filter(request)
26
- return nil unless request
27
-
25
+ def filter(request)
28
26
  request.attributes.each do |key, value|
29
27
  if key == :ip
30
28
  request.attributes[key] = generate_random_ip
@@ -27,13 +27,9 @@ module RequestLogAnalyzer::Filter
27
27
  # Returns nil otherwise.
28
28
  # <tt>request</tt> Request Object
29
29
  def filter(request)
30
- return nil unless request
31
-
32
- found_field = request.every(@field).any? { |value| @value === value }
33
-
30
+ found_field = request.every(@field).any? { |value| @value === value.to_s }
34
31
  return nil if !found_field && @mode == :select
35
32
  return nil if found_field && @mode == :reject
36
-
37
33
  return request
38
34
  end
39
35
  end