wvanbergen-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)
@@ -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,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.
@@ -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
@@ -22,8 +22,6 @@ module RequestLogAnalyzer::Filter
22
22
  # Returns nil otherwise
23
23
  # <tt>request</tt> Request object.
24
24
  def filter(request)
25
- return nil unless request
26
-
27
25
  if @after && @before && request.timestamp <= @before && @after <= request.timestamp
28
26
  return request
29
27
  elsif @after && @before.nil? && @after <= request.timestamp
@@ -1,22 +1,10 @@
1
1
  module RequestLogAnalyzer
2
2
 
3
- module Anonymizers
4
- def anonymizer_for_ip(value, capture_definition)
5
- '127.0.0.1'
6
- end
7
-
8
- def anonymizer_for_url(value, capture_definition)
9
- value.sub(/^https?\:\/\/[A-Za-z0-9\.-]+\//, "http://example.com/")
10
- end
11
- end
12
-
13
3
  # The line definition class is used to specify what lines should be parsed from the log file.
14
4
  # It contains functionality to match a line against the definition and parse the information
15
5
  # from this line. This is used by the LogParser class when parsing a log file..
16
6
  class LineDefinition
17
7
 
18
- include RequestLogAnalyzer::Anonymizers
19
-
20
8
  class Definer
21
9
 
22
10
  attr_accessor :line_definitions
@@ -52,20 +40,6 @@ module RequestLogAnalyzer
52
40
  return definition
53
41
  end
54
42
 
55
- # Converts a parsed value (String) to the desired value using some heuristics.
56
- def convert_value(value, type)
57
- case type
58
- when :integer; value.to_i
59
- when :float; value.to_f
60
- when :decimal; value.to_f
61
- when :symbol; value.to_sym
62
- when :sec; value.to_f
63
- when :msec; value.to_f / 1000
64
- when :timestamp; value.gsub(/[^0-9]/,'')[0..13].to_i # Retrieve with: DateTime.parse(value, '%Y%m%d%H%M%S')
65
- else value
66
- end
67
- end
68
-
69
43
  # Checks whether a given line matches this definition.
70
44
  # It will return false if a line does not match. If the line matches, a hash is returned
71
45
  # with all the fields parsed from that line as content.
@@ -74,17 +48,7 @@ module RequestLogAnalyzer
74
48
  def matches(line, lineno = nil, parser = nil)
75
49
  if @teaser.nil? || @teaser =~ line
76
50
  if match_data = line.match(@regexp)
77
- request_info = { :line_type => name, :lineno => lineno }
78
-
79
- captures.each_with_index do |capture, index|
80
- next if capture == :ignore
81
-
82
- if match_data.captures[index]
83
- request_info[capture[:name]] = convert_value(match_data.captures[index], capture[:type])
84
- end
85
-
86
- end
87
- return request_info
51
+ return { :line_definition => self, :lineno => lineno, :captures => match_data.captures}
88
52
  else
89
53
  if @teaser && parser
90
54
  parser.warn(:teaser_check_failed, "Teaser matched for #{name.inspect}, but full line did not:\n#{line.inspect}")
@@ -97,63 +61,23 @@ module RequestLogAnalyzer
97
61
  end
98
62
 
99
63
  alias :=~ :matches
100
-
101
- def anonymize_value(value, capture_definition)
102
- if capture_definition[:anonymize].respond_to?(:call)
103
- capture_definition[:anonymize].call(value, capture_definition)
64
+
65
+ def match_for(line, request, lineno = nil, parser = nil)
66
+ if match_info = matches(line, lineno, parser)
67
+ convert_captured_values(match_info[:captures], request)
104
68
  else
105
- case capture_definition[:anonymize]
106
- when nil; value
107
- when false; value
108
- when true; '***'
109
- when :slightly; anonymize_slightly(value, capture_definition)
110
- else
111
- method_name = "anonymizer_for_#{capture_definition[:anonymize]}".to_sym
112
- self.respond_to?(method_name) ? self.send(method_name, value, capture_definition) : '***'
113
- end
69
+ false
114
70
  end
115
71
  end
116
-
117
- def anonymize_slightly(value, capture_definition)
118
- case capture_definition[:type]
119
- when :integer
120
- (value.to_i * (0.8 + rand * 0.4)).to_i
121
- when :double
122
- (value.to_f * (0.8 + rand * 0.4)).to_f
123
- when :msec
124
- (value.to_i * (0.8 + rand * 0.4)).to_i
125
- when :sec
126
- (value.to_f * (0.8 + rand * 0.4)).to_f
127
- when :timestamp
128
- (DateTime.parse(value) + (rand(100) - 50)).to_s
129
- else
130
- puts "Cannot anonymize #{capture_definition[:type].inspect} slightly, using ***"
131
- '***'
132
- end
133
- end
134
72
 
135
- # Anonymize a log line
136
- def anonymize(line, options = {})
137
- if self.teaser.nil? || self.teaser =~ line
138
- if self.regexp =~ line
139
- pos_adjustment = 0
140
- captures.each_with_index do |capture, index|
141
- unless $~[index + 1].nil?
142
- anonymized_value = anonymize_value($~[index + 1], capture).to_s
143
- line[($~.begin(index + 1) + pos_adjustment)...($~.end(index + 1) + pos_adjustment)] = anonymized_value
144
- pos_adjustment += anonymized_value.length - $~[index + 1].length
145
- end
146
- end
147
- line
148
- elsif self.teaser.nil?
149
- nil
150
- else
151
- options[:discard_teaser_lines] ? "" : line
152
- end
153
- else
154
- nil
73
+ def convert_captured_values(values, request)
74
+ value_hash = {}
75
+ captures.each_with_index do |capture, index|
76
+ value_hash[capture[:name]] ||= request.convert_value(values[index], capture)
155
77
  end
78
+ return value_hash
156
79
  end
80
+
157
81
  end
158
82
 
159
83
  end
@@ -6,12 +6,9 @@ module RequestLogAnalyzer
6
6
  # contents of the line, remain it intact or remove it altogether, based on the current
7
7
  # file format
8
8
  #
9
- # Currently, two processors are supported, :strip and :anonymize.
9
+ # Currently, one processors is supported:
10
10
  # * :strip will remove all irrelevent lines (according to the file format) from the
11
11
  # sources. A compact, information packed log will remain/.
12
- # * :anonymize will anonymize sensitive information from the lines according to the
13
- # anonymization rules in the file format. The result can be passed to third parties
14
- # without privacy concerns.
15
12
  #
16
13
  class LogProcessor
17
14
 
@@ -21,8 +18,8 @@ module RequestLogAnalyzer
21
18
  attr_accessor :output_file
22
19
 
23
20
  # Builds a logprocessor instance from the arguments given on the command line
24
- # <tt>command</tt> The command hat was used to start the log processor. This can either be
25
- # :strip or :anonymize. This will set the processing mode.
21
+ # <tt>command</tt> The command hat was used to start the log processor. This will set the
22
+ # processing mode. Currently, only :strip is supported.
26
23
  # <tt>arguments</tt> The parsed command line arguments (a CommandLine::Arguments instance)
27
24
  def self.build(command, arguments)
28
25
 
@@ -43,7 +40,7 @@ module RequestLogAnalyzer
43
40
 
44
41
  # Initializes a new LogProcessor instance.
45
42
  # <tt>format</tt> The file format to use (e.g. :rails).
46
- # <tt>mode</tt> The processing mode (:anonymize or :strip)
43
+ # <tt>mode</tt> The processing mode
47
44
  # <tt>options</tt> A hash with options to take into account
48
45
  def initialize(format, mode, options = {})
49
46
  @options = options
@@ -61,12 +58,11 @@ module RequestLogAnalyzer
61
58
  end
62
59
 
63
60
  # Processes an input stream by iteration over each line and processing it according to
64
- # the current operation mode (:strip, :anonymize)
61
+ # the current operation mode
65
62
  # <tt>io</tt> The IO instance to process.
66
63
  def process_io(io)
67
64
  case mode
68
65
  when :strip; io.each_line { |line| @output << strip_line(line) }
69
- when :anonymize; io.each_line { |line| @output << anonymize_line(line) }
70
66
  end
71
67
  end
72
68
 
@@ -77,22 +73,6 @@ module RequestLogAnalyzer
77
73
  file_format.line_definitions.any? { |name, definition| definition =~ line } ? line : ""
78
74
  end
79
75
 
80
- # Returns an anonymized version of the provided line. This can be a copy of the line it self,
81
- # an empty string or a string in which some substrings are substituted for anonymized values.
82
- # <tt>line</tt> The line to anonymize
83
- def anonymize_line(line)
84
- anonymized_line = nil
85
- file_format.line_definitions.detect { |name, definition| anonymized_line = definition.anonymize(line, options) }
86
-
87
- if anonymized_line
88
- return anonymized_line
89
- elsif options[:keep_junk_lines]
90
- return line
91
- else
92
- return ""
93
- end
94
- end
95
-
96
76
  # Runs the log processing by setting up the output stream and iterating over all the
97
77
  # input sources. Input sources can either be filenames (String instances) or IO streams
98
78
  # (IO instances). The strings "-" and "STDIN" will be substituted for the $stdin variable.
@@ -9,7 +9,45 @@ module RequestLogAnalyzer
9
9
  # Request#every(field_name) returns all values corresponding to the given field name as array.
10
10
  class Request
11
11
 
12
+ module Converters
13
+
14
+ def convert_value(value, capture_definition)
15
+ custom_converter_method = "convert_#{capture_definition[:type]}".to_sym
16
+ if respond_to?(custom_converter_method)
17
+ send(custom_converter_method, value, capture_definition)
18
+ elsif !value.nil?
19
+ case capture_definition[:type]
20
+ when :decimal; value.to_f
21
+ when :float; value.to_f
22
+ when :double; value.to_f
23
+ when :integer; value.to_i
24
+ when :int; value.to_i
25
+ when :symbol; value.to_sym
26
+ else; value.to_s
27
+ end
28
+ else
29
+ nil
30
+ end
31
+ end
32
+
33
+ # Slow default method to parse timestamps
34
+ def convert_timestamp(value, capture_definition)
35
+ DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i unless value.nil?
36
+ end
37
+
38
+ def convert_duration(value, capture_definition)
39
+ if value.nil?
40
+ nil
41
+ elsif capture_definition[:unit] == :msec
42
+ value.to_f / 1000.0
43
+ else
44
+ value.to_f
45
+ end
46
+ end
47
+ end
48
+
12
49
  include RequestLogAnalyzer::FileFormat::Awareness
50
+ include Converters
13
51
 
14
52
  attr_reader :lines
15
53
  attr_reader :attributes
@@ -32,12 +70,22 @@ module RequestLogAnalyzer
32
70
 
33
71
  # Adds another line to the request.
34
72
  # The line should be provides as a hash of the fields parsed from the line.
35
- def add_parsed_line (request_info_hash)
36
- @lines << request_info_hash
37
- @attributes = request_info_hash.merge(@attributes)
73
+ def add_parsed_line (parsed_line)
74
+ value_hash = parsed_line[:line_definition].convert_captured_values(parsed_line[:captures], self)
75
+ value_hash[:line_type] = parsed_line[:line_definition].name
76
+ value_hash[:lineno] = parsed_line[:lineno]
77
+ add_line_hash(value_hash)
78
+ end
79
+
80
+ def add_line_hash(value_hash)
81
+ @lines << value_hash
82
+ @attributes = value_hash.merge(@attributes)
38
83
  end
39
84
 
40
- alias :<< :add_parsed_line
85
+
86
+ def <<(hash)
87
+ hash[:line_definition] ? add_parsed_line(hash) : add_line_hash(hash)
88
+ end
41
89
 
42
90
  # Checks whether the given line type was parsed from the log file for this request
43
91
  def has_line_type?(line_type)
@@ -81,6 +129,10 @@ module RequestLogAnalyzer
81
129
  header_found && footer_found
82
130
  end
83
131
 
132
+ # This function is called before a Requests is yielded.
133
+ def validate
134
+ end
135
+
84
136
  # Returns the first timestamp encountered in a request.
85
137
  def timestamp
86
138
  first(:timestamp)
@@ -131,7 +131,6 @@ module RequestLogAnalyzer::Source
131
131
  if header_line?(request_data)
132
132
  unless @current_request.nil?
133
133
  if options[:assume_correct_order]
134
- @parsed_requests += 1
135
134
  handle_request(@current_request, &block) #yield @current_request
136
135
  @current_request = @file_format.create_request(request_data)
137
136
  else
@@ -146,7 +145,6 @@ module RequestLogAnalyzer::Source
146
145
  unless @current_request.nil?
147
146
  @current_request << request_data
148
147
  if footer_line?(request_data)
149
- @parsed_requests += 1
150
148
  handle_request(@current_request, &block) # yield @current_request
151
149
  @current_request = nil
152
150
  end
@@ -161,18 +159,19 @@ module RequestLogAnalyzer::Source
161
159
  # The default controller will send the request to every running aggegator.
162
160
  def handle_request(request, &block)
163
161
  @parsed_requests += 1
162
+ request.validate
164
163
  accepted = block_given? ? yield(request) : true
165
164
  @skipped_requests += 1 if not accepted
166
165
  end
167
166
 
168
167
  # Checks whether a given line hash is a header line.
169
168
  def header_line?(hash)
170
- file_format.line_definitions[hash[:line_type]].header
169
+ hash[:line_definition].header
171
170
  end
172
171
 
173
172
  # Checks whether a given line hash is a footer line.
174
173
  def footer_line?(hash)
175
- file_format.line_definitions[hash[:line_type]].footer
174
+ hash[:line_definition].footer
176
175
  end
177
176
  end
178
177
 
@@ -1,6 +1,6 @@
1
1
  module RequestLogAnalyzer::Tracker
2
2
 
3
- # Catagorize requests.
3
+ # Catagorize requests by frequency.
4
4
  # Count and analyze requests for a specific attribute
5
5
  #
6
6
  # Accepts the following options:
@@ -19,7 +19,7 @@ module RequestLogAnalyzer::Tracker
19
19
  # PUT | 13685 hits (28.4%) |░░░░░░░░░░░
20
20
  # POST | 11662 hits (24.2%) |░░░░░░░░░
21
21
  # DELETE | 512 hits (1.1%) |
22
- class Category < Base
22
+ class Frequency < Base
23
23
 
24
24
  attr_reader :categories
25
25
 
@@ -4,42 +4,61 @@ describe RequestLogAnalyzer::Controller do
4
4
 
5
5
  include RequestLogAnalyzerSpecHelper
6
6
 
7
- # it "should include the file format module" do
8
- # controller = RequestLogAnalyzer::Controller.new(:rails)
9
- # (class << controller; self; end).ancestors.include?(RequestLogAnalyzer::FileFormat::Rails)
10
- # end
11
-
12
- it "should call the aggregators when run" do
7
+ it "should use a custom output generator correctly" do
13
8
 
14
- mock_output = mock('output')
15
- mock_output.stub!(:io).and_return($stdout)
9
+ mock_output = mock('RequestLogAnalyzer::Output::Base')
10
+ mock_output.stub!(:io).and_return(mock_io)
16
11
  mock_output.should_receive(:header)
17
12
  mock_output.should_receive(:footer)
13
+
14
+ file_format = RequestLogAnalyzer::FileFormat.load(:rails)
15
+ source = RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => log_fixture(:rails_1x))
16
+ controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
17
+
18
+ controller.run!
19
+ end
20
+
21
+ it "should call aggregators correctly when run" do
18
22
 
19
23
  file_format = RequestLogAnalyzer::FileFormat.load(:rails)
20
24
  source = RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => log_fixture(:rails_1x))
21
25
  controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
22
26
 
23
- mock_aggregator = mock('aggregator')
27
+ mock_aggregator = mock('RequestLogAnalyzer::Aggregator::Base')
24
28
  mock_aggregator.should_receive(:prepare).once.ordered
25
29
  mock_aggregator.should_receive(:aggregate).with(an_instance_of(file_format.class::Request)).at_least(:twice).ordered
26
30
  mock_aggregator.should_receive(:finalize).once.ordered
27
31
  mock_aggregator.should_receive(:report).once.ordered
32
+
33
+ controller.aggregators << mock_aggregator
34
+ controller.run!
35
+ end
36
+
37
+ it "should call filters when run" do
38
+ file_format = RequestLogAnalyzer::FileFormat.load(:rails)
39
+ source = RequestLogAnalyzer::Source::LogParser.new(file_format, :source_files => log_fixture(:rails_1x))
40
+ controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
28
41
 
29
- another_mock_aggregator = mock('another aggregator')
30
- another_mock_aggregator.should_receive(:prepare).once.ordered
31
- another_mock_aggregator.should_receive(:aggregate).with(an_instance_of(file_format.class::Request)).at_least(:twice).ordered
32
- another_mock_aggregator.should_receive(:finalize).once.ordered
33
- another_mock_aggregator.should_receive(:report).once.ordered
34
-
35
- controller.aggregators << mock_aggregator << another_mock_aggregator
42
+ mock_filter = mock('RequestLogAnalyzer::Filter::Base')
43
+ mock_filter.should_receive(:prepare).once.ordered
44
+ mock_filter.should_receive(:filter).at_least(:twice)
45
+
46
+ controller.should_not_receive(:aggregate_request)
47
+
48
+ controller.filters << mock_filter
36
49
  controller.run!
37
50
  end
38
51
 
39
- it "should run well from the command line" do
40
- temp_file = "#{File.dirname(__FILE__)}/fixtures/temp.txt"
41
- system("#{File.dirname(__FILE__)}/../bin/request-log-analyzer #{log_fixture(:rails_1x)} > #{temp_file}").should be_true
52
+ it "should run well from the command line with the most important features" do
53
+
54
+ temp_file = "#{File.dirname(__FILE__)}/fixtures/report.txt"
55
+ temp_db = "#{File.dirname(__FILE__)}/fixtures/output.db"
56
+ binary = "#{File.dirname(__FILE__)}/../bin/request-log-analyzer"
57
+
58
+ system("#{binary} #{log_fixture(:rails_1x)} --database #{temp_db} --select Controller PeopleController --file #{temp_file} > /dev/null").should be_true
59
+
42
60
  File.unlink(temp_file)
61
+ File.unlink(temp_db)
43
62
  end
44
63
 
45
64
  end
@@ -50,13 +50,13 @@ describe RequestLogAnalyzer::FileFormat, :format_definition do
50
50
  line.first_test :regexp => /test/, :captures => []
51
51
  end
52
52
 
53
+
53
54
  @second_file_format.format_definition do |line|
54
55
  line.second_test :regexp => /test/, :captures => []
55
56
  end
56
57
 
58
+ @first_file_format.line_definer.should_not eql(@second_file_format.line_definer)
57
59
  @first_file_format.new.should have(1).line_definitions
58
- @first_file_format.new.line_definitions[:first_test].should_not be_nil
59
- @second_file_format.new.should have(1).line_definitions
60
60
  @second_file_format.new.line_definitions[:second_test].should_not be_nil
61
61
  end
62
62
  end
@@ -4,24 +4,31 @@ class SpecFormat < RequestLogAnalyzer::FileFormat::Base
4
4
  line.header = true
5
5
  line.teaser = /processing /
6
6
  line.regexp = /processing request (\d+)/
7
- line.captures = [{ :name => :request_no, :type => :integer, :anonymize => :slightly }]
7
+ line.captures = [{ :name => :request_no, :type => :integer }]
8
8
  end
9
9
 
10
10
  format_definition.test do |line|
11
11
  line.teaser = /testing /
12
- line.regexp = /testing is (\w+)/
13
- line.captures = [{ :name => :test_capture, :type => :string, :anonymize => true}]
12
+ line.regexp = /testing is (\w+)(?: in (\d+\.\d+)ms)?/
13
+ line.captures = [{ :name => :test_capture, :type => :test_type },
14
+ { :name => :duration, :type => :duration, :unit => :msec }]
14
15
  end
15
16
 
16
17
  format_definition.last do |line|
17
18
  line.footer = true
18
19
  line.teaser = /finishing /
19
20
  line.regexp = /finishing request (\d+)/
20
- line.captures = [{ :name => :request_no, :type => :integer}]
21
+ line.captures = [{ :name => :request_no, :type => :integer }]
21
22
  end
22
23
 
23
24
  report do |analyze|
24
- analyze.category :test_capture, :title => 'What is testing exactly?'
25
+ analyze.frequency :test_capture, :title => 'What is testing exactly?'
26
+ end
27
+
28
+ class Request < RequestLogAnalyzer::Request
29
+ def convert_test_type(value, definition)
30
+ "Testing is #{value}"
31
+ end
25
32
  end
26
33
 
27
34
  end
data/spec/filter_spec.rb CHANGED
@@ -134,11 +134,11 @@ describe RequestLogAnalyzer::Filter::Field, 'regexp in accept mode' do
134
134
  end
135
135
  end
136
136
 
137
- describe RequestLogAnalyzer::Filter::Anonimize, 'anonimize request' do
137
+ describe RequestLogAnalyzer::Filter::Anonymize, 'anonymize request' do
138
138
  include RequestLogAnalyzerSpecHelper
139
139
 
140
140
  before(:each) do
141
- @filter = RequestLogAnalyzer::Filter::Anonimize.new(spec_format)
141
+ @filter = RequestLogAnalyzer::Filter::Anonymize.new(spec_format)
142
142
  @filter.prepare
143
143
  end
144
144
 
@@ -150,7 +150,7 @@ describe RequestLogAnalyzer::Filter::Anonimize, 'anonimize request' do
150
150
  @filter.filter(request(:url => 'https://test.mysite.com/employees'))[:url].should eql('http://example.com/employees')
151
151
  end
152
152
 
153
- it "should anonimize url" do
153
+ it "should fuzz durations" do
154
154
  @filter.filter(request(:duration => 100))[:duration].should_not eql(100)
155
155
  end
156
156
 
@@ -22,103 +22,36 @@ describe RequestLogAnalyzer::LineDefinition, :parsing do
22
22
  (@line_definition =~ "Testing LineDefinition, tries: 123").should be_kind_of(Hash)
23
23
  end
24
24
 
25
- it "should return a hash with all captures set" do
25
+ it "should return a hash with :captures set to an array" do
26
26
  hash = @line_definition.matches("Testing LineDefinition, tries: 123")
27
- hash[:what].should == "LineDefinition"
28
- hash[:tries].should == 123
27
+ hash[:captures][0].should == "LineDefinition"
28
+ hash[:captures][1].should == "123"
29
29
  end
30
30
 
31
- it "should return a hash with :line_type set" do
32
- @line_definition.matches("Testing LineDefinition, tries: 123")[:line_type].should == :test
33
- end
34
- end
35
-
36
- describe RequestLogAnalyzer::LineDefinition, :anonymizing_basics do
37
- before(:each) do
38
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
39
- :teaser => /Anonymize /,
40
- :regexp => /Anonymize (\w+)!/,
41
- :captures => [{ :name => :what, :type => :string }]
42
- })
43
- end
44
-
45
- it "should return nil if the teaser does not match" do
46
- @line_definition.anonymize("Nonsense").should be_nil
47
- end
48
-
49
- it "should return nil if no teaser exists and the regexp doesn't match" do
50
- line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
51
- :regexp => /Anonymize!/, :captures => []})
52
-
53
- line_definition.anonymize('nonsense').should be_nil
54
- end
55
-
56
- it "should return itself if only the teaser matches" do
57
- @line_definition.anonymize("Anonymize 456").should == "Anonymize 456"
58
- end
59
-
60
- it "should return an empty string if the teaser matches and discard_teaser_lines is set" do
61
- @line_definition.anonymize("Anonymize 456", :discard_teaser_lines => true).should == ""
31
+ it "should return a hash with :line_definition set" do
32
+ @line_definition.matches("Testing LineDefinition, tries: 123")[:line_definition].should == @line_definition
62
33
  end
63
-
64
- it "should return a string if the line matches" do
65
- @line_definition.anonymize("Anonymize anonymizing!").should be_kind_of(String)
66
- end
67
-
68
- it "should not anonymize :what" do
69
- @line_definition.anonymize("Anonymize anonymizing!").should == "Anonymize anonymizing!"
70
- end
71
34
  end
72
35
 
73
- describe RequestLogAnalyzer::LineDefinition, :anonymizing_specifics do
74
-
75
- it "should anonymize completely if anonymize is true" do
76
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
77
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => true }]})
36
+ describe RequestLogAnalyzer::LineDefinition, :converting do
78
37
 
79
- @line_definition.anonymize("Anonymize 1.2.3.4!").should == "Anonymize ***!"
80
- end
81
-
82
- it "should anonymize a URL" do
83
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
84
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => :url }]})
85
-
86
- @line_definition.anonymize("Anonymize https://www.not-anonymous.com/path/to/file.html!").should == "Anonymize http://example.com/path/to/file.html!"
87
- end
88
-
89
- it "should anonymize an IP address" do
90
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
91
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => :ip }]})
92
-
93
- @line_definition.anonymize("Anonymize 1.2.3.4!").should == "Anonymize 127.0.0.1!"
94
- end
38
+ include RequestLogAnalyzerSpecHelper
95
39
 
96
- it "should anonymize completely if the anonymizer is unknown" do
97
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
98
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :string, :anonymize => :unknown }]})
99
-
100
- @line_definition.anonymize("Anonymize 1.2.3.4!").should == "Anonymize ***!"
40
+ before(:each) do
41
+ @file_format = spec_format
42
+ @request = @file_format.create_request
101
43
  end
102
44
 
103
- it "should anonymize an integer slightly" do
104
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
105
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :integer, :anonymize => :slightly }]})
106
-
107
- @line_definition.anonymize("Anonymize 1234!").should =~ /Anonymize \d{3,4}\!/
45
+ it "should convert captures to a hash of converted values" do
46
+ hash = @file_format.line_definitions[:first].convert_captured_values(["456"], @request)
47
+ hash[:request_no].should == 456
108
48
  end
109
-
110
- it "should anonymize an integer slightly" do
111
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
112
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :integer, :anonymize => :slightly }]})
113
49
 
114
- @line_definition.anonymize("Anonymize 1234!").should =~ /Anonymize \d{3,4}\!/
50
+ it "should convert captures to a hash" do
51
+ hash = @file_format.line_definitions[:test].convert_captured_values(["willem", nil], @request)
52
+ hash[:test_capture].should == 'Testing is willem'
53
+ hash[:duration].should be_nil
115
54
  end
116
-
117
- it "should anonymize an double slightly" do
118
- @line_definition = RequestLogAnalyzer::LineDefinition.new(:test, {
119
- :regexp => /Anonymize (.+)!/, :captures => [{ :name => :what, :type => :double, :anonymize => :slightly }]})
120
55
 
121
- @line_definition.anonymize("Anonymize 1.3!").should =~ /Anonymize 1\.\d+\!/
122
- end
123
-
56
+
124
57
  end
@@ -28,10 +28,9 @@ describe RequestLogAnalyzer::Source::LogParser, :requests do
28
28
 
29
29
  it "should parse all request values when spanned over multiple files" do
30
30
  @log_parser.parse_files([log_fixture(:multiple_files_1), log_fixture(:multiple_files_2)]) do |request|
31
- request.lines.should have(4).items
32
-
31
+ request.lines.should have(4).items
33
32
  request[:request_no].should == 1
34
- request[:test_capture].should == "amazing"
33
+ request[:test_capture].should == "Testing is amazing" # Note the custom converter
35
34
  end
36
35
  end
37
36
 
@@ -1,44 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
2
  require 'request_log_analyzer/log_processor'
3
3
 
4
- describe RequestLogAnalyzer::LogProcessor, 'anonymization' do
5
-
6
- include RequestLogAnalyzerSpecHelper
7
-
8
- before(:each) do
9
- @log_anonymizer = RequestLogAnalyzer::LogProcessor.new(spec_format, :anonymize, {})
10
- @alternate_log_anonymizer = RequestLogAnalyzer::LogProcessor.new(spec_format, :anonymize, {:keep_junk_lines => true, :discard_teaser_lines => true})
11
- end
12
-
13
- it "should keep a junk line if :keep_junk_lines is true" do
14
- @alternate_log_anonymizer.anonymize_line("junk line\n").should == "junk line\n"
15
- end
16
-
17
- it "should remove a junk line" do
18
- @log_anonymizer.anonymize_line("junk line\n").should be_empty
19
- end
20
-
21
- it "should keep a teaser line intact" do
22
- @log_anonymizer.anonymize_line("processing 1234\n").should == "processing 1234\n"
23
- end
24
-
25
- it "should discard a teaser line if discard_teaser_line is true" do
26
- @alternate_log_anonymizer.anonymize_line("processing 1234\n").should be_empty
27
- end
28
-
29
- it "should keep a matching line intact if no anonymizing is declared" do
30
- @alternate_log_anonymizer.anonymize_line("finishing request 130\n").should == "finishing request 130\n"
31
- end
32
-
33
- it "should anonymize values completely if requested" do
34
- @alternate_log_anonymizer.anonymize_line("testing is great\n").should == "testing is ***\n"
35
- end
36
-
37
- it "should anonymize values slightly if requested" do
38
- @alternate_log_anonymizer.anonymize_line("finishing request 130\n").should =~ /^finishing request 1\d\d\n$/
39
- end
40
- end
41
-
42
4
  describe RequestLogAnalyzer::LogProcessor, 'stripping log files' do
43
5
 
44
6
  include RequestLogAnalyzerSpecHelper
@@ -29,7 +29,7 @@ describe RequestLogAnalyzer::Source::LogParser, :merb do
29
29
  @log_parser.parse_file(log_fixture(:merb)) { |found_request| request ||= found_request }
30
30
 
31
31
  request.should be_completed
32
- #request[:timestamp].should == DateTime.parse('Fri Aug 29 11:10:23 +0200 2008') # FIX ME
32
+ request[:timestamp].should == 20080829111023 # 'Fri Aug 29 11:10:23 +0200 2008'
33
33
  request[:dispatch_time].should == 0.243424
34
34
  request[:after_filters_time].should == 6.9e-05
35
35
  request[:before_filters_time].should == 0.213213
@@ -80,6 +80,7 @@ describe "RequestLogAnalyzer::FileFormat::RailsDevelopment - Rails with developm
80
80
 
81
81
  before(:each) do
82
82
  @file_format = RequestLogAnalyzer::FileFormat.load(:rails_development)
83
+ @request = @file_format.create_request
83
84
  end
84
85
 
85
86
  it "should have a valid language definitions" do
@@ -87,33 +88,33 @@ describe "RequestLogAnalyzer::FileFormat::RailsDevelopment - Rails with developm
87
88
  end
88
89
 
89
90
  it "should parse a rendered line" do
90
- info = @file_format.line_definitions[:rendered].matches("Rendered layouts/_footer (2.9ms)")
91
+ info = @file_format.line_definitions[:rendered].match_for("Rendered layouts/_footer (2.9ms)", @request)
91
92
  info[:render_file].should == 'layouts/_footer'
92
93
  info[:render_duration].should == 0.0029
93
94
  end
94
95
 
95
96
  it "should parse a query executed line with colors" do
96
- info = @file_format.line_definitions[:query_executed].matches(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ")
97
+ info = @file_format.line_definitions[:query_executed].match_for(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ", @request)
97
98
  info[:query_class].should == 'User'
98
99
  info[:query_duration].should == 0.0004
99
100
  info[:query_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 18205844)'
100
101
  end
101
102
 
102
103
  it "should parse a query executed line without colors" do
103
- info = @file_format.line_definitions[:query_executed].matches(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ")
104
+ info = @file_format.line_definitions[:query_executed].match_for(" User Load (0.4ms) SELECT * FROM `users` WHERE (`users`.`id` = 18205844) ", @request)
104
105
  info[:query_class].should == 'User'
105
106
  info[:query_duration].should == 0.0004
106
107
  info[:query_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 18205844)'
107
108
  end
108
109
 
109
110
  it "should parse a cached query line with colors" do
110
- info = @file_format.line_definitions[:query_cached].matches(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ')
111
+ info = @file_format.line_definitions[:query_cached].match_for(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ', @request)
111
112
  info[:cached_duration].should == 0.0
112
113
  info[:cached_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 0)'
113
114
  end
114
115
 
115
116
  it "should parse a cached query line without colors" do
116
- info = @file_format.line_definitions[:query_cached].matches(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ')
117
+ info = @file_format.line_definitions[:query_cached].match_for(' CACHE (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = 0) ', @request)
117
118
  info[:cached_duration].should == 0.0
118
119
  info[:cached_sql].should == 'SELECT * FROM `users` WHERE (`users`.`id` = 0)'
119
120
  end
data/spec/spec_helper.rb CHANGED
@@ -28,6 +28,22 @@ module RequestLogAnalyzerSpecHelper
28
28
  format.create_request(fields)
29
29
  end
30
30
  end
31
+
32
+ def mock_io
33
+ mio = mock('IO')
34
+ mio.stub!(:print)
35
+ mio.stub!(:puts)
36
+ mio.stub!(:write)
37
+ return mio
38
+ end
39
+
40
+ def mock_output
41
+ output = mock('RequestLogAnalyzer::Output')
42
+ output.stub!(:header)
43
+ output.stub!(:footer)
44
+ output.stub!(:io).and_return(mock_io)
45
+ return output
46
+ end
31
47
 
32
48
  end
33
49
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wvanbergen-request-log-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-01-21 00:00:00 -08:00
13
+ date: 2009-01-24 00:00:00 -08:00
14
14
  default_executable: request-log-analyzer
15
15
  dependencies: []
16
16
 
@@ -49,7 +49,7 @@ files:
49
49
  - lib/request_log_analyzer/file_format/rails_development.rb
50
50
  - lib/request_log_analyzer/filter
51
51
  - lib/request_log_analyzer/filter.rb
52
- - lib/request_log_analyzer/filter/anonimize.rb
52
+ - lib/request_log_analyzer/filter/anonymize.rb
53
53
  - lib/request_log_analyzer/filter/field.rb
54
54
  - lib/request_log_analyzer/filter/timespan.rb
55
55
  - lib/request_log_analyzer/line_definition.rb
@@ -64,8 +64,8 @@ files:
64
64
  - lib/request_log_analyzer/source/log_parser.rb
65
65
  - lib/request_log_analyzer/tracker
66
66
  - lib/request_log_analyzer/tracker.rb
67
- - lib/request_log_analyzer/tracker/category.rb
68
67
  - lib/request_log_analyzer/tracker/duration.rb
68
+ - lib/request_log_analyzer/tracker/frequency.rb
69
69
  - lib/request_log_analyzer/tracker/hourly_spread.rb
70
70
  - lib/request_log_analyzer/tracker/timespan.rb
71
71
  - spec