request-log-analyzer 1.1.1 → 1.1.2

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